Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
ContenT
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ximper Linux
ContenT
Commits
fc7a945f
Commit
fc7a945f
authored
Feb 19, 2026
by
Roman Alifanov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add async/await background processes, on signal handlers, pid()
parent
12c3a0f1
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
637 additions
and
22 deletions
+637
-22
LANGUAGE_SPEC.md
LANGUAGE_SPEC.md
+52
-0
README.md
README.md
+35
-3
README_ru.md
README_ru.md
+35
-3
ast_nodes.py
bootstrap/ast_nodes.py
+18
-0
codegen.py
bootstrap/codegen.py
+3
-0
constants.py
bootstrap/constants.py
+2
-0
dce.py
bootstrap/dce.py
+15
-2
dispatch_codegen.py
bootstrap/dispatch_codegen.py
+77
-2
expr_codegen.py
bootstrap/expr_codegen.py
+50
-6
__init__.py
bootstrap/methods/__init__.py
+4
-0
core.py
bootstrap/methods/core.py
+5
-0
process_handle.py
bootstrap/methods/process_handle.py
+30
-0
parser.py
bootstrap/parser.py
+41
-5
stdlib.py
bootstrap/stdlib.py
+2
-0
stmt_codegen.py
bootstrap/stmt_codegen.py
+56
-1
tokens.py
bootstrap/tokens.py
+6
-0
test_async.py
tests/test_async.py
+206
-0
No files found.
LANGUAGE_SPEC.md
View file @
fc7a945f
...
@@ -752,6 +752,51 @@ func processData () {
...
@@ -752,6 +752,51 @@ func processData () {
}
}
```
```
### Фоновые процессы (async/await)
Запуск фоновых процессов. Компилируется в bash
`coproc`
(именованные, bash 4.3+).
```
# async — запуск фонового процесса
proc = async yad ("--progress", "--auto-close")
# Методы процесс-хэндла
proc.write ("50") # отправить данные в stdin процесса
proc.read () # прочитать строку из stdout процесса
proc.close () # закрыть stdin (EOF)
proc.kill () # убить процесс
proc.wait () # ждать завершения
proc.pid # PID процесса
# await — ожидание завершения
await proc
```
Контекстный менеджер с автоматическим cleanup:
```
with proc in async cmd () {
proc.write ("data")
} # auto-close + await
```
### Обработка сигналов (on)
Регистрация обработчиков сигналов. Компилируется в bash
`trap`
.
```
on SIGINT {
print ("прервано")
exit (1)
}
on EXIT {
print ("завершение")
}
```
Поддерживаемые сигналы:
`SIGINT`
,
`SIGTERM`
,
`SIGHUP`
,
`SIGUSR1`
,
`SIGUSR2`
,
`EXIT`
.
### Import
### Import
```
```
...
@@ -774,6 +819,12 @@ print ("Hello, {name}!")
...
@@ -774,6 +819,12 @@ print ("Hello, {name}!")
print (data)
print (data)
```
```
### pid ()
```
mypid = pid () # текущий PID процесса ($$)
```
### http
### http
```
```
...
@@ -1409,6 +1460,7 @@ Error: Unknown method 'badMethod' for type 'fs'. Available: append, exists, list
...
@@ -1409,6 +1460,7 @@ Error: Unknown method 'badMethod' for type 'fs'. Available: append, exists, list
-
**Словарей**
—
`del`
,
`get`
,
`has`
,
`keys`
,
`set`
-
**Словарей**
—
`del`
,
`get`
,
`has`
,
`keys`
,
`set`
-
**Строк**
—
`charAt`
,
`contains`
,
`ends`
,
`index`
,
`len`
,
`lower`
,
`replace`
,
`split`
,
`starts`
,
`substr`
,
`trim`
,
`upper`
,
`urlencode`
-
**Строк**
—
`charAt`
,
`contains`
,
`ends`
,
`index`
,
`len`
,
`lower`
,
`replace`
,
`split`
,
`starts`
,
`substr`
,
`trim`
,
`upper`
,
`urlencode`
-
**Файловых дескрипторов**
—
`close`
,
`read`
,
`readline`
,
`write`
,
`writeln`
-
**Файловых дескрипторов**
—
`close`
,
`read`
,
`readline`
,
`write`
,
`writeln`
-
**Процесс-хэндлов**
—
`close`
,
`kill`
,
`read`
,
`wait`
,
`write`
-
**Stdlib namespaces**
:
-
**Stdlib namespaces**
:
-
`fs`
—
`append`
,
`exists`
,
`list`
,
`mkdir`
,
`open`
,
`read`
,
`remove`
,
`write`
-
`fs`
—
`append`
,
`exists`
,
`list`
,
`mkdir`
,
`open`
,
`read`
,
`remove`
,
`write`
-
`http`
—
`delete`
,
`get`
,
`post`
,
`put`
-
`http`
—
`delete`
,
`get`
,
`post`
,
`put`
...
...
README.md
View file @
fc7a945f
...
@@ -20,6 +20,8 @@
...
@@ -20,6 +20,8 @@
-
**String interpolation**
—
`"Hello, {name}!"`
-
**String interpolation**
—
`"Hello, {name}!"`
-
**@awk functions**
— compile to AWK for ~300x speedup on string/numeric operations
-
**@awk functions**
— compile to AWK for ~300x speedup on string/numeric operations
-
**Compile-time method validation**
— catches unknown methods before runtime
-
**Compile-time method validation**
— catches unknown methods before runtime
-
**Background processes**
—
`async`
/
`await`
for running background processes with coproc
-
**Signal handling**
—
`on SIGINT { }`
for signal handlers (compiles to
`trap`
)
-
**Optimized output**
- no unnecessary subshells, inlined methods
-
**Optimized output**
- no unnecessary subshells, inlined methods
## Installation
## Installation
...
@@ -261,6 +263,33 @@ with f in fs.open ("/tmp/test.txt") {
...
@@ -261,6 +263,33 @@ with f in fs.open ("/tmp/test.txt") {
} # f.close() called automatically
} # f.close() called automatically
```
```
### Background Processes (async/await)
```
# Launch background process
proc = async cat ()
proc.write ("hello")
proc.close ()
await proc
# Access process PID
proc = async sleep ("10")
print ("PID: {proc.pid}")
proc.kill ()
await proc
# Automatic cleanup with 'with'
with proc in async cmd () {
proc.write ("data")
} # auto-close + await
# Signal handlers
on SIGINT {
print ("interrupted")
exit (1)
}
```
### Control Flow
### Control Flow
```
```
...
@@ -306,7 +335,7 @@ try {
...
@@ -306,7 +335,7 @@ try {
| Module | Functions |
| Module | Functions |
|--------|-----------|
|--------|-----------|
|
**I/O**
|
`print()`
,
`exit()`
|
|
**I/O**
|
`print()`
,
`exit()`
,
`pid()`
|
|
**HTTP**
|
`http.get/post/put/delete`
|
|
**HTTP**
|
`http.get/post/put/delete`
|
|
**Filesystem**
|
`fs.read/write/append/exists/remove/mkdir/list`
,
`fs.open()`
|
|
**Filesystem**
|
`fs.read/write/append/exists/remove/mkdir/list`
,
`fs.open()`
|
|
**File handles**
|
`f.read()`
,
`f.readline()`
,
`f.write()`
,
`f.writeln()`
,
`f.close()`
|
|
**File handles**
|
`f.read()`
,
`f.readline()`
,
`f.write()`
,
`f.writeln()`
,
`f.close()`
|
...
@@ -323,6 +352,8 @@ try {
...
@@ -323,6 +352,8 @@ try {
|
**Args**
|
`args.count/get`
|
|
**Args**
|
`args.count/get`
|
|
**Logger**
|
`logger.info/warn/error/debug`
|
|
**Logger**
|
`logger.info/warn/error/debug`
|
|
**Env**
|
`env.VAR`
read,
`env.VAR = value`
set — environment variables |
|
**Env**
|
`env.VAR`
read,
`env.VAR = value`
set — environment variables |
|
**Process**
|
`proc.write()`
,
`proc.read()`
,
`proc.close()`
,
`proc.kill()`
,
`proc.wait()`
,
`proc.pid`
|
|
**Signals**
|
`on SIGINT/SIGTERM/SIGHUP/SIGUSR1/SIGUSR2/EXIT { }`
|
## CLI Commands
## CLI Commands
...
@@ -502,7 +533,7 @@ bootstrap/ # Bootstrap compiler (Python)
...
@@ -502,7 +533,7 @@ bootstrap/ # Bootstrap compiler (Python)
│ ├── string.py # String methods
│ ├── string.py # String methods
│ ├── array.py # Array methods
│ ├── array.py # Array methods
│ ├── dict.py # Dict methods
│ ├── dict.py # Dict methods
│ └── ... # http, fs, json, logger, math, time, etc.
│ └── ... # http, fs, json, logger, math, time,
process_handle,
etc.
├── dce.py # Dead code elimination
├── dce.py # Dead code elimination
├── codegen.py # Main Bash code generator (mixin coordinator)
├── codegen.py # Main Bash code generator (mixin coordinator)
├── expr_codegen.py # Expression generation (mixin)
├── expr_codegen.py # Expression generation (mixin)
...
@@ -528,7 +559,8 @@ tests/ # Test suite
...
@@ -528,7 +559,8 @@ tests/ # Test suite
├── test_stdlib.py # Standard library (env, json, fs, with)
├── test_stdlib.py # Standard library (env, json, fs, with)
├── test_decorators.py # Decorators, typing, @test, user decorators
├── test_decorators.py # Decorators, typing, @test, user decorators
├── test_awk.py # AWK functions (map/filter, sync, assert)
├── test_awk.py # AWK functions (map/filter, sync, assert)
└── test_shell.py # Shell commands, pipes, mixed pipes
├── test_shell.py # Shell commands, pipes, mixed pipes
└── test_async.py # Background processes (async/await/on, pid)
examples/ # Example .ct programs
examples/ # Example .ct programs
```
```
...
...
README_ru.md
View file @
fc7a945f
...
@@ -20,6 +20,8 @@
...
@@ -20,6 +20,8 @@
-
**Строковая интерполяция**
—
`"Привет, {name}!"`
-
**Строковая интерполяция**
—
`"Привет, {name}!"`
-
**@awk функции**
— компиляция в AWK для ускорения ~300x на строковых/числовых операциях
-
**@awk функции**
— компиляция в AWK для ускорения ~300x на строковых/числовых операциях
-
**Проверка методов при компиляции**
— выявление несуществующих методов до запуска
-
**Проверка методов при компиляции**
— выявление несуществующих методов до запуска
-
**Фоновые процессы**
—
`async`
/
`await`
для запуска фоновых процессов через coproc
-
**Обработка сигналов**
—
`on SIGINT { }`
для обработчиков сигналов (компилируется в
`trap`
)
-
**Оптимизированный вывод**
— без лишних subshell, инлайнинг методов
-
**Оптимизированный вывод**
— без лишних subshell, инлайнинг методов
## Установка
## Установка
...
@@ -261,6 +263,33 @@ with f in fs.open ("/tmp/test.txt") {
...
@@ -261,6 +263,33 @@ with f in fs.open ("/tmp/test.txt") {
} # f.close() вызывается автоматически
} # f.close() вызывается автоматически
```
```
### Фоновые процессы (async/await)
```
# Запуск фонового процесса
proc = async cat ()
proc.write ("hello")
proc.close ()
await proc
# Доступ к PID процесса
proc = async sleep ("10")
print ("PID: {proc.pid}")
proc.kill ()
await proc
# Автоматический cleanup через 'with'
with proc in async cmd () {
proc.write ("data")
} # auto-close + await
# Обработчики сигналов
on SIGINT {
print ("прервано")
exit (1)
}
```
### Условия и циклы
### Условия и циклы
```
```
...
@@ -306,7 +335,7 @@ try {
...
@@ -306,7 +335,7 @@ try {
| Модуль | Функции |
| Модуль | Функции |
|--------|---------|
|--------|---------|
|
**Ввод/вывод**
|
`print()`
,
`exit()`
|
|
**Ввод/вывод**
|
`print()`
,
`exit()`
,
`pid()`
|
|
**HTTP**
|
`http.get/post/put/delete`
|
|
**HTTP**
|
`http.get/post/put/delete`
|
|
**Файловая система**
|
`fs.read/write/append/exists/remove/mkdir/list`
,
`fs.open()`
|
|
**Файловая система**
|
`fs.read/write/append/exists/remove/mkdir/list`
,
`fs.open()`
|
|
**Файловые дескрипторы**
|
`f.read()`
,
`f.readline()`
,
`f.write()`
,
`f.writeln()`
,
`f.close()`
|
|
**Файловые дескрипторы**
|
`f.read()`
,
`f.readline()`
,
`f.write()`
,
`f.writeln()`
,
`f.close()`
|
...
@@ -323,6 +352,8 @@ try {
...
@@ -323,6 +352,8 @@ try {
|
**Аргументы**
|
`args.count/get`
|
|
**Аргументы**
|
`args.count/get`
|
|
**Логгер**
|
`logger.info/warn/error/debug`
|
|
**Логгер**
|
`logger.info/warn/error/debug`
|
|
**Окружение**
|
`env.VAR`
чтение,
`env.VAR = value`
установка — переменные окружения |
|
**Окружение**
|
`env.VAR`
чтение,
`env.VAR = value`
установка — переменные окружения |
|
**Процессы**
|
`proc.write()`
,
`proc.read()`
,
`proc.close()`
,
`proc.kill()`
,
`proc.wait()`
,
`proc.pid`
|
|
**Сигналы**
|
`on SIGINT/SIGTERM/SIGHUP/SIGUSR1/SIGUSR2/EXIT { }`
|
## Команды CLI
## Команды CLI
...
@@ -494,7 +525,7 @@ bootstrap/ # Bootstrap-компилятор (Python)
...
@@ -494,7 +525,7 @@ bootstrap/ # Bootstrap-компилятор (Python)
│ ├── string.py # Строковые методы
│ ├── string.py # Строковые методы
│ ├── array.py # Методы массивов
│ ├── array.py # Методы массивов
│ ├── dict.py # Методы словарей
│ ├── dict.py # Методы словарей
│ └── ... # http, fs, json, logger, math, time, etc.
│ └── ... # http, fs, json, logger, math, time,
process_handle,
etc.
├── dce.py # Устранение мёртвого кода
├── dce.py # Устранение мёртвого кода
├── codegen.py # Основной генератор Bash-кода (координатор миксинов)
├── codegen.py # Основной генератор Bash-кода (координатор миксинов)
├── expr_codegen.py # Генерация выражений (миксин)
├── expr_codegen.py # Генерация выражений (миксин)
...
@@ -520,7 +551,8 @@ tests/ # Тестовый набор
...
@@ -520,7 +551,8 @@ tests/ # Тестовый набор
├── test_stdlib.py # Стандартная библиотека (env, json, fs, with)
├── test_stdlib.py # Стандартная библиотека (env, json, fs, with)
├── test_decorators.py # Декораторы, типизация, @test
├── test_decorators.py # Декораторы, типизация, @test
├── test_awk.py # AWK-функции
├── test_awk.py # AWK-функции
└── test_shell.py # Shell-команды, pipe
├── test_shell.py # Shell-команды, pipe
└── test_async.py # Фоновые процессы (async/await/on, pid)
examples/ # Примеры .ct программ
examples/ # Примеры .ct программ
```
```
...
...
bootstrap/ast_nodes.py
View file @
fc7a945f
...
@@ -141,6 +141,11 @@ class NewExpr (Expression):
...
@@ -141,6 +141,11 @@ class NewExpr (Expression):
location
:
Optional
[
SourceLocation
]
=
None
location
:
Optional
[
SourceLocation
]
=
None
@dataclass
class
AsyncExpr
(
Expression
):
expression
:
Optional
[
Expression
]
=
None
location
:
Optional
[
SourceLocation
]
=
None
@dataclass
@dataclass
class
Statement
(
ASTNode
):
class
Statement
(
ASTNode
):
...
@@ -251,6 +256,19 @@ class ImportStmt (Statement):
...
@@ -251,6 +256,19 @@ class ImportStmt (Statement):
@dataclass
@dataclass
class
AwaitStmt
(
Statement
):
expression
:
Optional
[
Expression
]
=
None
location
:
Optional
[
SourceLocation
]
=
None
@dataclass
class
OnSignalStmt
(
Statement
):
signal
:
str
=
""
body
:
Optional
[
'Block'
]
=
None
location
:
Optional
[
SourceLocation
]
=
None
@dataclass
class
RangePattern
(
Expression
):
class
RangePattern
(
Expression
):
"""Range pattern for when branches: 1..10"""
"""Range pattern for when branches: 1..10"""
start
:
Optional
[
Expression
]
=
None
start
:
Optional
[
Expression
]
=
None
...
...
bootstrap/codegen.py
View file @
fc7a945f
...
@@ -52,6 +52,9 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin,
...
@@ -52,6 +52,9 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin,
self
.
dict_vars
:
Set
[
str
]
=
set
()
self
.
dict_vars
:
Set
[
str
]
=
set
()
self
.
object_vars
:
Set
[
str
]
=
set
()
self
.
object_vars
:
Set
[
str
]
=
set
()
self
.
file_handle_vars
:
Set
[
str
]
=
set
()
self
.
file_handle_vars
:
Set
[
str
]
=
set
()
self
.
process_handle_vars
:
Set
[
str
]
=
set
()
self
.
process_handle_map
:
Dict
[
str
,
str
]
=
{}
self
.
coproc_counter
=
0
self
.
nameref_vars
:
Set
[
str
]
=
set
()
# vars that are namerefs to arrays/dicts
self
.
nameref_vars
:
Set
[
str
]
=
set
()
# vars that are namerefs to arrays/dicts
self
.
callback_vars
:
Set
[
str
]
=
set
()
# vars that hold function names (callbacks)
self
.
callback_vars
:
Set
[
str
]
=
set
()
# vars that hold function names (callbacks)
self
.
instance_vars
:
Dict
[
str
,
str
]
=
{}
# var_name -> class_name
self
.
instance_vars
:
Dict
[
str
,
str
]
=
{}
# var_name -> class_name
...
...
bootstrap/constants.py
View file @
fc7a945f
...
@@ -18,3 +18,5 @@ FS_FUNC_PREFIX = "__ct_fs_"
...
@@ -18,3 +18,5 @@ FS_FUNC_PREFIX = "__ct_fs_"
JSON_FUNC_PREFIX
=
"__ct_json_"
JSON_FUNC_PREFIX
=
"__ct_json_"
REGEX_FUNC_PREFIX
=
"__ct_regex_"
REGEX_FUNC_PREFIX
=
"__ct_regex_"
MATH_FUNC_PREFIX
=
"__ct_math_"
MATH_FUNC_PREFIX
=
"__ct_math_"
COPROC_PREFIX
=
"__ct_cp"
bootstrap/dce.py
View file @
fc7a945f
...
@@ -5,7 +5,8 @@ import re
...
@@ -5,7 +5,8 @@ import re
from
.ast_nodes
import
(
from
.ast_nodes
import
(
ClassDecl
,
NewExpr
,
CallExpr
,
Identifier
,
FunctionDecl
,
Assignment
,
ClassDecl
,
NewExpr
,
CallExpr
,
Identifier
,
FunctionDecl
,
Assignment
,
ExpressionStmt
,
IfStmt
,
ForStmt
,
ForeachStmt
,
WhileStmt
,
WhenStmt
,
ExpressionStmt
,
IfStmt
,
ForStmt
,
ForeachStmt
,
WhileStmt
,
WhenStmt
,
WhenBranch
,
TryStmt
,
ThrowStmt
,
DeferStmt
,
ReturnStmt
,
ArrayLiteral
,
WhenBranch
,
TryStmt
,
ThrowStmt
,
DeferStmt
,
AwaitStmt
,
OnSignalStmt
,
AsyncExpr
,
ReturnStmt
,
ArrayLiteral
,
DictLiteral
,
IndexAccess
,
Lambda
,
MemberAccess
,
ThisExpr
,
Block
,
DictLiteral
,
IndexAccess
,
Lambda
,
MemberAccess
,
ThisExpr
,
Block
,
BinaryOp
,
UnaryOp
,
WithStmt
,
Program
,
StringLiteral
BinaryOp
,
UnaryOp
,
WithStmt
,
Program
,
StringLiteral
)
)
...
@@ -16,7 +17,7 @@ class UsageAnalyzer:
...
@@ -16,7 +17,7 @@ class UsageAnalyzer:
CATEGORIES
=
{
CATEGORIES
=
{
'core'
,
'object'
,
'http'
,
'fs'
,
'json'
,
'logger'
,
'string'
,
'core'
,
'object'
,
'http'
,
'fs'
,
'json'
,
'logger'
,
'string'
,
'array'
,
'dict'
,
'regex'
,
'math'
,
'time'
,
'awk'
,
'exception'
,
'array'
,
'dict'
,
'regex'
,
'math'
,
'time'
,
'awk'
,
'exception'
,
'args'
,
'misc'
,
'args'
,
'misc'
,
'async'
,
}
}
ARRAY_RETURNING_METHODS
=
{
'keys'
,
'split'
,
'slice'
}
ARRAY_RETURNING_METHODS
=
{
'keys'
,
'split'
,
'slice'
}
...
@@ -259,6 +260,14 @@ class UsageAnalyzer:
...
@@ -259,6 +260,14 @@ class UsageAnalyzer:
self
.
used
.
add
(
'exception'
)
self
.
used
.
add
(
'exception'
)
self
.
_analyze_expr
(
stmt
.
expression
)
self
.
_analyze_expr
(
stmt
.
expression
)
elif
isinstance
(
stmt
,
AwaitStmt
):
self
.
used
.
add
(
'async'
)
self
.
_analyze_expr
(
stmt
.
expression
)
elif
isinstance
(
stmt
,
OnSignalStmt
):
self
.
used
.
add
(
'async'
)
self
.
_analyze_body
(
stmt
.
body
)
elif
isinstance
(
stmt
,
ReturnStmt
):
elif
isinstance
(
stmt
,
ReturnStmt
):
if
stmt
.
value
:
if
stmt
.
value
:
self
.
_analyze_expr
(
stmt
.
value
)
self
.
_analyze_expr
(
stmt
.
value
)
...
@@ -313,6 +322,10 @@ class UsageAnalyzer:
...
@@ -313,6 +322,10 @@ class UsageAnalyzer:
elif
isinstance
(
expr
,
Lambda
):
elif
isinstance
(
expr
,
Lambda
):
self
.
_analyze_body
(
expr
.
body
)
self
.
_analyze_body
(
expr
.
body
)
elif
isinstance
(
expr
,
AsyncExpr
):
self
.
used
.
add
(
'async'
)
self
.
_analyze_expr
(
expr
.
expression
)
elif
isinstance
(
expr
,
NewExpr
):
elif
isinstance
(
expr
,
NewExpr
):
self
.
has_classes
=
True
self
.
has_classes
=
True
self
.
used_classes
.
add
(
expr
.
class_name
)
self
.
used_classes
.
add
(
expr
.
class_name
)
...
...
bootstrap/dispatch_codegen.py
View file @
fc7a945f
from
.ast_nodes
import
(
from
.ast_nodes
import
(
CallExpr
,
MemberAccess
,
Identifier
,
ThisExpr
,
Assignment
,
ArrayLiteral
,
CallExpr
,
MemberAccess
,
Identifier
,
ThisExpr
,
Assignment
,
ArrayLiteral
,
DictLiteral
,
NewExpr
,
Lambda
,
ExpressionStmt
,
BaseCall
,
ReturnStmt
,
DictLiteral
,
NewExpr
,
AsyncExpr
,
Lambda
,
ExpressionStmt
,
BaseCall
,
ReturnStmt
,
StringLiteral
,
BinaryOp
,
IndexAccess
,
TypeAnnotation
,
IntegerLiteral
,
StringLiteral
,
BinaryOp
,
IndexAccess
,
TypeAnnotation
,
IntegerLiteral
,
FloatLiteral
,
BoolLiteral
FloatLiteral
,
BoolLiteral
)
)
from
.methods
import
(
from
.methods
import
(
STRING_METHODS
,
ARRAY_METHODS
,
DICT_METHODS
,
FILE_HANDLE_METHODS
,
STRING_METHODS
,
ARRAY_METHODS
,
DICT_METHODS
,
FILE_HANDLE_METHODS
,
PROCESS_HANDLE_METHODS
,
NAMESPACE_METHODS
,
BUILTIN_NAMESPACES
,
BUILTIN_FUNCS
,
get_method_names
NAMESPACE_METHODS
,
BUILTIN_NAMESPACES
,
BUILTIN_FUNCS
,
get_method_names
)
)
from
.constants
import
RET_VAR
,
RET_ARR
from
.constants
import
RET_VAR
,
RET_ARR
,
COPROC_PREFIX
ARR_METHODS
=
{
name
:
m
.
bash_func
for
name
,
m
in
ARRAY_METHODS
.
items
()}
ARR_METHODS
=
{
name
:
m
.
bash_func
for
name
,
m
in
ARRAY_METHODS
.
items
()}
STR_METHODS
=
{
name
:
m
.
bash_func
for
name
,
m
in
STRING_METHODS
.
items
()}
STR_METHODS
=
{
name
:
m
.
bash_func
for
name
,
m
in
STRING_METHODS
.
items
()}
...
@@ -206,6 +207,10 @@ class DispatchMixin:
...
@@ -206,6 +207,10 @@ class DispatchMixin:
column
=
stmt
.
location
.
column
if
stmt
.
location
else
0
column
=
stmt
.
location
.
column
if
stmt
.
location
else
0
)
)
if
isinstance
(
stmt
.
value
,
AsyncExpr
):
self
.
_generate_async_assignment
(
stmt
,
target
)
return
if
isinstance
(
stmt
.
value
,
BinaryOp
)
and
stmt
.
value
.
operator
==
"|"
:
if
isinstance
(
stmt
.
value
,
BinaryOp
)
and
stmt
.
value
.
operator
==
"|"
:
self
.
_generate_pipe_assignment
(
stmt
,
target
)
self
.
_generate_pipe_assignment
(
stmt
,
target
)
return
return
...
@@ -590,6 +595,13 @@ class DispatchMixin:
...
@@ -590,6 +595,13 @@ class DispatchMixin:
else
:
else
:
self
.
_validate_type_method
(
"file_handle"
,
method
,
location
)
self
.
_validate_type_method
(
"file_handle"
,
method
,
location
)
if
obj_name
in
self
.
process_handle_vars
:
if
method
in
PROCESS_HANDLE_METHODS
:
self
.
_generate_process_method
(
obj_name
,
method
,
args
,
target
)
return
True
else
:
self
.
_validate_type_method
(
"process_handle"
,
method
,
location
)
if
obj_name
in
self
.
object_vars
:
if
obj_name
in
self
.
object_vars
:
ret_type
=
self
.
_get_method_return_type
(
obj_name
,
method
)
ret_type
=
self
.
_get_method_return_type
(
obj_name
,
method
)
obj
=
self
.
generate_expr
(
callee
.
object
)
obj
=
self
.
generate_expr
(
callee
.
object
)
...
@@ -732,6 +744,43 @@ class DispatchMixin:
...
@@ -732,6 +744,43 @@ class DispatchMixin:
self
.
emit
(
f
'declare -gA {target}=()'
)
self
.
emit
(
f
'declare -gA {target}=()'
)
self
.
dict_vars
.
add
(
target
)
self
.
dict_vars
.
add
(
target
)
def
_generate_async_assignment
(
self
,
stmt
:
Assignment
,
target
:
str
):
self
.
coproc_counter
+=
1
cp_name
=
f
'{COPROC_PREFIX}{self.coproc_counter}'
inner
=
stmt
.
value
.
expression
if
isinstance
(
inner
,
CallExpr
)
and
self
.
_is_shell_command
(
inner
):
cmd_str
=
self
.
_extract_shell_command_str
(
inner
)
elif
isinstance
(
inner
,
CallExpr
)
and
isinstance
(
inner
.
callee
,
Identifier
):
func_name
=
inner
.
callee
.
name
args_str
=
self
.
_generate_call_args_str
(
inner
.
arguments
)
cmd_str
=
f
'exec 3>&1; {func_name} {args_str}'
.
strip
()
else
:
cmd_str
=
self
.
generate_expr
(
inner
)
self
.
emit
(
f
'coproc {cp_name} {{ {cmd_str}; }}'
)
self
.
emit
(
f
'{cp_name}_wr=${{{cp_name}[1]}}'
)
self
.
emit
(
f
'{cp_name}_rd=${{{cp_name}[0]}}'
)
self
.
process_handle_vars
.
add
(
target
)
self
.
process_handle_map
[
target
]
=
cp_name
def
_generate_process_method
(
self
,
var_name
:
str
,
method
:
str
,
args
:
list
,
target
:
str
=
None
):
cp
=
self
.
process_handle_map
[
var_name
]
if
method
==
"write"
:
data
=
args
[
0
]
if
args
else
""
self
.
emit
(
f
'echo "{data}" >&${cp}_wr'
)
elif
method
==
"read"
:
self
.
emit
(
f
'read -r {RET_VAR} <&${cp}_rd'
)
self
.
emit
(
f
'echo "${RET_VAR}"'
)
if
target
:
self
.
emit_var_assign
(
target
,
f
'${RET_VAR}'
)
elif
method
==
"close"
:
self
.
emit
(
f
'exec {{{cp}_wr}}>&-'
)
elif
method
==
"kill"
:
self
.
emit
(
f
'kill ${cp}_PID 2>/dev/null || true'
)
elif
method
==
"wait"
:
self
.
emit
(
f
'wait ${cp}_PID 2>/dev/null || true'
)
def
_generate_pipe_assignment
(
self
,
stmt
:
Assignment
,
target
:
str
):
def
_generate_pipe_assignment
(
self
,
stmt
:
Assignment
,
target
:
str
):
"""Generate pipe expression assignment."""
"""Generate pipe expression assignment."""
elements
=
self
.
_collect_pipe_chain
(
stmt
.
value
)
elements
=
self
.
_collect_pipe_chain
(
stmt
.
value
)
...
@@ -1023,6 +1072,14 @@ class DispatchMixin:
...
@@ -1023,6 +1072,14 @@ class DispatchMixin:
self
.
_validate_type_method
(
"file_handle"
,
method
,
location
)
self
.
_validate_type_method
(
"file_handle"
,
method
,
location
)
return
False
return
False
if
var_name
in
self
.
process_handle_vars
:
if
method
in
PROCESS_HANDLE_METHODS
:
self
.
_generate_process_method
(
var_name
,
method
,
args
)
return
True
else
:
self
.
_validate_type_method
(
"process_handle"
,
method
,
location
)
return
False
if
method
in
STR_METHODS
:
if
method
in
STR_METHODS
:
func_name
=
STR_METHODS
[
method
]
func_name
=
STR_METHODS
[
method
]
args_str
=
" "
.
join
([
f
'"{a}"'
for
a
in
args
])
args_str
=
" "
.
join
([
f
'"{a}"'
for
a
in
args
])
...
@@ -1080,6 +1137,8 @@ class DispatchMixin:
...
@@ -1080,6 +1137,8 @@ class DispatchMixin:
return
f
'__ct_assert "{args[0]}"'
return
f
'__ct_assert "{args[0]}"'
elif
name
==
"assert_eq"
:
elif
name
==
"assert_eq"
:
return
f
'__ct_assert_eq {args_str}'
return
f
'__ct_assert_eq {args_str}'
elif
name
==
"pid"
:
return
'__ct_pid'
else
:
else
:
if
self
.
_is_callback_var
(
name
):
if
self
.
_is_callback_var
(
name
):
return
f
'"${{{name}}}" {args_str}'
return
f
'"${{{name}}}" {args_str}'
...
@@ -1175,6 +1234,22 @@ class DispatchMixin:
...
@@ -1175,6 +1234,22 @@ class DispatchMixin:
else
:
else
:
self
.
_validate_type_method
(
"file_handle"
,
method
,
location
)
self
.
_validate_type_method
(
"file_handle"
,
method
,
location
)
if
var_name
and
var_name
in
self
.
process_handle_vars
:
if
method
in
PROCESS_HANDLE_METHODS
:
cp
=
self
.
process_handle_map
[
var_name
]
if
method
==
"read"
:
return
f
'read -r {RET_VAR} <&${cp}_rd && echo "${RET_VAR}"'
elif
method
==
"write"
:
return
f
'echo {args_str} >&${cp}_wr'
elif
method
==
"close"
:
return
f
'exec {{{cp}_wr}}>&-'
elif
method
==
"kill"
:
return
f
'kill ${cp}_PID 2>/dev/null || true'
elif
method
==
"wait"
:
return
f
'wait ${cp}_PID 2>/dev/null || true'
else
:
self
.
_validate_type_method
(
"process_handle"
,
method
,
location
)
if
method
in
STR_METHODS
:
if
method
in
STR_METHODS
:
return
f
'{STR_METHODS[method]} "{obj}" {args_str}'
.
strip
()
return
f
'{STR_METHODS[method]} "{obj}" {args_str}'
.
strip
()
...
...
bootstrap/expr_codegen.py
View file @
fc7a945f
...
@@ -2,8 +2,8 @@ import re
...
@@ -2,8 +2,8 @@ import re
from
.ast_nodes
import
(
from
.ast_nodes
import
(
Expression
,
IntegerLiteral
,
FloatLiteral
,
StringLiteral
,
BoolLiteral
,
Expression
,
IntegerLiteral
,
FloatLiteral
,
StringLiteral
,
BoolLiteral
,
NilLiteral
,
Identifier
,
ThisExpr
,
ArrayLiteral
,
DictLiteral
,
BinaryOp
,
NilLiteral
,
Identifier
,
ThisExpr
,
ArrayLiteral
,
DictLiteral
,
BinaryOp
,
UnaryOp
,
CallExpr
,
MemberAccess
,
IndexAccess
,
Lambda
,
NewExpr
,
BaseCall
,
UnaryOp
,
CallExpr
,
MemberAccess
,
IndexAccess
,
Lambda
,
NewExpr
,
AsyncExpr
,
Block
,
ReturnStmt
B
aseCall
,
B
lock
,
ReturnStmt
)
)
...
@@ -71,6 +71,9 @@ class ExprMixin:
...
@@ -71,6 +71,9 @@ class ExprMixin:
args_str
=
" "
.
join
([
f
'"{a}"'
for
a
in
args
])
args_str
=
" "
.
join
([
f
'"{a}"'
for
a
in
args
])
return
f
'$({expr.class_name} {args_str}; echo "$__ct_last_instance")'
return
f
'$({expr.class_name} {args_str}; echo "$__ct_last_instance")'
if
isinstance
(
expr
,
AsyncExpr
):
return
self
.
_generate_async_expr
(
expr
)
if
isinstance
(
expr
,
BaseCall
):
if
isinstance
(
expr
,
BaseCall
):
args
=
[
self
.
generate_expr
(
a
)
for
a
in
expr
.
arguments
]
args
=
[
self
.
generate_expr
(
a
)
for
a
in
expr
.
arguments
]
args_str
=
" "
.
join
([
f
'"{a}"'
for
a
in
args
])
args_str
=
" "
.
join
([
f
'"{a}"'
for
a
in
args
])
...
@@ -109,7 +112,11 @@ class ExprMixin:
...
@@ -109,7 +112,11 @@ class ExprMixin:
parts
=
content
.
split
(
'.'
,
1
)
parts
=
content
.
split
(
'.'
,
1
)
if
parts
[
0
]
==
'this'
:
if
parts
[
0
]
==
'this'
:
return
f
'
\\
$${{__CT_OBJ["$this.{parts[1]}"]}}'
return
f
'
\\
$${{__CT_OBJ["$this.{parts[1]}"]}}'
else
:
ph_vars
=
getattr
(
self
,
'process_handle_vars'
,
set
())
ph_map
=
getattr
(
self
,
'process_handle_map'
,
{})
if
parts
[
0
]
in
ph_vars
and
parts
[
1
]
==
'pid'
:
cp
=
ph_map
[
parts
[
0
]]
return
f
'
\\
$${cp}_PID'
return
f
'
\\
$${{__CT_OBJ["${{{parts[0]}}}.{parts[1]}"]:-}}'
return
f
'
\\
$${{__CT_OBJ["${{{parts[0]}}}.{parts[1]}"]:-}}'
return
f
'
\\
$${{{content}}}'
return
f
'
\\
$${{{content}}}'
...
@@ -128,7 +135,11 @@ class ExprMixin:
...
@@ -128,7 +135,11 @@ class ExprMixin:
parts
=
content
.
split
(
'.'
,
1
)
parts
=
content
.
split
(
'.'
,
1
)
if
parts
[
0
]
==
'this'
:
if
parts
[
0
]
==
'this'
:
return
f
'${{__CT_OBJ["$this.{parts[1]}"]}}'
return
f
'${{__CT_OBJ["$this.{parts[1]}"]}}'
else
:
ph_vars
=
getattr
(
self
,
'process_handle_vars'
,
set
())
ph_map
=
getattr
(
self
,
'process_handle_map'
,
{})
if
parts
[
0
]
in
ph_vars
and
parts
[
1
]
==
'pid'
:
cp
=
ph_map
[
parts
[
0
]]
return
f
'${cp}_PID'
return
f
'${{__CT_OBJ["${{{parts[0]}}}.{parts[1]}"]:-}}'
return
f
'${{__CT_OBJ["${{{parts[0]}}}.{parts[1]}"]:-}}'
return
f
'${{{content}}}'
return
f
'${{{content}}}'
...
@@ -261,12 +272,21 @@ class ExprMixin:
...
@@ -261,12 +272,21 @@ class ExprMixin:
paren_idx
=
content
.
find
(
'('
)
paren_idx
=
content
.
find
(
'('
)
func_name
=
content
[:
paren_idx
]
.
strip
()
func_name
=
content
[:
paren_idx
]
.
strip
()
args_part
=
content
[
paren_idx
+
1
:
-
1
]
.
strip
()
args_part
=
content
[
paren_idx
+
1
:
-
1
]
.
strip
()
interp_builtin_map
=
{
'print'
:
'__ct_print'
,
'len'
:
'__ct_str_len'
,
'exit'
:
'__ct_exit'
,
'range'
:
'__ct_range'
,
'is_number'
:
'__ct_is_number'
,
'is_empty'
:
'__ct_is_empty'
,
'pid'
:
'__ct_pid'
,
'random'
:
'__ct_random'
,
'random_range'
:
'__ct_random_range'
,
}
bash_name
=
interp_builtin_map
.
get
(
func_name
,
func_name
)
if
args_part
:
if
args_part
:
args_list
=
self
.
_parse_args_with_parens
(
args_part
)
args_list
=
self
.
_parse_args_with_parens
(
args_part
)
args_bash
=
' '
.
join
([
self
.
_convert_arg_to_bash
(
a
)
for
a
in
args_list
])
args_bash
=
' '
.
join
([
self
.
_convert_arg_to_bash
(
a
)
for
a
in
args_list
])
return
f
'$({
func
_name} {args_bash})'
return
f
'$({
bash
_name} {args_bash})'
else
:
else
:
return
f
'$({
func
_name})'
return
f
'$({
bash
_name})'
def
_parse_args_with_parens
(
self
,
args_str
:
str
)
->
list
:
def
_parse_args_with_parens
(
self
,
args_str
:
str
)
->
list
:
"""Parse comma-separated args, respecting nested parentheses."""
"""Parse comma-separated args, respecting nested parentheses."""
...
@@ -433,12 +453,36 @@ class ExprMixin:
...
@@ -433,12 +453,36 @@ class ExprMixin:
return
None
return
None
def
_generate_async_expr
(
self
,
expr
:
AsyncExpr
)
->
str
:
from
.constants
import
COPROC_PREFIX
self
.
coproc_counter
+=
1
cp_name
=
f
'{COPROC_PREFIX}{self.coproc_counter}'
inner
=
expr
.
expression
if
isinstance
(
inner
,
CallExpr
)
and
self
.
_is_shell_command
(
inner
):
cmd_str
=
self
.
_extract_shell_command_str
(
inner
)
elif
isinstance
(
inner
,
CallExpr
)
and
isinstance
(
inner
.
callee
,
Identifier
):
func_name
=
inner
.
callee
.
name
args_str
=
self
.
_generate_call_args_str
(
inner
.
arguments
)
cmd_str
=
f
'exec 3>&1; {func_name} {args_str}'
.
strip
()
else
:
cmd_str
=
self
.
generate_expr
(
inner
)
self
.
emit
(
f
'coproc {cp_name} {{ {cmd_str}; }}'
)
self
.
emit
(
f
'{cp_name}_wr=${{{cp_name}[1]}}'
)
self
.
emit
(
f
'{cp_name}_rd=${{{cp_name}[0]}}'
)
return
""
def
_generate_member_access
(
self
,
expr
:
MemberAccess
)
->
str
:
def
_generate_member_access
(
self
,
expr
:
MemberAccess
)
->
str
:
if
isinstance
(
expr
.
object
,
Identifier
)
and
expr
.
object
.
name
==
"env"
:
if
isinstance
(
expr
.
object
,
Identifier
)
and
expr
.
object
.
name
==
"env"
:
return
f
'${{{expr.member}}}'
return
f
'${{{expr.member}}}'
if
isinstance
(
expr
.
object
,
Identifier
):
if
isinstance
(
expr
.
object
,
Identifier
):
obj_name
=
expr
.
object
.
name
obj_name
=
expr
.
object
.
name
if
obj_name
in
getattr
(
self
,
'process_handle_vars'
,
set
())
and
expr
.
member
==
"pid"
:
cp
=
self
.
process_handle_map
[
obj_name
]
return
f
'${cp}_PID'
if
isinstance
(
expr
.
object
,
Identifier
):
obj_name
=
expr
.
object
.
name
param_map
=
getattr
(
self
,
'param_name_map'
,
{})
param_map
=
getattr
(
self
,
'param_name_map'
,
{})
mapped_name
=
param_map
.
get
(
obj_name
,
obj_name
)
mapped_name
=
param_map
.
get
(
obj_name
,
obj_name
)
if
mapped_name
in
getattr
(
self
,
'dict_vars'
,
set
()):
if
mapped_name
in
getattr
(
self
,
'dict_vars'
,
set
()):
...
...
bootstrap/methods/__init__.py
View file @
fc7a945f
...
@@ -13,6 +13,7 @@ from .time import TimeMethods
...
@@ -13,6 +13,7 @@ from .time import TimeMethods
from
.args
import
ArgsMethods
from
.args
import
ArgsMethods
from
.core
import
CoreFunctions
,
AwkBuiltinFunctions
from
.core
import
CoreFunctions
,
AwkBuiltinFunctions
from
.reflect
import
ReflectMethods
from
.reflect
import
ReflectMethods
from
.process_handle
import
ProcessHandleMethods
STRING_METHODS
=
collect_methods
(
StringMethods
)
STRING_METHODS
=
collect_methods
(
StringMethods
)
ARRAY_METHODS
=
collect_methods
(
ArrayMethods
)
ARRAY_METHODS
=
collect_methods
(
ArrayMethods
)
...
@@ -29,6 +30,7 @@ ARGS_METHODS = collect_methods(ArgsMethods)
...
@@ -29,6 +30,7 @@ ARGS_METHODS = collect_methods(ArgsMethods)
CORE_FUNCTIONS
=
collect_methods
(
CoreFunctions
)
CORE_FUNCTIONS
=
collect_methods
(
CoreFunctions
)
AWK_BUILTIN_FUNCTIONS
=
collect_methods
(
AwkBuiltinFunctions
)
AWK_BUILTIN_FUNCTIONS
=
collect_methods
(
AwkBuiltinFunctions
)
REFLECT_METHODS
=
collect_methods
(
ReflectMethods
)
REFLECT_METHODS
=
collect_methods
(
ReflectMethods
)
PROCESS_HANDLE_METHODS
=
collect_methods
(
ProcessHandleMethods
)
NAMESPACE_REGISTRY
=
{
NAMESPACE_REGISTRY
=
{
"fs"
:
FS_METHODS
,
"fs"
:
FS_METHODS
,
...
@@ -56,6 +58,7 @@ def get_method(type_name: str, method_name: str):
...
@@ -56,6 +58,7 @@ def get_method(type_name: str, method_name: str):
"array"
:
ARRAY_METHODS
,
"array"
:
ARRAY_METHODS
,
"dict"
:
DICT_METHODS
,
"dict"
:
DICT_METHODS
,
"file_handle"
:
FILE_HANDLE_METHODS
,
"file_handle"
:
FILE_HANDLE_METHODS
,
"process_handle"
:
PROCESS_HANDLE_METHODS
,
}
}
return
registry
.
get
(
type_name
,
{})
.
get
(
method_name
)
return
registry
.
get
(
type_name
,
{})
.
get
(
method_name
)
...
@@ -66,6 +69,7 @@ def get_method_names(type_name: str) -> set:
...
@@ -66,6 +69,7 @@ def get_method_names(type_name: str) -> set:
"array"
:
ARRAY_METHODS
,
"array"
:
ARRAY_METHODS
,
"dict"
:
DICT_METHODS
,
"dict"
:
DICT_METHODS
,
"file_handle"
:
FILE_HANDLE_METHODS
,
"file_handle"
:
FILE_HANDLE_METHODS
,
"process_handle"
:
PROCESS_HANDLE_METHODS
,
}
}
methods
=
registry
.
get
(
type_name
,
{})
methods
=
registry
.
get
(
type_name
,
{})
return
set
(
methods
.
keys
())
return
set
(
methods
.
keys
())
...
...
bootstrap/methods/core.py
View file @
fc7a945f
...
@@ -56,6 +56,11 @@ class CoreFunctions:
...
@@ -56,6 +56,11 @@ class CoreFunctions:
awk_builtin
=
lambda
a
:
f
"int({a[0]} + rand() * ({a[1]} - {a[0]} + 1))"
,
awk_builtin
=
lambda
a
:
f
"int({a[0]} + rand() * ({a[1]} - {a[0]} + 1))"
,
min_args
=
2
,
max_args
=
2
,
min_args
=
2
,
max_args
=
2
,
)
)
pid
=
Method
(
name
=
"pid"
,
bash_func
=
"__ct_pid"
,
bash_impl
=
'echo "$$"'
,
)
class
AwkBuiltinFunctions
:
class
AwkBuiltinFunctions
:
...
...
bootstrap/methods/process_handle.py
0 → 100644
View file @
fc7a945f
from
.base
import
Method
class
ProcessHandleMethods
:
write
=
Method
(
name
=
"write"
,
bash_func
=
"__ct_ph_write"
,
bash_impl
=
None
,
min_args
=
1
,
max_args
=
1
,
)
read
=
Method
(
name
=
"read"
,
bash_func
=
"__ct_ph_read"
,
bash_impl
=
None
,
)
close
=
Method
(
name
=
"close"
,
bash_func
=
"__ct_ph_close"
,
bash_impl
=
None
,
)
kill
=
Method
(
name
=
"kill"
,
bash_func
=
"__ct_ph_kill"
,
bash_impl
=
None
,
)
wait
=
Method
(
name
=
"wait"
,
bash_func
=
"__ct_ph_wait"
,
bash_impl
=
None
,
)
bootstrap/parser.py
View file @
fc7a945f
...
@@ -4,10 +4,10 @@ from .ast_nodes import (
...
@@ -4,10 +4,10 @@ from .ast_nodes import (
SourceLocation
,
Program
,
Declaration
,
Statement
,
Decorator
,
FunctionDecl
,
SourceLocation
,
Program
,
Declaration
,
Statement
,
Decorator
,
FunctionDecl
,
Parameter
,
ClassDecl
,
ClassField
,
ConstructorDecl
,
ImportStmt
,
Block
,
ReturnStmt
,
Parameter
,
ClassDecl
,
ClassField
,
ConstructorDecl
,
ImportStmt
,
Block
,
ReturnStmt
,
BreakStmt
,
ContinueStmt
,
IfStmt
,
WhileStmt
,
ForStmt
,
ForeachStmt
,
WithStmt
,
BreakStmt
,
ContinueStmt
,
IfStmt
,
WhileStmt
,
ForStmt
,
ForeachStmt
,
WithStmt
,
TryStmt
,
ThrowStmt
,
DeferStmt
,
WhenStmt
,
WhenBranch
,
RangePattern
,
TryStmt
,
ThrowStmt
,
DeferStmt
,
AwaitStmt
,
OnSignalStmt
,
WhenStmt
,
WhenBranch
,
RangePattern
,
ExpressionStmt
,
Assignment
,
IntegerLiteral
,
FloatLiteral
,
StringLiteral
,
ExpressionStmt
,
Assignment
,
IntegerLiteral
,
FloatLiteral
,
StringLiteral
,
BoolLiteral
,
NilLiteral
,
ThisExpr
,
ArrayLiteral
,
DictLiteral
,
Identifier
,
BoolLiteral
,
NilLiteral
,
ThisExpr
,
ArrayLiteral
,
DictLiteral
,
Identifier
,
BinaryOp
,
UnaryOp
,
CallExpr
,
MemberAccess
,
IndexAccess
,
NewExpr
,
Lambda
,
BinaryOp
,
UnaryOp
,
CallExpr
,
MemberAccess
,
IndexAccess
,
NewExpr
,
AsyncExpr
,
Lambda
,
BaseCall
,
Expression
,
TypeAnnotation
BaseCall
,
Expression
,
TypeAnnotation
)
)
from
.errors
import
CompileError
,
ErrorCollector
from
.errors
import
CompileError
,
ErrorCollector
...
@@ -53,6 +53,18 @@ class Parser:
...
@@ -53,6 +53,18 @@ class Parser:
self
.
error
(
msg
)
self
.
error
(
msg
)
return
self
.
current
()
return
self
.
current
()
KEYWORD_AS_IDENT
=
{
TokenType
.
ON
,
TokenType
.
ASYNC
,
TokenType
.
AWAIT
}
def
expect_name
(
self
,
message
:
str
=
"Expected identifier"
)
->
Token
:
if
self
.
check
(
TokenType
.
IDENTIFIER
):
return
self
.
advance
()
if
self
.
current
()
.
type
in
self
.
KEYWORD_AS_IDENT
:
tok
=
self
.
advance
()
tok
.
value
=
tok
.
value
or
tok
.
type
.
name
.
lower
()
return
tok
self
.
error
(
message
)
return
self
.
current
()
def
error
(
self
,
message
:
str
,
token
:
Token
=
None
):
def
error
(
self
,
message
:
str
,
token
:
Token
=
None
):
token
=
token
or
self
.
current
()
token
=
token
or
self
.
current
()
self
.
errors
.
add_error
(
self
.
errors
.
add_error
(
...
@@ -149,7 +161,7 @@ class Parser:
...
@@ -149,7 +161,7 @@ class Parser:
obj
=
None
obj
=
None
if
self
.
match
(
TokenType
.
DOT
):
if
self
.
match
(
TokenType
.
DOT
):
obj
=
name
obj
=
name
name
=
self
.
expect
(
TokenType
.
IDENTIFIER
,
"Expected method name after '.'"
)
.
value
name
=
self
.
expect
_name
(
"Expected method name after '.'"
)
.
value
arguments
=
[]
arguments
=
[]
if
self
.
match
(
TokenType
.
LPAREN
):
if
self
.
match
(
TokenType
.
LPAREN
):
...
@@ -188,7 +200,7 @@ class Parser:
...
@@ -188,7 +200,7 @@ class Parser:
def
parse_function
(
self
,
decorators
:
List
[
Decorator
]
=
None
)
->
FunctionDecl
:
def
parse_function
(
self
,
decorators
:
List
[
Decorator
]
=
None
)
->
FunctionDecl
:
loc
=
self
.
location
()
loc
=
self
.
location
()
self
.
expect
(
TokenType
.
FUNC
)
self
.
expect
(
TokenType
.
FUNC
)
name
=
self
.
expect
(
TokenType
.
IDENTIFIER
,
"Expected function name"
)
.
value
name
=
self
.
expect
_name
(
"Expected function name"
)
.
value
self
.
expect
(
TokenType
.
LPAREN
,
"Expected '(' after function name"
)
self
.
expect
(
TokenType
.
LPAREN
,
"Expected '(' after function name"
)
params
=
self
.
parse_parameters
()
params
=
self
.
parse_parameters
()
...
@@ -333,6 +345,10 @@ class Parser:
...
@@ -333,6 +345,10 @@ class Parser:
return
self
.
parse_throw
()
return
self
.
parse_throw
()
if
self
.
check
(
TokenType
.
DEFER
):
if
self
.
check
(
TokenType
.
DEFER
):
return
self
.
parse_defer
()
return
self
.
parse_defer
()
if
self
.
check
(
TokenType
.
AWAIT
):
return
self
.
parse_await
()
if
self
.
check
(
TokenType
.
ON
):
return
self
.
parse_on_signal
()
if
self
.
check
(
TokenType
.
WHEN
):
if
self
.
check
(
TokenType
.
WHEN
):
return
self
.
parse_when
()
return
self
.
parse_when
()
if
self
.
check
(
TokenType
.
LBRACE
):
if
self
.
check
(
TokenType
.
LBRACE
):
...
@@ -528,6 +544,20 @@ class Parser:
...
@@ -528,6 +544,20 @@ class Parser:
expression
=
self
.
parse_expression
()
expression
=
self
.
parse_expression
()
return
DeferStmt
(
expression
=
expression
,
location
=
loc
)
return
DeferStmt
(
expression
=
expression
,
location
=
loc
)
def
parse_await
(
self
)
->
AwaitStmt
:
loc
=
self
.
location
()
self
.
expect
(
TokenType
.
AWAIT
)
expr
=
self
.
parse_expression
()
return
AwaitStmt
(
expression
=
expr
,
location
=
loc
)
def
parse_on_signal
(
self
)
->
OnSignalStmt
:
loc
=
self
.
location
()
self
.
expect
(
TokenType
.
ON
)
signal
=
self
.
expect
(
TokenType
.
IDENTIFIER
,
"Expected signal name"
)
.
value
self
.
skip_newlines
()
body
=
self
.
parse_block
()
return
OnSignalStmt
(
signal
=
signal
,
body
=
body
,
location
=
loc
)
def
parse_when
(
self
)
->
WhenStmt
:
def
parse_when
(
self
)
->
WhenStmt
:
loc
=
self
.
location
()
loc
=
self
.
location
()
self
.
expect
(
TokenType
.
WHEN
)
self
.
expect
(
TokenType
.
WHEN
)
...
@@ -635,6 +665,12 @@ class Parser:
...
@@ -635,6 +665,12 @@ class Parser:
operand
=
self
.
parse_unary
()
operand
=
self
.
parse_unary
()
return
UnaryOp
(
operator
=
operator
.
value
,
operand
=
operand
,
location
=
loc
)
return
UnaryOp
(
operator
=
operator
.
value
,
operand
=
operand
,
location
=
loc
)
if
self
.
check
(
TokenType
.
ASYNC
):
loc
=
self
.
location
()
self
.
advance
()
expr
=
self
.
parse_unary
()
return
AsyncExpr
(
expression
=
expr
,
location
=
loc
)
if
self
.
check
(
TokenType
.
NEW
):
if
self
.
check
(
TokenType
.
NEW
):
loc
=
self
.
location
()
loc
=
self
.
location
()
self
.
advance
()
# consume 'new'
self
.
advance
()
# consume 'new'
...
@@ -660,7 +696,7 @@ class Parser:
...
@@ -660,7 +696,7 @@ class Parser:
expr
=
CallExpr
(
callee
=
expr
,
arguments
=
args
,
location
=
expr
.
location
)
expr
=
CallExpr
(
callee
=
expr
,
arguments
=
args
,
location
=
expr
.
location
)
elif
self
.
match
(
TokenType
.
DOT
):
elif
self
.
match
(
TokenType
.
DOT
):
member
=
self
.
expect
(
TokenType
.
IDENTIFIER
,
"Expected member name"
)
.
value
member
=
self
.
expect
_name
(
"Expected member name"
)
.
value
expr
=
MemberAccess
(
object
=
expr
,
member
=
member
,
location
=
expr
.
location
)
expr
=
MemberAccess
(
object
=
expr
,
member
=
member
,
location
=
expr
.
location
)
elif
self
.
match
(
TokenType
.
LBRACKET
):
elif
self
.
match
(
TokenType
.
LBRACKET
):
...
...
bootstrap/stdlib.py
View file @
fc7a945f
...
@@ -98,6 +98,8 @@ class StdlibMixin:
...
@@ -98,6 +98,8 @@ class StdlibMixin:
self
.
emit
(
f
"{CORE_FUNCTIONS['len'].bash_func} () {{ {CORE_FUNCTIONS['len'].bash_impl}; }}"
)
self
.
emit
(
f
"{CORE_FUNCTIONS['len'].bash_func} () {{ {CORE_FUNCTIONS['len'].bash_impl}; }}"
)
self
.
emit
()
self
.
emit
()
self
.
emit
(
f
"{CORE_FUNCTIONS['pid'].bash_func} () {{ {CORE_FUNCTIONS['pid'].bash_impl}; }}"
)
self
.
emit
()
def
_emit_http
(
self
):
def
_emit_http
(
self
):
"""HTTP functions from HTTP_METHODS."""
"""HTTP functions from HTTP_METHODS."""
...
...
bootstrap/stmt_codegen.py
View file @
fc7a945f
from
.ast_nodes
import
(
from
.ast_nodes
import
(
FunctionDecl
,
ClassDecl
,
ImportStmt
,
Assignment
,
ExpressionStmt
,
IfStmt
,
FunctionDecl
,
ClassDecl
,
ImportStmt
,
Assignment
,
ExpressionStmt
,
IfStmt
,
WhileStmt
,
ForStmt
,
ForeachStmt
,
WithStmt
,
TryStmt
,
ThrowStmt
,
DeferStmt
,
WhileStmt
,
ForStmt
,
ForeachStmt
,
WithStmt
,
TryStmt
,
ThrowStmt
,
DeferStmt
,
AwaitStmt
,
OnSignalStmt
,
AsyncExpr
,
WhenStmt
,
RangePattern
,
ReturnStmt
,
BreakStmt
,
ContinueStmt
,
Block
,
WhenStmt
,
RangePattern
,
ReturnStmt
,
BreakStmt
,
ContinueStmt
,
Block
,
CallExpr
,
Identifier
,
MemberAccess
,
ThisExpr
,
StringLiteral
,
NewExpr
,
CallExpr
,
Identifier
,
MemberAccess
,
ThisExpr
,
StringLiteral
,
NewExpr
,
BinaryOp
,
DictLiteral
,
ArrayLiteral
,
WhenBranch
BinaryOp
,
DictLiteral
,
ArrayLiteral
,
WhenBranch
)
)
from
.constants
import
RET_VAR
,
RET_ARR
from
.constants
import
RET_VAR
,
RET_ARR
,
COPROC_PREFIX
class
StmtMixin
:
class
StmtMixin
:
...
@@ -41,6 +42,10 @@ class StmtMixin:
...
@@ -41,6 +42,10 @@ class StmtMixin:
self
.
generate_throw
(
stmt
)
self
.
generate_throw
(
stmt
)
elif
isinstance
(
stmt
,
DeferStmt
):
elif
isinstance
(
stmt
,
DeferStmt
):
self
.
generate_defer
(
stmt
)
self
.
generate_defer
(
stmt
)
elif
isinstance
(
stmt
,
AwaitStmt
):
self
.
generate_await
(
stmt
)
elif
isinstance
(
stmt
,
OnSignalStmt
):
self
.
generate_on_signal
(
stmt
)
elif
isinstance
(
stmt
,
WhenStmt
):
elif
isinstance
(
stmt
,
WhenStmt
):
self
.
generate_when
(
stmt
)
self
.
generate_when
(
stmt
)
elif
isinstance
(
stmt
,
ReturnStmt
):
elif
isinstance
(
stmt
,
ReturnStmt
):
...
@@ -318,8 +323,29 @@ class StmtMixin:
...
@@ -318,8 +323,29 @@ class StmtMixin:
self
.
emit
(
"# with statement"
)
self
.
emit
(
"# with statement"
)
local_kw
=
"local "
if
self
.
in_function
else
""
local_kw
=
"local "
if
self
.
in_function
else
""
with_async
=
{}
for
i
,
(
var
,
resource
)
in
enumerate
(
zip
(
stmt
.
variables
,
stmt
.
resources
)):
for
i
,
(
var
,
resource
)
in
enumerate
(
zip
(
stmt
.
variables
,
stmt
.
resources
)):
if
isinstance
(
resource
,
AsyncExpr
):
self
.
coproc_counter
+=
1
cp_name
=
f
'{COPROC_PREFIX}{self.coproc_counter}'
inner
=
resource
.
expression
if
isinstance
(
inner
,
CallExpr
)
and
self
.
_is_shell_command
(
inner
):
cmd_str
=
self
.
_extract_shell_command_str
(
inner
)
elif
isinstance
(
inner
,
CallExpr
)
and
isinstance
(
inner
.
callee
,
Identifier
):
func_name
=
inner
.
callee
.
name
args_str
=
self
.
_generate_call_args_str
(
inner
.
arguments
)
cmd_str
=
f
'exec 3>&1; {func_name} {args_str}'
.
strip
()
else
:
cmd_str
=
self
.
generate_expr
(
inner
)
self
.
emit
(
f
'coproc {cp_name} {{ {cmd_str}; }}'
)
self
.
emit
(
f
'{cp_name}_wr=${{{cp_name}[1]}}'
)
self
.
emit
(
f
'{cp_name}_rd=${{{cp_name}[0]}}'
)
self
.
process_handle_vars
.
add
(
var
)
self
.
process_handle_map
[
var
]
=
cp_name
with_async
[
i
]
=
cp_name
continue
if
isinstance
(
resource
,
CallExpr
)
and
isinstance
(
resource
.
callee
,
MemberAccess
):
if
isinstance
(
resource
,
CallExpr
)
and
isinstance
(
resource
.
callee
,
MemberAccess
):
if
resource
.
callee
.
member
==
"open"
:
if
resource
.
callee
.
member
==
"open"
:
self
.
file_handle_vars
.
add
(
var
)
self
.
file_handle_vars
.
add
(
var
)
...
@@ -339,6 +365,11 @@ class StmtMixin:
...
@@ -339,6 +365,11 @@ class StmtMixin:
self
.
emit
(
"# with cleanup"
)
self
.
emit
(
"# with cleanup"
)
for
i
in
range
(
len
(
stmt
.
variables
)
-
1
,
-
1
,
-
1
):
for
i
in
range
(
len
(
stmt
.
variables
)
-
1
,
-
1
,
-
1
):
if
i
in
with_async
:
cp
=
with_async
[
i
]
self
.
emit
(
f
'exec {{{cp}_wr}}>&- 2>/dev/null || true'
)
self
.
emit
(
f
'wait ${cp}_PID 2>/dev/null || true'
)
else
:
self
.
emit
(
f
'__ct_fh___exit__ "$__ct_with_{i}"'
)
self
.
emit
(
f
'__ct_fh___exit__ "$__ct_with_{i}"'
)
def
generate_try
(
self
,
stmt
:
TryStmt
):
def
generate_try
(
self
,
stmt
:
TryStmt
):
...
@@ -414,6 +445,30 @@ class StmtMixin:
...
@@ -414,6 +445,30 @@ class StmtMixin:
expr
=
self
.
generate_expr
(
stmt
.
expression
)
expr
=
self
.
generate_expr
(
stmt
.
expression
)
self
.
deferred_calls
.
append
(
expr
)
self
.
deferred_calls
.
append
(
expr
)
def
generate_await
(
self
,
stmt
:
AwaitStmt
):
if
isinstance
(
stmt
.
expression
,
Identifier
):
var
=
stmt
.
expression
.
name
if
var
in
self
.
process_handle_vars
:
cp
=
self
.
process_handle_map
[
var
]
self
.
emit
(
f
'wait ${cp}_PID 2>/dev/null || true'
)
return
expr
=
self
.
generate_expr
(
stmt
.
expression
)
self
.
emit
(
f
'wait "{expr}" 2>/dev/null || true'
)
def
generate_on_signal
(
self
,
stmt
:
OnSignalStmt
):
signal_map
=
{
"SIGINT"
:
"INT"
,
"SIGTERM"
:
"TERM"
,
"SIGHUP"
:
"HUP"
,
"SIGUSR1"
:
"USR1"
,
"SIGUSR2"
:
"USR2"
,
"EXIT"
:
"EXIT"
,
}
sig
=
signal_map
.
get
(
stmt
.
signal
,
stmt
.
signal
)
handler
=
f
'__ct_on_{sig.lower()}'
self
.
emit
(
f
'{handler} () {{'
)
with
self
.
indented
():
for
s
in
stmt
.
body
.
statements
:
self
.
generate_statement
(
s
)
self
.
emit
(
'}'
)
self
.
emit
(
f
'trap {handler} {sig}'
)
def
generate_return
(
self
,
stmt
:
ReturnStmt
):
def
generate_return
(
self
,
stmt
:
ReturnStmt
):
if
self
.
deferred_calls
:
if
self
.
deferred_calls
:
self
.
emit
(
"# Deferred calls before return"
)
self
.
emit
(
"# Deferred calls before return"
)
...
...
bootstrap/tokens.py
View file @
fc7a945f
...
@@ -37,6 +37,9 @@ class TokenType (Enum):
...
@@ -37,6 +37,9 @@ class TokenType (Enum):
WHEN
=
auto
()
WHEN
=
auto
()
WITH
=
auto
()
WITH
=
auto
()
NEW
=
auto
()
NEW
=
auto
()
ASYNC
=
auto
()
AWAIT
=
auto
()
ON
=
auto
()
PLUS
=
auto
()
PLUS
=
auto
()
MINUS
=
auto
()
MINUS
=
auto
()
...
@@ -103,6 +106,9 @@ KEYWORDS = {
...
@@ -103,6 +106,9 @@ KEYWORDS = {
'when'
:
TokenType
.
WHEN
,
'when'
:
TokenType
.
WHEN
,
'with'
:
TokenType
.
WITH
,
'with'
:
TokenType
.
WITH
,
'new'
:
TokenType
.
NEW
,
'new'
:
TokenType
.
NEW
,
'async'
:
TokenType
.
ASYNC
,
'await'
:
TokenType
.
AWAIT
,
'on'
:
TokenType
.
ON
,
'true'
:
TokenType
.
TRUE
,
'true'
:
TokenType
.
TRUE
,
'false'
:
TokenType
.
FALSE
,
'false'
:
TokenType
.
FALSE
,
'nil'
:
TokenType
.
NIL
,
'nil'
:
TokenType
.
NIL
,
...
...
tests/test_async.py
0 → 100644
View file @
fc7a945f
from
helpers
import
run_ct
,
compile_ct
class
TestAsyncBasic
:
def
test_async_cat_write_close_await
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
proc = async cat ()
proc.write ("hello from coproc")
proc.close ()
await proc
print ("done")
'''
)
assert
code
==
0
assert
"done"
in
stdout
def
test_async_pid
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
proc = async sleep ("10")
mypid = proc.pid
print ("pid:{mypid}")
proc.kill ()
await proc
'''
)
assert
code
==
0
assert
"pid:"
in
stdout
pid_val
=
stdout
.
strip
()
.
split
(
"pid:"
)[
1
]
assert
pid_val
.
isdigit
()
def
test_async_pid_interpolation
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
proc = async sleep ("10")
print ("PID={proc.pid}")
proc.kill ()
await proc
'''
)
assert
code
==
0
assert
"PID="
in
stdout
pid_val
=
stdout
.
strip
()
.
split
(
"PID="
)[
1
]
assert
pid_val
.
isdigit
()
def
test_async_kill
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
proc = async sleep ("60")
proc.kill ()
await proc
print ("killed")
'''
)
assert
code
==
0
assert
"killed"
in
stdout
def
test_async_read
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
proc = async echo ("hello output")
line = proc.read ()
print ("got:{line}")
await proc
'''
)
assert
code
==
0
assert
"got:hello output"
in
stdout
class
TestAwaitStatement
:
def
test_await_process
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
proc = async echo ("test")
await proc
print ("waited")
'''
)
assert
code
==
0
assert
"waited"
in
stdout
class
TestOnSignal
:
def
test_on_exit
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
on EXIT {
print ("exiting")
}
print ("running")
'''
)
assert
code
==
0
assert
"running"
in
stdout
assert
"exiting"
in
stdout
def
test_on_signal_compile
(
self
):
code
,
output
,
_
=
compile_ct
(
'''
on SIGTERM {
print ("terminated")
exit (1)
}
print ("ok")
'''
)
assert
code
==
0
assert
"trap __ct_on_term TERM"
in
output
assert
"__ct_on_term ()"
in
output
class
TestPidBuiltin
:
def
test_pid_returns_number
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
mypid = pid ()
print ("{mypid}")
'''
)
assert
code
==
0
assert
stdout
.
strip
()
.
isdigit
()
def
test_pid_in_interpolation
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
print ("PID={pid ()}")
'''
)
assert
code
==
0
pid_val
=
stdout
.
strip
()
.
split
(
"PID="
)[
1
]
assert
pid_val
.
isdigit
()
class
TestCompileAsync
:
def
test_compile_coproc
(
self
):
code
,
output
,
_
=
compile_ct
(
'''
proc = async cat ()
proc.write ("data")
proc.close ()
await proc
'''
)
assert
code
==
0
assert
"coproc __ct_cp1"
in
output
assert
"__ct_cp1_wr=${__ct_cp1[1]}"
in
output
assert
"__ct_cp1_rd=${__ct_cp1[0]}"
in
output
assert
'echo "data" >&$__ct_cp1_wr'
in
output
assert
"exec {__ct_cp1_wr}>&-"
in
output
assert
"wait $__ct_cp1_PID"
in
output
def
test_compile_on_signal
(
self
):
code
,
output
,
_
=
compile_ct
(
'''
on SIGINT {
print ("interrupted")
}
'''
)
assert
code
==
0
assert
"__ct_on_int () {"
in
output
assert
"trap __ct_on_int INT"
in
output
def
test_compile_pid
(
self
):
code
,
output
,
_
=
compile_ct
(
'''
x = pid ()
print ("{x}")
'''
)
assert
code
==
0
assert
"__ct_pid"
in
output
def
test_compile_multiple_coprocs
(
self
):
code
,
output
,
_
=
compile_ct
(
'''
p1 = async cat ()
p2 = async cat ()
p1.close ()
p2.close ()
await p1
await p2
'''
)
assert
code
==
0
assert
"coproc __ct_cp1"
in
output
assert
"coproc __ct_cp2"
in
output
assert
"wait $__ct_cp1_PID"
in
output
assert
"wait $__ct_cp2_PID"
in
output
class
TestKeywordAsIdentifier
:
def
test_on_as_method_name
(
self
):
code
,
stdout
,
_
=
run_ct
(
'''
class Bus {
func on (event) {
print ("event:{event}")
}
}
b = new Bus ()
b.on ("click")
'''
)
assert
code
==
0
assert
"event:click"
in
stdout
def
test_on_as_decorator_method
(
self
):
code
,
output
,
_
=
compile_ct
(
'''
class EventBus {
listeners = {}
func on (event, handler) {
this.listeners.set (event, handler)
}
func emit (event, data) {
if this.listeners.has (event) {
handler = this.listeners.get (event)
handler (data)
}
}
}
bus = new EventBus ()
@bus.on ("click")
func on_click (data) {
print ("Clicked: {data}")
}
bus.emit ("click", "button1")
'''
)
assert
code
==
0
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment