snail-lang 0.3.9__tar.gz → 0.4.1__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.3.9 → snail_lang-0.4.1}/Cargo.lock +7 -7
- {snail_lang-0.3.9 → snail_lang-0.4.1}/PKG-INFO +7 -14
- {snail_lang-0.3.9 → snail_lang-0.4.1}/README.md +5 -13
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-ast/Cargo.toml +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-core/Cargo.toml +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-error/Cargo.toml +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/Cargo.toml +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/README.md +2 -2
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/awk.rs +8 -3
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/constants.rs +0 -4
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/program.rs +4 -3
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/py_ast.rs +2 -2
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/stmt.rs +23 -14
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/Cargo.toml +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/lib.rs +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/snail.pest +2 -2
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/tests/errors.rs +9 -3
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/tests/parser.rs +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-python/Cargo.toml +1 -1
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-python/src/lib.rs +57 -4
- {snail_lang-0.3.9 → snail_lang-0.4.1}/pyproject.toml +4 -1
- snail_lang-0.4.1/python/snail/cli.py +142 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/python/snail/runtime/structured_accessor.py +1 -1
- snail_lang-0.3.9/python/snail/cli.py +0 -69
- snail_lang-0.3.9/python/snail/vendor/__init__.py +0 -0
- snail_lang-0.3.9/python/snail/vendor/jmespath/LICENSE +0 -21
- snail_lang-0.3.9/python/snail/vendor/jmespath/__init__.py +0 -12
- snail_lang-0.3.9/python/snail/vendor/jmespath/ast.py +0 -90
- snail_lang-0.3.9/python/snail/vendor/jmespath/compat.py +0 -19
- snail_lang-0.3.9/python/snail/vendor/jmespath/exceptions.py +0 -137
- snail_lang-0.3.9/python/snail/vendor/jmespath/functions.py +0 -366
- snail_lang-0.3.9/python/snail/vendor/jmespath/lexer.py +0 -258
- snail_lang-0.3.9/python/snail/vendor/jmespath/parser.py +0 -526
- snail_lang-0.3.9/python/snail/vendor/jmespath/visitor.py +0 -329
- {snail_lang-0.3.9 → snail_lang-0.4.1}/Cargo.toml +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/LICENSE +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-ast/README.md +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-ast/src/ast.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-ast/src/awk.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-ast/src/lib.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-core/README.md +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-core/src/lib.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-error/README.md +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-error/src/lib.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/expr.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/helpers.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/lib.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-lower/src/operators.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/README.md +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/awk.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/expr.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/literal.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/stmt.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/string.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/src/util.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/tests/common.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/tests/statements.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/crates/snail-parser/tests/syntax_strings.rs +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/python/snail/__init__.py +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/python/snail/runtime/__init__.py +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/python/snail/runtime/compact_try.py +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/python/snail/runtime/regex.py +0 -0
- {snail_lang-0.3.9 → snail_lang-0.4.1}/python/snail/runtime/subprocess.py +0 -0
|
@@ -485,11 +485,11 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
|
485
485
|
|
|
486
486
|
[[package]]
|
|
487
487
|
name = "snail-ast"
|
|
488
|
-
version = "0.
|
|
488
|
+
version = "0.4.1"
|
|
489
489
|
|
|
490
490
|
[[package]]
|
|
491
491
|
name = "snail-core"
|
|
492
|
-
version = "0.
|
|
492
|
+
version = "0.4.1"
|
|
493
493
|
dependencies = [
|
|
494
494
|
"pyo3",
|
|
495
495
|
"snail-ast",
|
|
@@ -500,14 +500,14 @@ dependencies = [
|
|
|
500
500
|
|
|
501
501
|
[[package]]
|
|
502
502
|
name = "snail-error"
|
|
503
|
-
version = "0.
|
|
503
|
+
version = "0.4.1"
|
|
504
504
|
dependencies = [
|
|
505
505
|
"snail-ast",
|
|
506
506
|
]
|
|
507
507
|
|
|
508
508
|
[[package]]
|
|
509
509
|
name = "snail-lower"
|
|
510
|
-
version = "0.
|
|
510
|
+
version = "0.4.1"
|
|
511
511
|
dependencies = [
|
|
512
512
|
"pyo3",
|
|
513
513
|
"snail-ast",
|
|
@@ -516,7 +516,7 @@ dependencies = [
|
|
|
516
516
|
|
|
517
517
|
[[package]]
|
|
518
518
|
name = "snail-parser"
|
|
519
|
-
version = "0.
|
|
519
|
+
version = "0.4.1"
|
|
520
520
|
dependencies = [
|
|
521
521
|
"pest",
|
|
522
522
|
"pest_derive",
|
|
@@ -526,7 +526,7 @@ dependencies = [
|
|
|
526
526
|
|
|
527
527
|
[[package]]
|
|
528
528
|
name = "snail-proptest"
|
|
529
|
-
version = "0.
|
|
529
|
+
version = "0.4.1"
|
|
530
530
|
dependencies = [
|
|
531
531
|
"proptest",
|
|
532
532
|
"pyo3",
|
|
@@ -540,7 +540,7 @@ dependencies = [
|
|
|
540
540
|
|
|
541
541
|
[[package]]
|
|
542
542
|
name = "snail-python"
|
|
543
|
-
version = "0.
|
|
543
|
+
version = "0.4.1"
|
|
544
544
|
dependencies = [
|
|
545
545
|
"pyo3",
|
|
546
546
|
"snail-core",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snail-lang
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Requires-Dist: jmespath>=1.0.1
|
|
4
5
|
Requires-Dist: maturin>=1.5 ; extra == 'dev'
|
|
5
6
|
Requires-Dist: pytest ; extra == 'dev'
|
|
6
7
|
Provides-Extra: dev
|
|
@@ -12,17 +13,11 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
|
12
13
|
<p align="center">
|
|
13
14
|
<img src="logo.png" alt="Snail logo" width="200">
|
|
14
15
|
</p>
|
|
15
|
-
|
|
16
|
-
<h1 align="center">Snail</h1>
|
|
17
16
|
<p align="center"><em>What do you get when you shove a snake in a shell?</em></p>
|
|
18
17
|
|
|
19
|
-
<h1>Snail
|
|
20
|
-
improving my knowledge of AI code developement. Things are probably broken
|
|
21
|
-
in interesting and horrible ways.</h1>
|
|
22
|
-
|
|
23
|
-
---
|
|
18
|
+
<h1 align="center">Snail</h1>
|
|
24
19
|
|
|
25
|
-
**Snail** is a programming language that compiles to Python, combining Python's
|
|
20
|
+
**Snail** is a programming language that compiles to Python, combining Python's familiarity and extensive libraries with Perl/awk-inspired syntax for quick scripts and one-liners.
|
|
26
21
|
|
|
27
22
|
## Installing Snail
|
|
28
23
|
|
|
@@ -62,7 +57,7 @@ BEGIN { total = 0 }
|
|
|
62
57
|
END { print("Sum:", total); assert total == 15}
|
|
63
58
|
```
|
|
64
59
|
|
|
65
|
-
Built-in variables: `$
|
|
60
|
+
Built-in variables: `$0` (line), `$1`, `$2` etc (access fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
|
|
66
61
|
|
|
67
62
|
|
|
68
63
|
### Compact Error Handling
|
|
@@ -77,7 +72,7 @@ err = risky()?
|
|
|
77
72
|
err = risky():$e?
|
|
78
73
|
|
|
79
74
|
# Provide a fallback value (exception available as $e)
|
|
80
|
-
value = js("malformed json"):{}?
|
|
75
|
+
value = js("malformed json"):{"error": "invalid json"}?
|
|
81
76
|
details = fetch_url("foo.com"):"default html"?
|
|
82
77
|
exception_info = fetch_url("example.com"):$e.http_response_code?
|
|
83
78
|
|
|
@@ -192,7 +187,7 @@ snail 'result = int("oops"):"bad int {$e}"?; print(result)'
|
|
|
192
187
|
snail 'm = "user@example.com" in /^[\\w.]+@([\\w.]+)$/; if m { print(m[1]) }'
|
|
193
188
|
|
|
194
189
|
# Awk mode: print line numbers for matches
|
|
195
|
-
rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$
|
|
190
|
+
rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$0}") }'
|
|
196
191
|
```
|
|
197
192
|
|
|
198
193
|
## 🏗️ Architecture
|
|
@@ -239,8 +234,6 @@ Installation per platform:
|
|
|
239
234
|
- **macOS**: `brew install python@3.12` (or use the system Python 3)
|
|
240
235
|
- **Windows**: Download from [python.org](https://www.python.org/downloads/)
|
|
241
236
|
|
|
242
|
-
**No Python packages required**: Snail vendors jmespath under `snail.vendor`.
|
|
243
|
-
|
|
244
237
|
### Build, Test, and Install
|
|
245
238
|
|
|
246
239
|
```bash
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<img src="logo.png" alt="Snail logo" width="200">
|
|
3
3
|
</p>
|
|
4
|
-
|
|
5
|
-
<h1 align="center">Snail</h1>
|
|
6
4
|
<p align="center"><em>What do you get when you shove a snake in a shell?</em></p>
|
|
7
5
|
|
|
8
|
-
<h1>Snail
|
|
9
|
-
improving my knowledge of AI code developement. Things are probably broken
|
|
10
|
-
in interesting and horrible ways.</h1>
|
|
11
|
-
|
|
12
|
-
---
|
|
6
|
+
<h1 align="center">Snail</h1>
|
|
13
7
|
|
|
14
|
-
**Snail** is a programming language that compiles to Python, combining Python's
|
|
8
|
+
**Snail** is a programming language that compiles to Python, combining Python's familiarity and extensive libraries with Perl/awk-inspired syntax for quick scripts and one-liners.
|
|
15
9
|
|
|
16
10
|
## Installing Snail
|
|
17
11
|
|
|
@@ -51,7 +45,7 @@ BEGIN { total = 0 }
|
|
|
51
45
|
END { print("Sum:", total); assert total == 15}
|
|
52
46
|
```
|
|
53
47
|
|
|
54
|
-
Built-in variables: `$
|
|
48
|
+
Built-in variables: `$0` (line), `$1`, `$2` etc (access fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
|
|
55
49
|
|
|
56
50
|
|
|
57
51
|
### Compact Error Handling
|
|
@@ -66,7 +60,7 @@ err = risky()?
|
|
|
66
60
|
err = risky():$e?
|
|
67
61
|
|
|
68
62
|
# Provide a fallback value (exception available as $e)
|
|
69
|
-
value = js("malformed json"):{}?
|
|
63
|
+
value = js("malformed json"):{"error": "invalid json"}?
|
|
70
64
|
details = fetch_url("foo.com"):"default html"?
|
|
71
65
|
exception_info = fetch_url("example.com"):$e.http_response_code?
|
|
72
66
|
|
|
@@ -181,7 +175,7 @@ snail 'result = int("oops"):"bad int {$e}"?; print(result)'
|
|
|
181
175
|
snail 'm = "user@example.com" in /^[\\w.]+@([\\w.]+)$/; if m { print(m[1]) }'
|
|
182
176
|
|
|
183
177
|
# Awk mode: print line numbers for matches
|
|
184
|
-
rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$
|
|
178
|
+
rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$0}") }'
|
|
185
179
|
```
|
|
186
180
|
|
|
187
181
|
## 🏗️ Architecture
|
|
@@ -228,8 +222,6 @@ Installation per platform:
|
|
|
228
222
|
- **macOS**: `brew install python@3.12` (or use the system Python 3)
|
|
229
223
|
- **Windows**: Download from [python.org](https://www.python.org/downloads/)
|
|
230
224
|
|
|
231
|
-
**No Python packages required**: Snail vendors jmespath under `snail.vendor`.
|
|
232
|
-
|
|
233
225
|
### Build, Test, and Install
|
|
234
226
|
|
|
235
227
|
```bash
|
|
@@ -27,7 +27,7 @@ This crate is the semantic transformation core of the Snail compiler. It takes S
|
|
|
27
27
|
- **Regex expressions** (`/pattern/`): Transformed into `__snail_regex_compile(pattern)` call
|
|
28
28
|
- **Regex matching** (`string in /pattern/`): Transformed into `__snail_regex_search(string, pattern)` call
|
|
29
29
|
- **Structured accessors** (`$[query]`): Transformed into `__SnailStructuredAccessor(query)` instance
|
|
30
|
-
- **Awk variables**: `$
|
|
30
|
+
- **Awk variables**: `$0`, `$<num>`, `$n`, `$fn`, `$p`, `$m` mapped to Python variable names
|
|
31
31
|
|
|
32
32
|
## Awk Mode Lowering
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ When lowering awk programs, generates a complete Python AST that:
|
|
|
35
35
|
1. Imports `sys` for accessing command-line arguments and stdin
|
|
36
36
|
2. Executes BEGIN blocks before processing input
|
|
37
37
|
3. Creates a main loop that reads lines from files or stdin
|
|
38
|
-
4. Updates awk variables (`$
|
|
38
|
+
4. Updates awk variables (`$0`, `$<num>`, `$n`, etc.) for each line
|
|
39
39
|
5. Evaluates patterns and executes actions for matching lines
|
|
40
40
|
6. Executes END blocks after all input is processed
|
|
41
41
|
|
|
@@ -365,7 +365,12 @@ pub(crate) fn lower_awk_rules_with_auto_print(
|
|
|
365
365
|
let mut stmts = Vec::new();
|
|
366
366
|
for rule in rules {
|
|
367
367
|
let mut action = if rule.has_explicit_action() {
|
|
368
|
-
lower_block_with_auto_print(
|
|
368
|
+
lower_block_with_auto_print(
|
|
369
|
+
builder,
|
|
370
|
+
rule.action.as_ref().unwrap(),
|
|
371
|
+
auto_print,
|
|
372
|
+
&rule.span,
|
|
373
|
+
)?
|
|
369
374
|
} else {
|
|
370
375
|
vec![awk_default_print(builder, &rule.span)?]
|
|
371
376
|
};
|
|
@@ -427,8 +432,8 @@ fn regex_pattern_components(pattern: &Expr) -> Option<(Expr, RegexPattern, Sourc
|
|
|
427
432
|
span,
|
|
428
433
|
} => Some((*value.clone(), pattern.clone(), span.clone())),
|
|
429
434
|
Expr::Regex { pattern, span } => Some((
|
|
430
|
-
Expr::
|
|
431
|
-
|
|
435
|
+
Expr::FieldIndex {
|
|
436
|
+
index: "0".to_string(),
|
|
432
437
|
span: span.clone(),
|
|
433
438
|
},
|
|
434
439
|
pattern.clone(),
|
|
@@ -9,8 +9,6 @@ pub const SNAIL_JMESPATH_QUERY: &str = "__snail_jmespath_query";
|
|
|
9
9
|
pub const SNAIL_PARTIAL_HELPER: &str = "__snail_partial";
|
|
10
10
|
|
|
11
11
|
// Awk-related constants (public within crate)
|
|
12
|
-
pub(crate) const SNAIL_AWK_LINE: &str = "$l";
|
|
13
|
-
pub(crate) const SNAIL_AWK_FIELDS: &str = "$f";
|
|
14
12
|
pub(crate) const SNAIL_AWK_NR: &str = "$n";
|
|
15
13
|
pub(crate) const SNAIL_AWK_FNR: &str = "$fn";
|
|
16
14
|
pub(crate) const SNAIL_AWK_PATH: &str = "$p";
|
|
@@ -24,8 +22,6 @@ pub(crate) const SNAIL_AWK_MATCH_PYVAR: &str = "__snail_match";
|
|
|
24
22
|
|
|
25
23
|
pub(crate) fn injected_py_name(name: &str) -> Option<&'static str> {
|
|
26
24
|
match name {
|
|
27
|
-
SNAIL_AWK_LINE => Some(SNAIL_AWK_LINE_PYVAR),
|
|
28
|
-
SNAIL_AWK_FIELDS => Some(SNAIL_AWK_FIELDS_PYVAR),
|
|
29
25
|
SNAIL_AWK_NR => Some(SNAIL_AWK_NR_PYVAR),
|
|
30
26
|
SNAIL_AWK_FNR => Some(SNAIL_AWK_FNR_PYVAR),
|
|
31
27
|
SNAIL_AWK_PATH => Some(SNAIL_AWK_PATH_PYVAR),
|
|
@@ -18,7 +18,8 @@ pub fn lower_program_with_auto_print(
|
|
|
18
18
|
auto_print_last: bool,
|
|
19
19
|
) -> Result<PyObject, LowerError> {
|
|
20
20
|
let builder = AstBuilder::new(py).map_err(py_err_to_lower)?;
|
|
21
|
-
let body =
|
|
21
|
+
let body =
|
|
22
|
+
lower_block_with_auto_print(&builder, &program.stmts, auto_print_last, &program.span)?;
|
|
22
23
|
builder.module(body, &program.span).map_err(py_err_to_lower)
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -62,7 +63,7 @@ pub fn lower_awk_program_with_auto_print(
|
|
|
62
63
|
|
|
63
64
|
let mut main_body = Vec::new();
|
|
64
65
|
for block in &program.begin_blocks {
|
|
65
|
-
let lowered = lower_block_with_auto_print(&builder, block, auto_print)?;
|
|
66
|
+
let lowered = lower_block_with_auto_print(&builder, block, auto_print, &span)?;
|
|
66
67
|
main_body.extend(lowered);
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -166,7 +167,7 @@ pub fn lower_awk_program_with_auto_print(
|
|
|
166
167
|
main_body.push(for_loop);
|
|
167
168
|
|
|
168
169
|
for block in &program.end_blocks {
|
|
169
|
-
let lowered = lower_block_with_auto_print(&builder, block, auto_print)?;
|
|
170
|
+
let lowered = lower_block_with_auto_print(&builder, block, auto_print, &span)?;
|
|
170
171
|
main_body.extend(lowered);
|
|
171
172
|
}
|
|
172
173
|
|
|
@@ -69,9 +69,9 @@ impl<'py> AstBuilder<'py> {
|
|
|
69
69
|
|
|
70
70
|
pub fn set_location(node: &Bound<'_, PyAny>, span: &SourceSpan) -> PyResult<()> {
|
|
71
71
|
node.setattr("lineno", span.start.line)?;
|
|
72
|
-
node.setattr("col_offset", span.start.column)?;
|
|
72
|
+
node.setattr("col_offset", span.start.column.saturating_sub(1))?;
|
|
73
73
|
node.setattr("end_lineno", span.end.line)?;
|
|
74
|
-
node.setattr("end_col_offset", span.end.column)?;
|
|
74
|
+
node.setattr("end_col_offset", span.end.column.saturating_sub(1))?;
|
|
75
75
|
Ok(())
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -24,10 +24,10 @@ pub(crate) fn lower_stmt(builder: &AstBuilder<'_>, stmt: &Stmt) -> Result<PyObje
|
|
|
24
24
|
span,
|
|
25
25
|
} => {
|
|
26
26
|
let test = lower_expr(builder, cond)?;
|
|
27
|
-
let body = lower_block(builder, body)?;
|
|
27
|
+
let body = lower_block(builder, body, span)?;
|
|
28
28
|
let orelse = else_body
|
|
29
29
|
.as_ref()
|
|
30
|
-
.map(|items| lower_block(builder, items))
|
|
30
|
+
.map(|items| lower_block(builder, items, span))
|
|
31
31
|
.transpose()?
|
|
32
32
|
.unwrap_or_default();
|
|
33
33
|
builder
|
|
@@ -51,10 +51,10 @@ pub(crate) fn lower_stmt(builder: &AstBuilder<'_>, stmt: &Stmt) -> Result<PyObje
|
|
|
51
51
|
} => {
|
|
52
52
|
let target = lower_assign_target(builder, target)?;
|
|
53
53
|
let iter = lower_expr(builder, iter)?;
|
|
54
|
-
let body = lower_block(builder, body)?;
|
|
54
|
+
let body = lower_block(builder, body, span)?;
|
|
55
55
|
let orelse = else_body
|
|
56
56
|
.as_ref()
|
|
57
|
-
.map(|items| lower_block(builder, items))
|
|
57
|
+
.map(|items| lower_block(builder, items, span))
|
|
58
58
|
.transpose()?
|
|
59
59
|
.unwrap_or_default();
|
|
60
60
|
builder
|
|
@@ -77,7 +77,7 @@ pub(crate) fn lower_stmt(builder: &AstBuilder<'_>, stmt: &Stmt) -> Result<PyObje
|
|
|
77
77
|
span,
|
|
78
78
|
} => {
|
|
79
79
|
let args = lower_parameters(builder, params)?;
|
|
80
|
-
let body = lower_block(builder, body)?;
|
|
80
|
+
let body = lower_block(builder, body, span)?;
|
|
81
81
|
builder
|
|
82
82
|
.call_node(
|
|
83
83
|
"FunctionDef",
|
|
@@ -94,7 +94,7 @@ pub(crate) fn lower_stmt(builder: &AstBuilder<'_>, stmt: &Stmt) -> Result<PyObje
|
|
|
94
94
|
.map_err(py_err_to_lower)
|
|
95
95
|
}
|
|
96
96
|
Stmt::Class { name, body, span } => {
|
|
97
|
-
let body = lower_block(builder, body)?;
|
|
97
|
+
let body = lower_block(builder, body, span)?;
|
|
98
98
|
builder
|
|
99
99
|
.call_node(
|
|
100
100
|
"ClassDef",
|
|
@@ -116,19 +116,19 @@ pub(crate) fn lower_stmt(builder: &AstBuilder<'_>, stmt: &Stmt) -> Result<PyObje
|
|
|
116
116
|
finally_body,
|
|
117
117
|
span,
|
|
118
118
|
} => {
|
|
119
|
-
let body = lower_block(builder, body)?;
|
|
119
|
+
let body = lower_block(builder, body, span)?;
|
|
120
120
|
let handlers = handlers
|
|
121
121
|
.iter()
|
|
122
122
|
.map(|handler| lower_except_handler(builder, handler))
|
|
123
123
|
.collect::<Result<Vec<_>, _>>()?;
|
|
124
124
|
let orelse = else_body
|
|
125
125
|
.as_ref()
|
|
126
|
-
.map(|items| lower_block(builder, items))
|
|
126
|
+
.map(|items| lower_block(builder, items, span))
|
|
127
127
|
.transpose()?
|
|
128
128
|
.unwrap_or_default();
|
|
129
129
|
let finalbody = finally_body
|
|
130
130
|
.as_ref()
|
|
131
|
-
.map(|items| lower_block(builder, items))
|
|
131
|
+
.map(|items| lower_block(builder, items, span))
|
|
132
132
|
.transpose()?
|
|
133
133
|
.unwrap_or_default();
|
|
134
134
|
builder
|
|
@@ -149,7 +149,7 @@ pub(crate) fn lower_stmt(builder: &AstBuilder<'_>, stmt: &Stmt) -> Result<PyObje
|
|
|
149
149
|
.iter()
|
|
150
150
|
.map(|item| lower_with_item(builder, item))
|
|
151
151
|
.collect::<Result<Vec<_>, _>>()?;
|
|
152
|
-
let body = lower_block(builder, body)?;
|
|
152
|
+
let body = lower_block(builder, body, span)?;
|
|
153
153
|
builder
|
|
154
154
|
.call_node(
|
|
155
155
|
"With",
|
|
@@ -313,14 +313,16 @@ pub(crate) fn lower_stmt(builder: &AstBuilder<'_>, stmt: &Stmt) -> Result<PyObje
|
|
|
313
313
|
pub(crate) fn lower_block(
|
|
314
314
|
builder: &AstBuilder<'_>,
|
|
315
315
|
block: &[Stmt],
|
|
316
|
+
span: &SourceSpan,
|
|
316
317
|
) -> Result<Vec<PyObject>, LowerError> {
|
|
317
|
-
lower_block_with_auto_print(builder, block, false)
|
|
318
|
+
lower_block_with_auto_print(builder, block, false, span)
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
pub(crate) fn lower_block_with_auto_print(
|
|
321
322
|
builder: &AstBuilder<'_>,
|
|
322
323
|
block: &[Stmt],
|
|
323
324
|
auto_print: bool,
|
|
325
|
+
span: &SourceSpan,
|
|
324
326
|
) -> Result<Vec<PyObject>, LowerError> {
|
|
325
327
|
let mut stmts = Vec::new();
|
|
326
328
|
for (idx, stmt) in block.iter().enumerate() {
|
|
@@ -340,6 +342,13 @@ pub(crate) fn lower_block_with_auto_print(
|
|
|
340
342
|
}
|
|
341
343
|
stmts.push(lower_stmt(builder, stmt)?);
|
|
342
344
|
}
|
|
345
|
+
if stmts.is_empty() {
|
|
346
|
+
stmts.push(
|
|
347
|
+
builder
|
|
348
|
+
.call_node("Pass", Vec::new(), span)
|
|
349
|
+
.map_err(py_err_to_lower)?,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
343
352
|
Ok(stmts)
|
|
344
353
|
}
|
|
345
354
|
|
|
@@ -352,7 +361,7 @@ fn lower_if(
|
|
|
352
361
|
span: &SourceSpan,
|
|
353
362
|
) -> Result<PyObject, LowerError> {
|
|
354
363
|
let test = lower_expr(builder, cond)?;
|
|
355
|
-
let body = lower_block(builder, body)?;
|
|
364
|
+
let body = lower_block(builder, body, span)?;
|
|
356
365
|
let orelse = if let Some((elif_cond, elif_body)) = elifs.first() {
|
|
357
366
|
vec![lower_if(
|
|
358
367
|
builder,
|
|
@@ -363,7 +372,7 @@ fn lower_if(
|
|
|
363
372
|
span,
|
|
364
373
|
)?]
|
|
365
374
|
} else if let Some(else_body) = else_body {
|
|
366
|
-
lower_block(builder, else_body)?
|
|
375
|
+
lower_block(builder, else_body, span)?
|
|
367
376
|
} else {
|
|
368
377
|
Vec::new()
|
|
369
378
|
};
|
|
@@ -468,7 +477,7 @@ fn lower_except_handler(
|
|
|
468
477
|
.as_ref()
|
|
469
478
|
.map(|name| name.to_string().into_py(builder.py()))
|
|
470
479
|
.unwrap_or_else(|| builder.py().None().into_py(builder.py()));
|
|
471
|
-
let body = lower_block(builder, &handler.body)?;
|
|
480
|
+
let body = lower_block(builder, &handler.body, &handler.span)?;
|
|
472
481
|
builder
|
|
473
482
|
.call_node(
|
|
474
483
|
"ExceptHandler",
|
|
@@ -86,7 +86,7 @@ pub fn parse_awk_program(source: &str) -> Result<AwkProgram, ParseError> {
|
|
|
86
86
|
})
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
const AWK_ONLY_NAMES: [&str;
|
|
89
|
+
const AWK_ONLY_NAMES: [&str; 4] = ["$n", "$fn", "$p", "$m"];
|
|
90
90
|
const AWK_ONLY_MESSAGE: &str = "awk variables are only valid in awk mode; use --awk";
|
|
91
91
|
|
|
92
92
|
fn validate_no_awk_syntax(program: &Program, source: &str) -> Result<(), ParseError> {
|
|
@@ -191,7 +191,7 @@ tuple_literal = { "(" ~ ")" | "(" ~ expr ~ "," ~ (expr ~ ("," ~ expr)* ~ ","?)?
|
|
|
191
191
|
list_comp = { "[" ~ expr ~ comp_for ~ "]" }
|
|
192
192
|
list_literal = { "[" ~ (expr ~ ("," ~ expr)*)? ~ "]" }
|
|
193
193
|
dict_comp = { "{" ~ expr ~ ":" ~ expr ~ comp_for ~ "}" }
|
|
194
|
-
dict_literal = { "{" ~
|
|
194
|
+
dict_literal = { "{" ~ dict_entry ~ ("," ~ dict_entry)* ~ "}" }
|
|
195
195
|
dict_entry = { expr ~ ":" ~ expr }
|
|
196
196
|
|
|
197
197
|
// Comprehension clauses
|
|
@@ -206,7 +206,7 @@ none = { "None" }
|
|
|
206
206
|
// Special variables: exception, AWK fields, injected vars
|
|
207
207
|
exception_var = { "$e" }
|
|
208
208
|
field_index_var = @{ "$" ~ ASCII_DIGIT+ }
|
|
209
|
-
injected_var = { "$fn" | "$
|
|
209
|
+
injected_var = { "$fn" | "$n" | "$p" | "$m" }
|
|
210
210
|
|
|
211
211
|
// Number, string, and regex literals
|
|
212
212
|
number = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
|
|
@@ -9,7 +9,7 @@ fn reports_parse_error_with_location() {
|
|
|
9
9
|
let message = err.to_string();
|
|
10
10
|
assert!(message.contains("-->"));
|
|
11
11
|
assert!(message.contains("if"));
|
|
12
|
-
expect_err_span(&err, 1,
|
|
12
|
+
expect_err_span(&err, 1, 6);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
#[test]
|
|
@@ -22,9 +22,9 @@ fn rejects_user_defined_dollar_identifiers() {
|
|
|
22
22
|
|
|
23
23
|
#[test]
|
|
24
24
|
fn rejects_awk_only_variables_in_regular_mode() {
|
|
25
|
-
let err = parse_err("value = $
|
|
25
|
+
let err = parse_err("value = $n");
|
|
26
26
|
let message = err.to_string();
|
|
27
|
-
assert!(message.contains("$
|
|
27
|
+
assert!(message.contains("$n"));
|
|
28
28
|
assert!(message.contains("--awk"));
|
|
29
29
|
expect_err_span(&err, 1, 9);
|
|
30
30
|
}
|
|
@@ -84,6 +84,12 @@ fn parser_reports_error_on_missing_colon_in_dict() {
|
|
|
84
84
|
assert!(err.span.is_some());
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
#[test]
|
|
88
|
+
fn parser_rejects_empty_dict_literal() {
|
|
89
|
+
let err = parse_err("d = {}");
|
|
90
|
+
assert!(err.span.is_some());
|
|
91
|
+
}
|
|
92
|
+
|
|
87
93
|
#[test]
|
|
88
94
|
fn parser_rejects_incomplete_function_def() {
|
|
89
95
|
let err = parse_err("def foo");
|
|
@@ -683,7 +683,7 @@ fn parses_class_followed_by_stmt_without_separator() {
|
|
|
683
683
|
|
|
684
684
|
#[test]
|
|
685
685
|
fn parses_try_followed_by_stmt_without_separator() {
|
|
686
|
-
// Note: using explicit exception type since bare `except { }` is ambiguous
|
|
686
|
+
// Note: using explicit exception type since bare `except { }` is ambiguous
|
|
687
687
|
let source = "try { x } except Exception { y } z";
|
|
688
688
|
let program = parse_ok(source);
|
|
689
689
|
assert_eq!(program.stmts.len(), 2);
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
use pyo3::Bound;
|
|
4
4
|
use pyo3::exceptions::{PyRuntimeError, PySyntaxError, PySystemExit};
|
|
5
5
|
use pyo3::prelude::*;
|
|
6
|
-
use pyo3::types::{PyDict, PyList, PyModule};
|
|
6
|
+
use pyo3::types::{PyDict, PyList, PyModule, PyTuple};
|
|
7
7
|
use snail_core::{
|
|
8
8
|
CompileMode, compile_snail_source_with_auto_print, format_snail_error, parse_awk_program,
|
|
9
9
|
parse_program,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
const SNAIL_TRACE_PREFIX: &str = "snail:";
|
|
13
|
+
|
|
12
14
|
fn parse_mode(mode: &str) -> PyResult<CompileMode> {
|
|
13
15
|
match mode {
|
|
14
16
|
"snail" => Ok(CompileMode::Snail),
|
|
@@ -19,6 +21,53 @@ fn parse_mode(mode: &str) -> PyResult<CompileMode> {
|
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
fn display_filename(filename: &str) -> String {
|
|
25
|
+
if filename.starts_with(SNAIL_TRACE_PREFIX) {
|
|
26
|
+
filename.to_string()
|
|
27
|
+
} else {
|
|
28
|
+
format!("{SNAIL_TRACE_PREFIX}{filename}")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn strip_display_prefix(filename: &str) -> &str {
|
|
33
|
+
filename
|
|
34
|
+
.strip_prefix(SNAIL_TRACE_PREFIX)
|
|
35
|
+
.unwrap_or(filename)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn split_source_lines(source: &str) -> Vec<String> {
|
|
39
|
+
let mut lines = Vec::new();
|
|
40
|
+
let mut start = 0;
|
|
41
|
+
for (idx, ch) in source.char_indices() {
|
|
42
|
+
if ch == '\n' {
|
|
43
|
+
let end = idx + 1;
|
|
44
|
+
lines.push(source[start..end].to_string());
|
|
45
|
+
start = end;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if start < source.len() {
|
|
49
|
+
lines.push(source[start..].to_string());
|
|
50
|
+
}
|
|
51
|
+
lines
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn register_linecache(py: Python<'_>, filename: &str, source: &str) -> PyResult<()> {
|
|
55
|
+
let linecache = py.import_bound("linecache")?;
|
|
56
|
+
let cache = linecache.getattr("cache")?;
|
|
57
|
+
let lines = split_source_lines(source);
|
|
58
|
+
let entry = PyTuple::new_bound(
|
|
59
|
+
py,
|
|
60
|
+
vec![
|
|
61
|
+
source.len().into_py(py),
|
|
62
|
+
py.None().into_py(py),
|
|
63
|
+
PyList::new_bound(py, lines).into_py(py),
|
|
64
|
+
filename.into_py(py),
|
|
65
|
+
],
|
|
66
|
+
);
|
|
67
|
+
cache.set_item(filename, entry)?;
|
|
68
|
+
Ok(())
|
|
69
|
+
}
|
|
70
|
+
|
|
22
71
|
fn compile_source(
|
|
23
72
|
py: Python<'_>,
|
|
24
73
|
source: &str,
|
|
@@ -74,10 +123,12 @@ fn compile_py(
|
|
|
74
123
|
) -> PyResult<PyObject> {
|
|
75
124
|
let mode = parse_mode(mode)?;
|
|
76
125
|
let python_ast = compile_source(py, source, mode, auto_print, filename)?;
|
|
126
|
+
let display = display_filename(filename);
|
|
127
|
+
register_linecache(py, &display, source)?;
|
|
77
128
|
let builtins = py.import_bound("builtins")?;
|
|
78
129
|
let code = builtins
|
|
79
130
|
.getattr("compile")?
|
|
80
|
-
.call1((python_ast,
|
|
131
|
+
.call1((python_ast, display, "exec"))?;
|
|
81
132
|
Ok(code.unbind())
|
|
82
133
|
}
|
|
83
134
|
|
|
@@ -94,11 +145,13 @@ fn exec_py(
|
|
|
94
145
|
) -> PyResult<i32> {
|
|
95
146
|
let mode = parse_mode(mode)?;
|
|
96
147
|
let python_ast = compile_source(py, source, mode, auto_print, filename)?;
|
|
148
|
+
let display = display_filename(filename);
|
|
149
|
+
register_linecache(py, &display, source)?;
|
|
97
150
|
let builtins = py.import_bound("builtins")?;
|
|
98
151
|
let code = builtins
|
|
99
152
|
.getattr("compile")?
|
|
100
|
-
.call1((python_ast,
|
|
101
|
-
let globals = prepare_globals(py, filename, &argv, auto_import)?;
|
|
153
|
+
.call1((python_ast, display, "exec"))?;
|
|
154
|
+
let globals = prepare_globals(py, strip_display_prefix(filename), &argv, auto_import)?;
|
|
102
155
|
|
|
103
156
|
match builtins.getattr("exec")?.call1((code.as_any(), &globals)) {
|
|
104
157
|
Ok(_) => Ok(0),
|
|
@@ -4,11 +4,14 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "snail-lang"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Snail programming language interpreter"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"jmespath>=1.0.1",
|
|
14
|
+
]
|
|
12
15
|
|
|
13
16
|
[project.optional-dependencies]
|
|
14
17
|
dev = [
|