snail-lang 0.7.0__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snail_lang-0.7.0 → snail_lang-0.7.2}/Cargo.lock +5 -5
- {snail_lang-0.7.0 → snail_lang-0.7.2}/Cargo.toml +6 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/PKG-INFO +19 -7
- {snail_lang-0.7.0 → snail_lang-0.7.2}/README.md +16 -5
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-ast/Cargo.toml +1 -1
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-error/Cargo.toml +1 -1
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/Cargo.toml +1 -1
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/snail.pest +2 -2
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/tests/syntax_expressions.rs +9 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/Cargo.toml +2 -2
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/constants.rs +3 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/expr.rs +57 -68
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/py_ast.rs +28 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/pyproject.toml +3 -2
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/__init__.py +2 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/cli.py +53 -5
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/__init__.py +13 -1
- snail_lang-0.7.2/python/snail/runtime/env.py +26 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/LICENSE +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-ast/README.md +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-ast/src/ast.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-ast/src/awk.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-ast/src/lib.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-error/README.md +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-error/src/lib.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/README.md +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/awk.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/expr.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/lib.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/literal.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/stmt.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/string.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/src/util.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/tests/common.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/tests/errors.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/tests/parser.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/tests/statements.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-parser/tests/syntax_strings.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/build.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/compiler.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lib.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/linecache.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/awk.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/desugar.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/helpers.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/map.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/mod.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/operators.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/program.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/stmt.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/lower/validate.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/crates/snail-python/src/profiling.rs +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/augmented.py +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/compact_try.py +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/lazy_file.py +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/lazy_text.py +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/regex.py +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/structured_accessor.py +0 -0
- {snail_lang-0.7.0 → snail_lang-0.7.2}/python/snail/runtime/subprocess.py +0 -0
|
@@ -312,25 +312,25 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
|
312
312
|
|
|
313
313
|
[[package]]
|
|
314
314
|
name = "snail-ast"
|
|
315
|
-
version = "0.7.
|
|
315
|
+
version = "0.7.2"
|
|
316
316
|
|
|
317
317
|
[[package]]
|
|
318
318
|
name = "snail-error"
|
|
319
|
-
version = "0.7.
|
|
319
|
+
version = "0.7.2"
|
|
320
320
|
dependencies = [
|
|
321
321
|
"snail-ast",
|
|
322
322
|
]
|
|
323
323
|
|
|
324
324
|
[[package]]
|
|
325
325
|
name = "snail-lower"
|
|
326
|
-
version = "0.7.
|
|
326
|
+
version = "0.7.2"
|
|
327
327
|
dependencies = [
|
|
328
328
|
"snail-python",
|
|
329
329
|
]
|
|
330
330
|
|
|
331
331
|
[[package]]
|
|
332
332
|
name = "snail-parser"
|
|
333
|
-
version = "0.7.
|
|
333
|
+
version = "0.7.2"
|
|
334
334
|
dependencies = [
|
|
335
335
|
"pest",
|
|
336
336
|
"pest_derive",
|
|
@@ -340,7 +340,7 @@ dependencies = [
|
|
|
340
340
|
|
|
341
341
|
[[package]]
|
|
342
342
|
name = "snail-python"
|
|
343
|
-
version = "0.7.
|
|
343
|
+
version = "0.7.2"
|
|
344
344
|
dependencies = [
|
|
345
345
|
"pyo3",
|
|
346
346
|
"snail-ast",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snail-lang
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Requires-Dist: astunparse>=1.6.3 ; python_full_version < '3.9'
|
|
4
5
|
Requires-Dist: jmespath>=1.0.1
|
|
5
6
|
Requires-Dist: maturin>=1.5 ; extra == 'dev'
|
|
6
7
|
Requires-Dist: pytest ; extra == 'dev'
|
|
7
8
|
Provides-Extra: dev
|
|
8
9
|
License-File: LICENSE
|
|
9
10
|
Summary: Snail programming language interpreter
|
|
10
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.8
|
|
11
12
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
12
13
|
|
|
13
14
|
<p align="center">
|
|
@@ -26,10 +27,10 @@ way into something good, but its certainly not there yet.
|
|
|
26
27
|
|
|
27
28
|
## Installing Snail
|
|
28
29
|
|
|
29
|
-
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
|
|
30
|
-
|
|
31
30
|
```bash
|
|
32
|
-
|
|
31
|
+
pip install snail-lang
|
|
32
|
+
-or-
|
|
33
|
+
uv tool install snail-lang
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
|
|
@@ -75,6 +76,7 @@ END { print("done") }
|
|
|
75
76
|
| `$p` | Current file path |
|
|
76
77
|
| `$m` | Last regex match object |
|
|
77
78
|
|
|
79
|
+
|
|
78
80
|
Begin/end blocks can live in the source file (`BEGIN { ... }` / `END { ... }`) or be supplied
|
|
79
81
|
via CLI flags (`-b`/`--begin`, `-e`/`--end`) for setup and teardown. CLI BEGIN blocks run
|
|
80
82
|
before in-file BEGIN blocks; CLI END blocks run after in-file END blocks.
|
|
@@ -112,6 +114,13 @@ BEGIN/END blocks are regular Snail blocks, so awk/map-only `$` variables are not
|
|
|
112
114
|
snail --map --begin "print('start')" --end "print('done')" "print($src)" *.txt
|
|
113
115
|
```
|
|
114
116
|
|
|
117
|
+
### Built-in Variables (All Modes)
|
|
118
|
+
|
|
119
|
+
| Variable | Description |
|
|
120
|
+
|----------|-------------|
|
|
121
|
+
| `$e` | Exception object in `expr:fallback?` |
|
|
122
|
+
| `$env` | Environment map (wrapper around `os.environ`) |
|
|
123
|
+
|
|
115
124
|
### Compact Error Handling
|
|
116
125
|
|
|
117
126
|
The `?` operator makes error handling terse yet expressive:
|
|
@@ -243,7 +252,7 @@ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
|
|
|
243
252
|
### Full Python Interoperability
|
|
244
253
|
|
|
245
254
|
Snail compiles to Python AST—import any Python module, use any library, in any
|
|
246
|
-
environment. Assuming that you are using Python 3.
|
|
255
|
+
environment. Assuming that you are using Python 3.8 or later.
|
|
247
256
|
|
|
248
257
|
## 🚀 Quick Start
|
|
249
258
|
|
|
@@ -262,6 +271,9 @@ snail 'if let [_, user, domain] = "user@example.com" in /^[\w.]+@([\w.]+)$/ { pr
|
|
|
262
271
|
|
|
263
272
|
# Awk mode: print line numbers for matches
|
|
264
273
|
rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$0}") }'
|
|
274
|
+
|
|
275
|
+
# Environment variables
|
|
276
|
+
snail 'print($env.PATH)'
|
|
265
277
|
```
|
|
266
278
|
|
|
267
279
|
## 📚 Documentation
|
|
@@ -294,7 +306,7 @@ machine snail adds 5 ms of overhead above the regular python3 interpreter.
|
|
|
294
306
|
|
|
295
307
|
### Prerequisites
|
|
296
308
|
|
|
297
|
-
**Python 3.
|
|
309
|
+
**Python 3.8+** (required at runtime)
|
|
298
310
|
|
|
299
311
|
Snail runs in-process via a Pyo3 extension module, so it uses the active Python environment.
|
|
300
312
|
|
|
@@ -14,10 +14,10 @@ way into something good, but its certainly not there yet.
|
|
|
14
14
|
|
|
15
15
|
## Installing Snail
|
|
16
16
|
|
|
17
|
-
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
|
|
18
|
-
|
|
19
17
|
```bash
|
|
20
|
-
|
|
18
|
+
pip install snail-lang
|
|
19
|
+
-or-
|
|
20
|
+
uv tool install snail-lang
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
|
|
@@ -63,6 +63,7 @@ END { print("done") }
|
|
|
63
63
|
| `$p` | Current file path |
|
|
64
64
|
| `$m` | Last regex match object |
|
|
65
65
|
|
|
66
|
+
|
|
66
67
|
Begin/end blocks can live in the source file (`BEGIN { ... }` / `END { ... }`) or be supplied
|
|
67
68
|
via CLI flags (`-b`/`--begin`, `-e`/`--end`) for setup and teardown. CLI BEGIN blocks run
|
|
68
69
|
before in-file BEGIN blocks; CLI END blocks run after in-file END blocks.
|
|
@@ -100,6 +101,13 @@ BEGIN/END blocks are regular Snail blocks, so awk/map-only `$` variables are not
|
|
|
100
101
|
snail --map --begin "print('start')" --end "print('done')" "print($src)" *.txt
|
|
101
102
|
```
|
|
102
103
|
|
|
104
|
+
### Built-in Variables (All Modes)
|
|
105
|
+
|
|
106
|
+
| Variable | Description |
|
|
107
|
+
|----------|-------------|
|
|
108
|
+
| `$e` | Exception object in `expr:fallback?` |
|
|
109
|
+
| `$env` | Environment map (wrapper around `os.environ`) |
|
|
110
|
+
|
|
103
111
|
### Compact Error Handling
|
|
104
112
|
|
|
105
113
|
The `?` operator makes error handling terse yet expressive:
|
|
@@ -231,7 +239,7 @@ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
|
|
|
231
239
|
### Full Python Interoperability
|
|
232
240
|
|
|
233
241
|
Snail compiles to Python AST—import any Python module, use any library, in any
|
|
234
|
-
environment. Assuming that you are using Python 3.
|
|
242
|
+
environment. Assuming that you are using Python 3.8 or later.
|
|
235
243
|
|
|
236
244
|
## 🚀 Quick Start
|
|
237
245
|
|
|
@@ -250,6 +258,9 @@ snail 'if let [_, user, domain] = "user@example.com" in /^[\w.]+@([\w.]+)$/ { pr
|
|
|
250
258
|
|
|
251
259
|
# Awk mode: print line numbers for matches
|
|
252
260
|
rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$0}") }'
|
|
261
|
+
|
|
262
|
+
# Environment variables
|
|
263
|
+
snail 'print($env.PATH)'
|
|
253
264
|
```
|
|
254
265
|
|
|
255
266
|
## 📚 Documentation
|
|
@@ -282,7 +293,7 @@ machine snail adds 5 ms of overhead above the regular python3 interpreter.
|
|
|
282
293
|
|
|
283
294
|
### Prerequisites
|
|
284
295
|
|
|
285
|
-
**Python 3.
|
|
296
|
+
**Python 3.8+** (required at runtime)
|
|
286
297
|
|
|
287
298
|
Snail runs in-process via a Pyo3 extension module, so it uses the active Python environment.
|
|
288
299
|
|
|
@@ -250,9 +250,9 @@ boolean = { "True" | "False" }
|
|
|
250
250
|
none = { "None" }
|
|
251
251
|
|
|
252
252
|
// Special variables: exception, AWK fields, map vars, injected vars
|
|
253
|
-
exception_var = { "$e" }
|
|
253
|
+
exception_var = { "$e" ~ !ident_continue }
|
|
254
254
|
field_index_var = @{ "$" ~ ASCII_DIGIT+ }
|
|
255
|
-
injected_var = { "$text" | "$src" | "$fn" | "$fd" | "$n" | "$p" | "$m" | "$f" }
|
|
255
|
+
injected_var = { "$text" | "$src" | "$env" | "$fn" | "$fd" | "$n" | "$p" | "$m" | "$f" }
|
|
256
256
|
|
|
257
257
|
// Number, string, and regex literals
|
|
258
258
|
number = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
|
|
@@ -293,6 +293,15 @@ fn parses_structured_accessor_with_pipeline() {
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
#[test]
|
|
297
|
+
fn parses_env_var() {
|
|
298
|
+
let program = parse_ok("value = $env");
|
|
299
|
+
assert_eq!(program.stmts.len(), 1);
|
|
300
|
+
|
|
301
|
+
let (_, value) = expect_assign(&program.stmts[0]);
|
|
302
|
+
expect_name(value, "$env");
|
|
303
|
+
}
|
|
304
|
+
|
|
296
305
|
#[test]
|
|
297
306
|
fn parses_empty_structured_accessor() {
|
|
298
307
|
let program = parse_ok("result = $[]");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "snail-python"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.2"
|
|
4
4
|
edition.workspace = true
|
|
5
5
|
build = "build.rs"
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ name = "snail_python"
|
|
|
9
9
|
crate-type = ["cdylib", "rlib"]
|
|
10
10
|
|
|
11
11
|
[dependencies]
|
|
12
|
-
pyo3 = { version = "0.21", features = ["extension-module", "abi3-
|
|
12
|
+
pyo3 = { version = "0.21", features = ["extension-module", "abi3-py38"] }
|
|
13
13
|
snail-ast = { path = "../snail-ast" }
|
|
14
14
|
snail-error = { path = "../snail-error" }
|
|
15
15
|
snail-parser = { path = "../snail-parser" }
|
|
@@ -37,9 +37,11 @@ pub(crate) const SNAIL_AWK_MATCH_PYVAR: &str = "__snail_match";
|
|
|
37
37
|
pub(crate) const SNAIL_MAP_SRC: &str = "$src";
|
|
38
38
|
pub(crate) const SNAIL_MAP_FD: &str = "$fd";
|
|
39
39
|
pub(crate) const SNAIL_MAP_TEXT: &str = "$text";
|
|
40
|
+
pub(crate) const SNAIL_ENV: &str = "$env";
|
|
40
41
|
pub(crate) const SNAIL_MAP_SRC_PYVAR: &str = "__snail_src";
|
|
41
42
|
pub(crate) const SNAIL_MAP_FD_PYVAR: &str = "__snail_fd";
|
|
42
43
|
pub(crate) const SNAIL_MAP_TEXT_PYVAR: &str = "__snail_text";
|
|
44
|
+
pub(crate) const SNAIL_ENV_PYVAR: &str = "__snail_env";
|
|
43
45
|
pub const SNAIL_LAZY_TEXT_CLASS: &str = "__SnailLazyText";
|
|
44
46
|
pub const SNAIL_LAZY_FILE_CLASS: &str = "__SnailLazyFile";
|
|
45
47
|
|
|
@@ -55,6 +57,7 @@ pub(crate) fn injected_py_name(name: &str) -> Option<&'static str> {
|
|
|
55
57
|
SNAIL_MAP_SRC => Some(SNAIL_MAP_SRC_PYVAR),
|
|
56
58
|
SNAIL_MAP_FD => Some(SNAIL_MAP_FD_PYVAR),
|
|
57
59
|
SNAIL_MAP_TEXT => Some(SNAIL_MAP_TEXT_PYVAR),
|
|
60
|
+
SNAIL_ENV => Some(SNAIL_ENV_PYVAR),
|
|
58
61
|
_ => None,
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -36,9 +36,7 @@ pub(crate) fn lower_assign_target(
|
|
|
36
36
|
AssignTarget::Index { value, index, span } => {
|
|
37
37
|
let value_expr = lower_expr_with_exception(builder, value, None)?;
|
|
38
38
|
let index_expr = lower_expr_with_exception(builder, index, None)?;
|
|
39
|
-
builder
|
|
40
|
-
.call_node("Subscript", vec![value_expr, index_expr, store_ctx], span)
|
|
41
|
-
.map_err(py_err_to_lower)
|
|
39
|
+
subscript_expr(builder, value_expr, index_expr, store_ctx, span)
|
|
42
40
|
}
|
|
43
41
|
AssignTarget::Starred { target, span } => {
|
|
44
42
|
let value = lower_assign_target(builder, target)?;
|
|
@@ -101,9 +99,7 @@ pub(crate) fn lower_delete_target(
|
|
|
101
99
|
AssignTarget::Index { value, index, span } => {
|
|
102
100
|
let value_expr = lower_expr_with_exception(builder, value, None)?;
|
|
103
101
|
let index_expr = lower_expr_with_exception(builder, index, None)?;
|
|
104
|
-
builder
|
|
105
|
-
.call_node("Subscript", vec![value_expr, index_expr, del_ctx], span)
|
|
106
|
-
.map_err(py_err_to_lower)
|
|
102
|
+
subscript_expr(builder, value_expr, index_expr, del_ctx, span)
|
|
107
103
|
}
|
|
108
104
|
AssignTarget::Starred { .. } => Err(LowerError::new(
|
|
109
105
|
"starred targets are not valid in del statements",
|
|
@@ -143,6 +139,19 @@ pub(crate) fn lower_delete_target(
|
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
141
|
|
|
142
|
+
fn subscript_expr(
|
|
143
|
+
builder: &AstBuilder<'_>,
|
|
144
|
+
value: PyObject,
|
|
145
|
+
index: PyObject,
|
|
146
|
+
ctx: PyObject,
|
|
147
|
+
span: &SourceSpan,
|
|
148
|
+
) -> Result<PyObject, LowerError> {
|
|
149
|
+
let slice = builder.wrap_index(index, span).map_err(py_err_to_lower)?;
|
|
150
|
+
builder
|
|
151
|
+
.call_node("Subscript", vec![value, slice, ctx], span)
|
|
152
|
+
.map_err(py_err_to_lower)
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
pub(crate) fn lower_regex_match(
|
|
147
156
|
builder: &AstBuilder<'_>,
|
|
148
157
|
value: &Expr,
|
|
@@ -450,17 +459,13 @@ pub(crate) fn lower_expr_with_exception(
|
|
|
450
459
|
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
451
460
|
)?;
|
|
452
461
|
let index_expr = number_expr(builder, &python_index.to_string(), span)?;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
],
|
|
461
|
-
span,
|
|
462
|
-
)
|
|
463
|
-
.map_err(py_err_to_lower)
|
|
462
|
+
subscript_expr(
|
|
463
|
+
builder,
|
|
464
|
+
value,
|
|
465
|
+
index_expr,
|
|
466
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
467
|
+
span,
|
|
468
|
+
)
|
|
464
469
|
}
|
|
465
470
|
Expr::Number { value, span } => number_expr(builder, value, span),
|
|
466
471
|
Expr::String {
|
|
@@ -805,17 +810,13 @@ pub(crate) fn lower_expr_with_exception(
|
|
|
805
810
|
span,
|
|
806
811
|
)
|
|
807
812
|
.map_err(py_err_to_lower)?;
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
],
|
|
816
|
-
span,
|
|
817
|
-
)
|
|
818
|
-
.map_err(py_err_to_lower)
|
|
813
|
+
subscript_expr(
|
|
814
|
+
builder,
|
|
815
|
+
tuple_expr,
|
|
816
|
+
index_expr,
|
|
817
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
818
|
+
span,
|
|
819
|
+
)
|
|
819
820
|
}
|
|
820
821
|
Expr::Regex { pattern, span } => {
|
|
821
822
|
let func = name_expr(
|
|
@@ -908,17 +909,13 @@ pub(crate) fn lower_expr_with_exception(
|
|
|
908
909
|
.parse::<i32>()
|
|
909
910
|
.map_err(|_| LowerError::new(format!("Invalid match group index: .{attr}")))?;
|
|
910
911
|
let index_expr = number_expr(builder, &index.to_string(), span)?;
|
|
911
|
-
return
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
],
|
|
919
|
-
span,
|
|
920
|
-
)
|
|
921
|
-
.map_err(py_err_to_lower);
|
|
912
|
+
return subscript_expr(
|
|
913
|
+
builder,
|
|
914
|
+
value,
|
|
915
|
+
index_expr,
|
|
916
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
917
|
+
span,
|
|
918
|
+
);
|
|
922
919
|
}
|
|
923
920
|
builder
|
|
924
921
|
.call_node(
|
|
@@ -935,13 +932,13 @@ pub(crate) fn lower_expr_with_exception(
|
|
|
935
932
|
Expr::Index { value, index, span } => {
|
|
936
933
|
let value = lower_expr_with_exception(builder, value, exception_name)?;
|
|
937
934
|
let index = lower_expr_with_exception(builder, index, exception_name)?;
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
935
|
+
subscript_expr(
|
|
936
|
+
builder,
|
|
937
|
+
value,
|
|
938
|
+
index,
|
|
939
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
940
|
+
span,
|
|
941
|
+
)
|
|
945
942
|
}
|
|
946
943
|
Expr::Paren { expr, .. } => lower_expr_with_exception(builder, expr, exception_name),
|
|
947
944
|
Expr::List { elements, span } => {
|
|
@@ -1344,17 +1341,13 @@ fn lower_postfix_name_incr(
|
|
|
1344
1341
|
span,
|
|
1345
1342
|
)
|
|
1346
1343
|
.map_err(py_err_to_lower)?;
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
],
|
|
1355
|
-
span,
|
|
1356
|
-
)
|
|
1357
|
-
.map_err(py_err_to_lower)
|
|
1344
|
+
subscript_expr(
|
|
1345
|
+
builder,
|
|
1346
|
+
tuple_expr,
|
|
1347
|
+
index_expr,
|
|
1348
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
1349
|
+
span,
|
|
1350
|
+
)
|
|
1358
1351
|
}
|
|
1359
1352
|
|
|
1360
1353
|
fn incr_delta(op: IncrOp) -> &'static str {
|
|
@@ -1506,17 +1499,13 @@ fn lower_lambda_body_expr(
|
|
|
1506
1499
|
span,
|
|
1507
1500
|
)
|
|
1508
1501
|
.map_err(py_err_to_lower)?;
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
],
|
|
1517
|
-
span,
|
|
1518
|
-
)
|
|
1519
|
-
.map_err(py_err_to_lower)
|
|
1502
|
+
subscript_expr(
|
|
1503
|
+
builder,
|
|
1504
|
+
tuple_expr,
|
|
1505
|
+
index_expr,
|
|
1506
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
1507
|
+
span,
|
|
1508
|
+
)
|
|
1520
1509
|
}
|
|
1521
1510
|
|
|
1522
1511
|
fn lower_call_arguments(
|
|
@@ -6,13 +6,19 @@ use snail_error::LowerError;
|
|
|
6
6
|
pub struct AstBuilder<'py> {
|
|
7
7
|
py: Python<'py>,
|
|
8
8
|
ast: Bound<'py, PyModule>,
|
|
9
|
+
needs_index_wrapper: bool,
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
impl<'py> AstBuilder<'py> {
|
|
12
13
|
pub fn new(py: Python<'py>) -> PyResult<Self> {
|
|
14
|
+
let version_info = py.import_bound("sys")?.getattr("version_info")?;
|
|
15
|
+
let major: u8 = version_info.get_item(0)?.extract()?;
|
|
16
|
+
let minor: u8 = version_info.get_item(1)?.extract()?;
|
|
17
|
+
let needs_index_wrapper = major == 3 && minor < 9;
|
|
13
18
|
Ok(Self {
|
|
14
19
|
py,
|
|
15
20
|
ast: py.import_bound("ast")?,
|
|
21
|
+
needs_index_wrapper,
|
|
16
22
|
})
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -65,6 +71,28 @@ impl<'py> AstBuilder<'py> {
|
|
|
65
71
|
let node = self.ast.getattr(name)?.call1(tuple)?;
|
|
66
72
|
Ok(node.into_py(self.py))
|
|
67
73
|
}
|
|
74
|
+
|
|
75
|
+
pub fn wrap_index(&self, slice: PyObject, span: &SourceSpan) -> PyResult<PyObject> {
|
|
76
|
+
if self.needs_index_wrapper {
|
|
77
|
+
let slice_obj = slice.bind(self.py);
|
|
78
|
+
if let Ok(slice_type) = self.ast.getattr("Slice")
|
|
79
|
+
&& slice_obj.is_instance(&slice_type)?
|
|
80
|
+
{
|
|
81
|
+
return Ok(slice);
|
|
82
|
+
}
|
|
83
|
+
if let Ok(ext_slice_type) = self.ast.getattr("ExtSlice")
|
|
84
|
+
&& slice_obj.is_instance(&ext_slice_type)?
|
|
85
|
+
{
|
|
86
|
+
return Ok(slice);
|
|
87
|
+
}
|
|
88
|
+
if let Ok(index_type) = self.ast.getattr("Index") {
|
|
89
|
+
let index = index_type.call1((slice,))?;
|
|
90
|
+
set_location(&index, span)?;
|
|
91
|
+
return Ok(index.into_py(self.py));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
Ok(slice)
|
|
95
|
+
}
|
|
68
96
|
}
|
|
69
97
|
|
|
70
98
|
pub fn set_location(node: &Bound<'_, PyAny>, span: &SourceSpan) -> PyResult<()> {
|
|
@@ -4,12 +4,13 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "snail-lang"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.2"
|
|
8
8
|
description = "Snail programming language interpreter"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
12
12
|
dependencies = [
|
|
13
|
+
"astunparse>=1.6.3; python_version < '3.9'",
|
|
13
14
|
"jmespath>=1.0.1",
|
|
14
15
|
]
|
|
15
16
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
+
from typing import Optional
|
|
5
6
|
|
|
6
7
|
from . import __build_info__, compile_ast, exec
|
|
7
8
|
|
|
@@ -79,7 +80,11 @@ def _install_trimmed_excepthook() -> None:
|
|
|
79
80
|
colorize = _colorize.can_colorize(file=sys.stderr)
|
|
80
81
|
except Exception:
|
|
81
82
|
colorize = hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
|
|
82
|
-
|
|
83
|
+
try:
|
|
84
|
+
formatted = tb_exc.format(colorize=colorize)
|
|
85
|
+
except TypeError:
|
|
86
|
+
formatted = tb_exc.format()
|
|
87
|
+
for line in formatted:
|
|
83
88
|
sys.stderr.write(line)
|
|
84
89
|
|
|
85
90
|
sys.excepthook = _snail_excepthook
|
|
@@ -87,13 +92,14 @@ def _install_trimmed_excepthook() -> None:
|
|
|
87
92
|
|
|
88
93
|
class _Args:
|
|
89
94
|
def __init__(self) -> None:
|
|
90
|
-
self.file: str
|
|
95
|
+
self.file: Optional[str] = None
|
|
91
96
|
self.awk = False
|
|
92
97
|
self.map = False
|
|
93
98
|
self.no_print = False
|
|
94
99
|
self.no_auto_import = False
|
|
95
100
|
self.debug = False
|
|
96
101
|
self.debug_snail_ast = False
|
|
102
|
+
self.debug_python_ast = False
|
|
97
103
|
self.version = False
|
|
98
104
|
self.help = False
|
|
99
105
|
self.begin_code: list[str] = []
|
|
@@ -127,6 +133,7 @@ def _print_help(file=None) -> None:
|
|
|
127
133
|
print(" -I, --no-auto-import disable auto-imports", file=file)
|
|
128
134
|
print(" --debug parse and compile, then print, do not run", file=file)
|
|
129
135
|
print(" --debug-snail-ast parse and print Snail AST, do not run", file=file)
|
|
136
|
+
print(" --debug-python-ast parse and print Python AST, do not run", file=file)
|
|
130
137
|
print(" -v, --version show version and exit", file=file)
|
|
131
138
|
print(" -h, --help show this help message and exit", file=file)
|
|
132
139
|
|
|
@@ -229,6 +236,10 @@ def _parse_args(argv: list[str]) -> _Args:
|
|
|
229
236
|
args.debug_snail_ast = True
|
|
230
237
|
idx += 1
|
|
231
238
|
continue
|
|
239
|
+
if token == "--debug-python-ast":
|
|
240
|
+
args.debug_python_ast = True
|
|
241
|
+
idx += 1
|
|
242
|
+
continue
|
|
232
243
|
if token == "-f":
|
|
233
244
|
if idx + 1 >= len(argv):
|
|
234
245
|
raise ValueError("option -f requires an argument")
|
|
@@ -252,7 +263,7 @@ def _parse_args(argv: list[str]) -> _Args:
|
|
|
252
263
|
return args
|
|
253
264
|
|
|
254
265
|
|
|
255
|
-
def _format_version(version: str, build_info: dict[str, object]
|
|
266
|
+
def _format_version(version: str, build_info: Optional[dict[str, object]]) -> str:
|
|
256
267
|
display_version = version if version.startswith("v") else f"v{version}"
|
|
257
268
|
if not build_info:
|
|
258
269
|
return display_version
|
|
@@ -277,7 +288,19 @@ def _get_version() -> str:
|
|
|
277
288
|
return version
|
|
278
289
|
|
|
279
290
|
|
|
280
|
-
def
|
|
291
|
+
def _format_python_runtime() -> str:
|
|
292
|
+
version = (
|
|
293
|
+
f"{sys.version_info.major}."
|
|
294
|
+
f"{sys.version_info.minor}."
|
|
295
|
+
f"{sys.version_info.micro}"
|
|
296
|
+
)
|
|
297
|
+
executable = sys.executable or "<unknown>"
|
|
298
|
+
if executable != "<unknown>":
|
|
299
|
+
executable = os.path.abspath(executable)
|
|
300
|
+
return f"Python {version} ({executable})"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
281
304
|
if argv is None:
|
|
282
305
|
_install_trimmed_excepthook()
|
|
283
306
|
argv = sys.argv[1:]
|
|
@@ -294,6 +317,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
294
317
|
return 0
|
|
295
318
|
if namespace.version:
|
|
296
319
|
print(_format_version(_get_version(), __build_info__))
|
|
320
|
+
print(_format_python_runtime())
|
|
297
321
|
return 0
|
|
298
322
|
|
|
299
323
|
# Validate --awk and --map are mutually exclusive
|
|
@@ -343,6 +367,24 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
343
367
|
print(snail_ast)
|
|
344
368
|
return 0
|
|
345
369
|
|
|
370
|
+
if namespace.debug_python_ast:
|
|
371
|
+
import ast
|
|
372
|
+
|
|
373
|
+
python_ast = compile_ast(
|
|
374
|
+
source,
|
|
375
|
+
mode=mode,
|
|
376
|
+
auto_print=not namespace.no_print,
|
|
377
|
+
filename=filename,
|
|
378
|
+
begin_code=namespace.begin_code,
|
|
379
|
+
end_code=namespace.end_code,
|
|
380
|
+
)
|
|
381
|
+
try:
|
|
382
|
+
output = ast.dump(python_ast, indent=2)
|
|
383
|
+
except TypeError:
|
|
384
|
+
output = ast.dump(python_ast)
|
|
385
|
+
print(output)
|
|
386
|
+
return 0
|
|
387
|
+
|
|
346
388
|
if namespace.debug:
|
|
347
389
|
import ast
|
|
348
390
|
import builtins
|
|
@@ -356,7 +398,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
356
398
|
end_code=namespace.end_code,
|
|
357
399
|
)
|
|
358
400
|
builtins.compile(python_ast, _display_filename(filename), "exec")
|
|
359
|
-
|
|
401
|
+
try:
|
|
402
|
+
output = ast.unparse(python_ast)
|
|
403
|
+
except AttributeError:
|
|
404
|
+
import astunparse
|
|
405
|
+
|
|
406
|
+
output = astunparse.unparse(python_ast).rstrip("\n")
|
|
407
|
+
print(output)
|
|
360
408
|
return 0
|
|
361
409
|
|
|
362
410
|
return exec(
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
|
+
from typing import Optional
|
|
4
5
|
|
|
5
6
|
__all__ = ["install_helpers", "AutoImportDict", "AUTO_IMPORT_NAMES"]
|
|
6
7
|
|
|
7
8
|
# Names that can be auto-imported when first referenced.
|
|
8
9
|
# Maps name -> (module, attribute) where attribute is None for whole-module imports.
|
|
9
|
-
AUTO_IMPORT_NAMES: dict[str, tuple[str, str
|
|
10
|
+
AUTO_IMPORT_NAMES: dict[str, tuple[str, Optional[str]]] = {
|
|
10
11
|
# Whole module imports: import X
|
|
11
12
|
"sys": ("sys", None),
|
|
12
13
|
"os": ("os", None),
|
|
@@ -47,6 +48,7 @@ _incr_attr = None
|
|
|
47
48
|
_incr_index = None
|
|
48
49
|
_aug_attr = None
|
|
49
50
|
_aug_index = None
|
|
51
|
+
_env_map = None
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
def _get_compact_try():
|
|
@@ -130,6 +132,15 @@ def _get_lazy_file_class():
|
|
|
130
132
|
return _lazy_file_class
|
|
131
133
|
|
|
132
134
|
|
|
135
|
+
def _get_env_map():
|
|
136
|
+
global _env_map
|
|
137
|
+
if _env_map is None:
|
|
138
|
+
from .env import EnvMap
|
|
139
|
+
|
|
140
|
+
_env_map = EnvMap()
|
|
141
|
+
return _env_map
|
|
142
|
+
|
|
143
|
+
|
|
133
144
|
def _get_incr_attr():
|
|
134
145
|
global _incr_attr
|
|
135
146
|
if _incr_attr is None:
|
|
@@ -244,6 +255,7 @@ def install_helpers(globals_dict: dict) -> None:
|
|
|
244
255
|
globals_dict["__snail_incr_index"] = _lazy_incr_index
|
|
245
256
|
globals_dict["__snail_aug_attr"] = _lazy_aug_attr
|
|
246
257
|
globals_dict["__snail_aug_index"] = _lazy_aug_index
|
|
258
|
+
globals_dict["__snail_env"] = _get_env_map()
|
|
247
259
|
globals_dict["js"] = _lazy_js
|
|
248
260
|
globals_dict["__SnailLazyText"] = _get_lazy_text_class()
|
|
249
261
|
globals_dict["__SnailLazyFile"] = _get_lazy_file_class()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EnvMap:
|
|
7
|
+
__slots__ = ("_env",)
|
|
8
|
+
|
|
9
|
+
def __init__(self, env=None) -> None:
|
|
10
|
+
self._env = os.environ if env is None else env
|
|
11
|
+
|
|
12
|
+
def __fallback__(self) -> str:
|
|
13
|
+
return ""
|
|
14
|
+
|
|
15
|
+
def _lookup(self, key):
|
|
16
|
+
try:
|
|
17
|
+
return self._env[key]
|
|
18
|
+
except KeyError as exc:
|
|
19
|
+
exc.__fallback__ = self.__fallback__
|
|
20
|
+
raise
|
|
21
|
+
|
|
22
|
+
def __getitem__(self, key):
|
|
23
|
+
return self._lookup(key)
|
|
24
|
+
|
|
25
|
+
def __getattr__(self, name):
|
|
26
|
+
return self._lookup(name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|