snail-lang 0.7.3__tar.gz → 0.7.5__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.3 → snail_lang-0.7.5}/Cargo.lock +5 -5
- {snail_lang-0.7.3 → snail_lang-0.7.5}/PKG-INFO +38 -3
- {snail_lang-0.7.3 → snail_lang-0.7.5}/README.md +37 -2
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-ast/Cargo.toml +1 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-error/Cargo.toml +1 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/Cargo.toml +1 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/lib.rs +89 -4
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/snail.pest +8 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/tests/errors.rs +24 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/Cargo.toml +1 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/compiler.rs +25 -4
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lib.rs +62 -13
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/awk.rs +29 -16
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/constants.rs +3 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/mod.rs +1 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/program.rs +42 -4
- {snail_lang-0.7.3 → snail_lang-0.7.5}/pyproject.toml +1 -1
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/cli.py +35 -23
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/runtime/__init__.py +27 -0
- snail_lang-0.7.5/python/snail/runtime/structured_accessor.py +166 -0
- snail_lang-0.7.3/python/snail/runtime/structured_accessor.py +0 -74
- {snail_lang-0.7.3 → snail_lang-0.7.5}/Cargo.toml +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/LICENSE +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-ast/README.md +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-ast/src/ast.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-ast/src/awk.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-ast/src/lib.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-error/README.md +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-error/src/lib.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/README.md +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/awk.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/expr.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/literal.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/stmt.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/string.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/src/util.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/tests/common.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/tests/parser.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/tests/statements.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-parser/tests/syntax_strings.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/build.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/linecache.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/desugar.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/expr.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/helpers.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/map.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/operators.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/py_ast.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/stmt.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/lower/validate.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/crates/snail-python/src/profiling.rs +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/__init__.py +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/runtime/augmented.py +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/runtime/compact_try.py +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/runtime/env.py +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/runtime/lazy_file.py +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/runtime/lazy_text.py +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/python/snail/runtime/regex.py +0 -0
- {snail_lang-0.7.3 → snail_lang-0.7.5}/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.5"
|
|
316
316
|
|
|
317
317
|
[[package]]
|
|
318
318
|
name = "snail-error"
|
|
319
|
-
version = "0.7.
|
|
319
|
+
version = "0.7.5"
|
|
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.5"
|
|
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.5"
|
|
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.5"
|
|
344
344
|
dependencies = [
|
|
345
345
|
"pyo3",
|
|
346
346
|
"snail-ast",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snail-lang
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Requires-Dist: astunparse>=1.6.3 ; python_full_version < '3.9'
|
|
5
5
|
Requires-Dist: jmespath>=1.0.1
|
|
6
6
|
Requires-Dist: maturin>=1.5 ; extra == 'dev'
|
|
@@ -121,6 +121,35 @@ snail --map --begin "print('start')" --end "print('done')" "print($src)" *.txt
|
|
|
121
121
|
| `$e` | Exception object in `expr:fallback?` |
|
|
122
122
|
| `$env` | Environment map (wrapper around `os.environ`) |
|
|
123
123
|
|
|
124
|
+
### Begin/End Blocks
|
|
125
|
+
|
|
126
|
+
Regular Snail programs can include `BEGIN { ... }` and `END { ... }` blocks for
|
|
127
|
+
setup and teardown. These blocks can also be supplied via CLI flags (`-b`/`--begin`,
|
|
128
|
+
`-e`/`--end`) in all modes. CLI BEGIN blocks run before in-file BEGIN blocks; CLI
|
|
129
|
+
END blocks run after in-file END blocks.
|
|
130
|
+
BEGIN/END blocks are regular Snail blocks, so awk/map-only `$` variables are not
|
|
131
|
+
available inside them.
|
|
132
|
+
|
|
133
|
+
```snail
|
|
134
|
+
print("running")
|
|
135
|
+
BEGIN { print("start") }
|
|
136
|
+
END { print("done") }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
In regular mode, my main use case for this feature is passing unexported
|
|
140
|
+
variables
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
my_bashvar=123
|
|
144
|
+
snail -b x=$my_bashvar 'int(x) + 1'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This is roughly the same as using $env to access an exported variable.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
my_bashvar=123 snail 'int($env.my_bashvar) + 1'
|
|
151
|
+
```
|
|
152
|
+
|
|
124
153
|
### Compact Error Handling
|
|
125
154
|
|
|
126
155
|
The `?` operator makes error handling terse yet expressive:
|
|
@@ -249,6 +278,13 @@ result = js('{{"foo": 12}}') | $[foo]
|
|
|
249
278
|
names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
|
|
250
279
|
```
|
|
251
280
|
|
|
281
|
+
Snail rewrites JMESPath queries in `$[query]` so that double-quoted segments are
|
|
282
|
+
treated as string literals. This lets you write
|
|
283
|
+
`$[items[?ifname=="eth0"].ifname]` inside a single-quoted shell command. If you
|
|
284
|
+
need JMESPath quoted identifiers (for keys like `"foo-bar"`), escape the quotes
|
|
285
|
+
in the query (for example, `$[\"foo-bar\"]`). JSON literal backticks
|
|
286
|
+
(`` `...` ``) are left unchanged.
|
|
287
|
+
|
|
252
288
|
### Full Python Interoperability
|
|
253
289
|
|
|
254
290
|
Snail compiles to Python AST—import any Python module, use any library, in any
|
|
@@ -287,7 +323,7 @@ Documentation is WIP
|
|
|
287
323
|
|
|
288
324
|
## 🔌 Editor Support
|
|
289
325
|
|
|
290
|
-
Vim/Neovim plugin with
|
|
326
|
+
Vim/Neovim plugin with Tree-sitter-based highlighting (Neovim), formatting, and run commands:
|
|
291
327
|
|
|
292
328
|
```vim
|
|
293
329
|
Plug 'sudonym1/snail', { 'rtp': 'extras/vim' }
|
|
@@ -340,4 +376,3 @@ cd /tmp/snail-pkg
|
|
|
340
376
|
makepkg -si
|
|
341
377
|
```
|
|
342
378
|
|
|
343
|
-
|
|
@@ -108,6 +108,35 @@ snail --map --begin "print('start')" --end "print('done')" "print($src)" *.txt
|
|
|
108
108
|
| `$e` | Exception object in `expr:fallback?` |
|
|
109
109
|
| `$env` | Environment map (wrapper around `os.environ`) |
|
|
110
110
|
|
|
111
|
+
### Begin/End Blocks
|
|
112
|
+
|
|
113
|
+
Regular Snail programs can include `BEGIN { ... }` and `END { ... }` blocks for
|
|
114
|
+
setup and teardown. These blocks can also be supplied via CLI flags (`-b`/`--begin`,
|
|
115
|
+
`-e`/`--end`) in all modes. CLI BEGIN blocks run before in-file BEGIN blocks; CLI
|
|
116
|
+
END blocks run after in-file END blocks.
|
|
117
|
+
BEGIN/END blocks are regular Snail blocks, so awk/map-only `$` variables are not
|
|
118
|
+
available inside them.
|
|
119
|
+
|
|
120
|
+
```snail
|
|
121
|
+
print("running")
|
|
122
|
+
BEGIN { print("start") }
|
|
123
|
+
END { print("done") }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
In regular mode, my main use case for this feature is passing unexported
|
|
127
|
+
variables
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
my_bashvar=123
|
|
131
|
+
snail -b x=$my_bashvar 'int(x) + 1'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This is roughly the same as using $env to access an exported variable.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
my_bashvar=123 snail 'int($env.my_bashvar) + 1'
|
|
138
|
+
```
|
|
139
|
+
|
|
111
140
|
### Compact Error Handling
|
|
112
141
|
|
|
113
142
|
The `?` operator makes error handling terse yet expressive:
|
|
@@ -236,6 +265,13 @@ result = js('{{"foo": 12}}') | $[foo]
|
|
|
236
265
|
names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
|
|
237
266
|
```
|
|
238
267
|
|
|
268
|
+
Snail rewrites JMESPath queries in `$[query]` so that double-quoted segments are
|
|
269
|
+
treated as string literals. This lets you write
|
|
270
|
+
`$[items[?ifname=="eth0"].ifname]` inside a single-quoted shell command. If you
|
|
271
|
+
need JMESPath quoted identifiers (for keys like `"foo-bar"`), escape the quotes
|
|
272
|
+
in the query (for example, `$[\"foo-bar\"]`). JSON literal backticks
|
|
273
|
+
(`` `...` ``) are left unchanged.
|
|
274
|
+
|
|
239
275
|
### Full Python Interoperability
|
|
240
276
|
|
|
241
277
|
Snail compiles to Python AST—import any Python module, use any library, in any
|
|
@@ -274,7 +310,7 @@ Documentation is WIP
|
|
|
274
310
|
|
|
275
311
|
## 🔌 Editor Support
|
|
276
312
|
|
|
277
|
-
Vim/Neovim plugin with
|
|
313
|
+
Vim/Neovim plugin with Tree-sitter-based highlighting (Neovim), formatting, and run commands:
|
|
278
314
|
|
|
279
315
|
```vim
|
|
280
316
|
Plug 'sudonym1/snail', { 'rtp': 'extras/vim' }
|
|
@@ -326,4 +362,3 @@ cd /tmp/snail-pkg
|
|
|
326
362
|
# Update pkgver and sha256sums as needed, then build and install
|
|
327
363
|
makepkg -si
|
|
328
364
|
```
|
|
329
|
-
|
|
@@ -12,7 +12,7 @@ mod string;
|
|
|
12
12
|
mod util;
|
|
13
13
|
|
|
14
14
|
use awk::parse_awk_rule;
|
|
15
|
-
use stmt::{parse_block, parse_stmt
|
|
15
|
+
use stmt::{parse_block, parse_stmt};
|
|
16
16
|
use util::{error_with_span, full_span, parse_error_from_pest, span_from_offset, span_from_pair};
|
|
17
17
|
|
|
18
18
|
#[derive(Parser)]
|
|
@@ -20,8 +20,16 @@ use util::{error_with_span, full_span, parse_error_from_pest, span_from_offset,
|
|
|
20
20
|
pub struct SnailParser;
|
|
21
21
|
|
|
22
22
|
pub type MapProgramWithBeginEnd = (Program, Vec<Vec<Stmt>>, Vec<Vec<Stmt>>);
|
|
23
|
+
pub type ProgramWithBeginEnd = (Program, Vec<Vec<Stmt>>, Vec<Vec<Stmt>>);
|
|
23
24
|
|
|
24
25
|
pub fn parse_program(source: &str) -> Result<Program, ParseError> {
|
|
26
|
+
let (program, _, _) = parse_program_with_begin_end(source)?;
|
|
27
|
+
Ok(program)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Parses a regular Snail program with in-file BEGIN/END blocks.
|
|
31
|
+
/// BEGIN/END blocks are parsed as regular Snail statement blocks (no map/awk vars).
|
|
32
|
+
pub fn parse_program_with_begin_end(source: &str) -> Result<ProgramWithBeginEnd, ParseError> {
|
|
25
33
|
let mut pairs = SnailParser::parse(Rule::program, source)
|
|
26
34
|
.map_err(|err| parse_error_from_pest(err, source))?;
|
|
27
35
|
let pair = pairs
|
|
@@ -29,14 +37,51 @@ pub fn parse_program(source: &str) -> Result<Program, ParseError> {
|
|
|
29
37
|
.ok_or_else(|| ParseError::new("missing program root"))?;
|
|
30
38
|
let span = full_span(source);
|
|
31
39
|
let mut stmts = Vec::new();
|
|
40
|
+
let mut begin_blocks = Vec::new();
|
|
41
|
+
let mut end_blocks = Vec::new();
|
|
42
|
+
let mut entries = Vec::new();
|
|
43
|
+
|
|
32
44
|
for inner in pair.into_inner() {
|
|
33
|
-
if inner.as_rule() == Rule::
|
|
34
|
-
|
|
45
|
+
if inner.as_rule() == Rule::program_entry_list {
|
|
46
|
+
for entry in inner.into_inner() {
|
|
47
|
+
if entry.as_rule() != Rule::program_entry {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
let entry_span = span_from_pair(&entry, source);
|
|
51
|
+
let mut entry_inner = entry.into_inner();
|
|
52
|
+
let entry_pair = entry_inner.next().ok_or_else(|| {
|
|
53
|
+
error_with_span("missing program entry", entry_span.clone(), source)
|
|
54
|
+
})?;
|
|
55
|
+
match entry_pair.as_rule() {
|
|
56
|
+
Rule::program_begin => {
|
|
57
|
+
let block = parse_begin_end_block(entry_pair, source, "BEGIN")?;
|
|
58
|
+
if !block.is_empty() {
|
|
59
|
+
begin_blocks.push(block);
|
|
60
|
+
}
|
|
61
|
+
entries.push((entry_span, ProgramEntryKind::BeginEnd));
|
|
62
|
+
}
|
|
63
|
+
Rule::program_end => {
|
|
64
|
+
let block = parse_begin_end_block(entry_pair, source, "END")?;
|
|
65
|
+
if !block.is_empty() {
|
|
66
|
+
end_blocks.push(block);
|
|
67
|
+
}
|
|
68
|
+
entries.push((entry_span, ProgramEntryKind::BeginEnd));
|
|
69
|
+
}
|
|
70
|
+
_ => {
|
|
71
|
+
let stmt = parse_stmt(entry_pair, source)?;
|
|
72
|
+
entries.push((entry_span, program_entry_kind_for_stmt(&stmt)));
|
|
73
|
+
stmts.push(stmt);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
35
77
|
}
|
|
36
78
|
}
|
|
79
|
+
|
|
80
|
+
validate_program_entry_separators(&entries, source)?;
|
|
81
|
+
|
|
37
82
|
let program = Program { stmts, span };
|
|
38
83
|
validate_no_awk_syntax(&program, source)?;
|
|
39
|
-
Ok(program)
|
|
84
|
+
Ok((program, begin_blocks, end_blocks))
|
|
40
85
|
}
|
|
41
86
|
|
|
42
87
|
pub fn parse_awk_program(source: &str) -> Result<AwkProgram, ParseError> {
|
|
@@ -210,6 +255,46 @@ fn parse_begin_end_block(
|
|
|
210
255
|
Ok(block)
|
|
211
256
|
}
|
|
212
257
|
|
|
258
|
+
#[derive(Clone, Copy)]
|
|
259
|
+
enum ProgramEntryKind {
|
|
260
|
+
BeginEnd,
|
|
261
|
+
Simple,
|
|
262
|
+
Compound,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn program_entry_kind_for_stmt(stmt: &Stmt) -> ProgramEntryKind {
|
|
266
|
+
match stmt {
|
|
267
|
+
Stmt::If { .. }
|
|
268
|
+
| Stmt::While { .. }
|
|
269
|
+
| Stmt::For { .. }
|
|
270
|
+
| Stmt::Def { .. }
|
|
271
|
+
| Stmt::Class { .. }
|
|
272
|
+
| Stmt::Try { .. }
|
|
273
|
+
| Stmt::With { .. } => ProgramEntryKind::Compound,
|
|
274
|
+
_ => ProgramEntryKind::Simple,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fn validate_program_entry_separators(
|
|
279
|
+
entries: &[(SourceSpan, ProgramEntryKind)],
|
|
280
|
+
source: &str,
|
|
281
|
+
) -> Result<(), ParseError> {
|
|
282
|
+
for window in entries.windows(2) {
|
|
283
|
+
let (prev_span, prev_kind) = &window[0];
|
|
284
|
+
let (next_span, _) = &window[1];
|
|
285
|
+
let gap = &source[prev_span.end.offset..next_span.start.offset];
|
|
286
|
+
let has_sep = gap.contains('\n') || gap.contains(';');
|
|
287
|
+
if !has_sep && matches!(prev_kind, ProgramEntryKind::Simple) {
|
|
288
|
+
return Err(error_with_span(
|
|
289
|
+
"expected statement separator",
|
|
290
|
+
span_from_offset(next_span.start.offset, next_span.start.offset, source),
|
|
291
|
+
source,
|
|
292
|
+
));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
Ok(())
|
|
296
|
+
}
|
|
297
|
+
|
|
213
298
|
#[derive(Clone, Copy)]
|
|
214
299
|
enum MapEntryKind {
|
|
215
300
|
BeginEnd,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
// Top-level program entry points
|
|
2
|
-
program = { SOI ~ stmt_sep* ~
|
|
2
|
+
program = { SOI ~ stmt_sep* ~ program_entry_list? ~ stmt_sep* ~ EOI }
|
|
3
3
|
awk_program = { SOI ~ stmt_sep* ~ awk_entry_list? ~ stmt_sep* ~ EOI }
|
|
4
4
|
map_program = { SOI ~ stmt_sep* ~ map_entry_list? ~ stmt_sep* ~ EOI }
|
|
5
5
|
|
|
6
|
+
// Regular mode: program with optional BEGIN/END blocks
|
|
7
|
+
program_entry_list = { program_entry ~ (stmt_sep* ~ program_entry)* ~ stmt_sep* }
|
|
8
|
+
program_entry = { program_begin_end | stmt }
|
|
9
|
+
program_begin_end = _{ program_begin | program_end }
|
|
10
|
+
program_begin = { "BEGIN" ~ block }
|
|
11
|
+
program_end = { "END" ~ block }
|
|
12
|
+
|
|
6
13
|
// AWK mode: pattern-action rules
|
|
7
14
|
awk_entry_list = { awk_entry ~ (stmt_sep* ~ awk_entry)* ~ stmt_sep* }
|
|
8
15
|
awk_entry = _{ awk_begin | awk_end | awk_rule }
|
|
@@ -3,7 +3,7 @@ mod common;
|
|
|
3
3
|
use common::*;
|
|
4
4
|
use snail_parser::{
|
|
5
5
|
parse_awk_program, parse_awk_program_with_begin_end, parse_map_program_with_begin_end,
|
|
6
|
-
parse_program,
|
|
6
|
+
parse_program, parse_program_with_begin_end,
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
#[test]
|
|
@@ -203,6 +203,29 @@ fn parser_rejects_invalid_parameter_syntax() {
|
|
|
203
203
|
assert!(err.span.is_some());
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// ========== Regular Mode BEGIN/END Parser Tests ==========
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn program_begin_end_parsed_as_blocks() {
|
|
210
|
+
let (program, begin_blocks, end_blocks) =
|
|
211
|
+
parse_program_with_begin_end("BEGIN { print(1) }\nprint(2)\nEND { print(3) }")
|
|
212
|
+
.expect("should parse");
|
|
213
|
+
assert_eq!(program.stmts.len(), 1);
|
|
214
|
+
assert_eq!(begin_blocks.len(), 1);
|
|
215
|
+
assert_eq!(end_blocks.len(), 1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#[test]
|
|
219
|
+
fn program_begin_end_rejects_awk_map_vars() {
|
|
220
|
+
let err =
|
|
221
|
+
parse_program_with_begin_end("BEGIN { print($0) }").expect_err("should reject awk vars");
|
|
222
|
+
assert!(err.to_string().contains("$0"));
|
|
223
|
+
|
|
224
|
+
let err =
|
|
225
|
+
parse_program_with_begin_end("BEGIN { print($src) }").expect_err("should reject map vars");
|
|
226
|
+
assert!(err.to_string().contains("$src"));
|
|
227
|
+
}
|
|
228
|
+
|
|
206
229
|
// ========== AWK Mode Parser Tests ==========
|
|
207
230
|
|
|
208
231
|
#[test]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
use crate::lower::{
|
|
2
2
|
lower_awk_program_with_auto_print, lower_map_program_with_begin_end,
|
|
3
|
-
|
|
3
|
+
lower_program_with_begin_end,
|
|
4
4
|
};
|
|
5
5
|
use pyo3::prelude::*;
|
|
6
6
|
use snail_ast::{CompileMode, Stmt};
|
|
7
7
|
use snail_error::{ParseError, SnailError};
|
|
8
8
|
use snail_parser::{
|
|
9
9
|
parse_awk_program, parse_awk_program_with_begin_end, parse_map_program_with_begin_end,
|
|
10
|
-
parse_program,
|
|
10
|
+
parse_program, parse_program_with_begin_end,
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
type BlockList = Vec<Vec<Stmt>>;
|
|
@@ -21,8 +21,14 @@ pub fn compile_snail_source_with_auto_print(
|
|
|
21
21
|
) -> Result<PyObject, SnailError> {
|
|
22
22
|
match mode {
|
|
23
23
|
CompileMode::Snail => {
|
|
24
|
-
let program =
|
|
25
|
-
let module =
|
|
24
|
+
let (program, begin_blocks, end_blocks) = parse_program_with_begin_end(source)?;
|
|
25
|
+
let module = lower_program_with_begin_end(
|
|
26
|
+
py,
|
|
27
|
+
&program,
|
|
28
|
+
&begin_blocks,
|
|
29
|
+
&end_blocks,
|
|
30
|
+
auto_print_last,
|
|
31
|
+
)?;
|
|
26
32
|
Ok(module)
|
|
27
33
|
}
|
|
28
34
|
CompileMode::Awk => {
|
|
@@ -44,6 +50,21 @@ pub fn compile_snail_source_with_auto_print(
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
pub fn compile_snail_source_with_begin_end(
|
|
54
|
+
py: Python<'_>,
|
|
55
|
+
main_source: &str,
|
|
56
|
+
begin_sources: &[&str],
|
|
57
|
+
end_sources: &[&str],
|
|
58
|
+
auto_print_last: bool,
|
|
59
|
+
) -> Result<PyObject, SnailError> {
|
|
60
|
+
let (program, begin_blocks, end_blocks) = parse_program_with_begin_end(main_source)?;
|
|
61
|
+
let begin_blocks = merge_cli_begin_blocks(begin_sources, begin_blocks)?;
|
|
62
|
+
let end_blocks = merge_cli_end_blocks(end_sources, end_blocks)?;
|
|
63
|
+
let module =
|
|
64
|
+
lower_program_with_begin_end(py, &program, &begin_blocks, &end_blocks, auto_print_last)?;
|
|
65
|
+
Ok(module)
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
pub fn compile_awk_source_with_begin_end(
|
|
48
69
|
py: Python<'_>,
|
|
49
70
|
main_source: &str,
|
|
@@ -8,12 +8,15 @@ mod profiling;
|
|
|
8
8
|
pub use lower::{
|
|
9
9
|
lower_awk_program, lower_awk_program_with_auto_print, lower_map_program,
|
|
10
10
|
lower_map_program_with_auto_print, lower_map_program_with_begin_end, lower_program,
|
|
11
|
-
lower_program_with_auto_print,
|
|
11
|
+
lower_program_with_auto_print, lower_program_with_begin_end,
|
|
12
12
|
};
|
|
13
13
|
pub use pyo3::prelude::{PyObject, Python};
|
|
14
14
|
|
|
15
|
-
use compiler::{
|
|
16
|
-
|
|
15
|
+
use compiler::{
|
|
16
|
+
compile_awk_source_with_begin_end, compile_map_source_with_begin_end,
|
|
17
|
+
compile_snail_source_with_auto_print, compile_snail_source_with_begin_end,
|
|
18
|
+
merge_map_cli_blocks,
|
|
19
|
+
};
|
|
17
20
|
use linecache::{display_filename, register_linecache, strip_display_prefix};
|
|
18
21
|
use profiling::{log_profile, profile_enabled};
|
|
19
22
|
use pyo3::Bound;
|
|
@@ -24,7 +27,7 @@ use snail_ast::CompileMode;
|
|
|
24
27
|
use snail_error::{ParseError, format_snail_error};
|
|
25
28
|
use snail_parser::{
|
|
26
29
|
parse_awk_program, parse_awk_program_with_begin_end, parse_map_program_with_begin_end,
|
|
27
|
-
|
|
30
|
+
parse_program_with_begin_end,
|
|
28
31
|
};
|
|
29
32
|
use std::time::Instant;
|
|
30
33
|
|
|
@@ -66,7 +69,7 @@ fn compile_source(
|
|
|
66
69
|
let total_start = Instant::now();
|
|
67
70
|
let compile_start = Instant::now();
|
|
68
71
|
|
|
69
|
-
// If
|
|
72
|
+
// If we have begin/end code, use the specialized function
|
|
70
73
|
let module = if !begin_code.is_empty() || !end_code.is_empty() {
|
|
71
74
|
let begin_refs: Vec<&str> = begin_code.iter().map(|s| s.as_str()).collect();
|
|
72
75
|
let end_refs: Vec<&str> = end_code.iter().map(|s| s.as_str()).collect();
|
|
@@ -79,8 +82,10 @@ fn compile_source(
|
|
|
79
82
|
compile_map_source_with_begin_end(py, source, &begin_refs, &end_refs, auto_print)
|
|
80
83
|
.map_err(|err| PySyntaxError::new_err(format_snail_error(&err, filename)))?
|
|
81
84
|
}
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
CompileMode::Snail => {
|
|
86
|
+
compile_snail_source_with_begin_end(py, source, &begin_refs, &end_refs, auto_print)
|
|
87
|
+
.map_err(|err| PySyntaxError::new_err(format_snail_error(&err, filename)))?
|
|
88
|
+
}
|
|
84
89
|
}
|
|
85
90
|
} else {
|
|
86
91
|
compile_snail_source_with_auto_print(py, source, mode, auto_print)
|
|
@@ -107,6 +112,8 @@ fn prepare_globals<'py>(
|
|
|
107
112
|
filename: &str,
|
|
108
113
|
argv: &[String],
|
|
109
114
|
auto_import: bool,
|
|
115
|
+
awk_field_separators: Option<String>,
|
|
116
|
+
awk_include_whitespace: Option<bool>,
|
|
110
117
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
111
118
|
let runtime = py.import_bound("snail.runtime")?;
|
|
112
119
|
|
|
@@ -126,6 +133,16 @@ fn prepare_globals<'py>(
|
|
|
126
133
|
sys.setattr("argv", PyList::new_bound(py, argv))?;
|
|
127
134
|
|
|
128
135
|
runtime.call_method1("install_helpers", (&globals,))?;
|
|
136
|
+
let separators = awk_field_separators
|
|
137
|
+
.as_deref()
|
|
138
|
+
.filter(|value| !value.is_empty());
|
|
139
|
+
let separators_value = match separators {
|
|
140
|
+
Some(separators) => separators.into_py(py),
|
|
141
|
+
None => py.None().into_py(py),
|
|
142
|
+
};
|
|
143
|
+
globals.set_item("__snail_awk_field_separators", separators_value)?;
|
|
144
|
+
let include_whitespace = awk_include_whitespace.unwrap_or(separators.is_none());
|
|
145
|
+
globals.set_item("__snail_awk_include_whitespace", include_whitespace)?;
|
|
129
146
|
|
|
130
147
|
Ok(globals)
|
|
131
148
|
}
|
|
@@ -196,7 +213,7 @@ fn compile_ast_py(
|
|
|
196
213
|
}
|
|
197
214
|
|
|
198
215
|
#[pyfunction(name = "exec")]
|
|
199
|
-
#[pyo3(signature = (source, *, argv = Vec::new(), mode = "snail", auto_print = true, auto_import = true, filename = "<snail>", begin_code = Vec::new(), end_code = Vec::new()))]
|
|
216
|
+
#[pyo3(signature = (source, *, argv = Vec::new(), mode = "snail", auto_print = true, auto_import = true, filename = "<snail>", begin_code = Vec::new(), end_code = Vec::new(), field_separators = None, include_whitespace = None))]
|
|
200
217
|
#[allow(clippy::too_many_arguments)]
|
|
201
218
|
fn exec_py(
|
|
202
219
|
py: Python<'_>,
|
|
@@ -208,6 +225,8 @@ fn exec_py(
|
|
|
208
225
|
filename: &str,
|
|
209
226
|
begin_code: Vec<String>,
|
|
210
227
|
end_code: Vec<String>,
|
|
228
|
+
field_separators: Option<String>,
|
|
229
|
+
include_whitespace: Option<bool>,
|
|
211
230
|
) -> PyResult<i32> {
|
|
212
231
|
let profile = profile_enabled();
|
|
213
232
|
let total_start = Instant::now();
|
|
@@ -236,7 +255,14 @@ fn exec_py(
|
|
|
236
255
|
log_profile("py_compile", compile_start.elapsed());
|
|
237
256
|
}
|
|
238
257
|
let globals_start = Instant::now();
|
|
239
|
-
let globals = prepare_globals(
|
|
258
|
+
let globals = prepare_globals(
|
|
259
|
+
py,
|
|
260
|
+
strip_display_prefix(filename),
|
|
261
|
+
&argv,
|
|
262
|
+
auto_import,
|
|
263
|
+
field_separators,
|
|
264
|
+
include_whitespace,
|
|
265
|
+
)?;
|
|
240
266
|
if profile {
|
|
241
267
|
log_profile("prepare_globals", globals_start.elapsed());
|
|
242
268
|
}
|
|
@@ -277,6 +303,14 @@ struct MapAst {
|
|
|
277
303
|
end_blocks: Vec<Vec<snail_ast::Stmt>>,
|
|
278
304
|
}
|
|
279
305
|
|
|
306
|
+
#[allow(dead_code)]
|
|
307
|
+
#[derive(Debug)]
|
|
308
|
+
struct SnailAst {
|
|
309
|
+
program: snail_ast::Program,
|
|
310
|
+
begin_blocks: Vec<Vec<snail_ast::Stmt>>,
|
|
311
|
+
end_blocks: Vec<Vec<snail_ast::Stmt>>,
|
|
312
|
+
}
|
|
313
|
+
|
|
280
314
|
#[pyfunction(name = "parse_ast")]
|
|
281
315
|
#[pyo3(signature = (source, *, mode = "snail", filename = "<snail>", begin_code = Vec::new(), end_code = Vec::new()))]
|
|
282
316
|
fn parse_ast_py(
|
|
@@ -289,9 +323,24 @@ fn parse_ast_py(
|
|
|
289
323
|
let err_to_syntax =
|
|
290
324
|
|err: ParseError| PySyntaxError::new_err(format_snail_error(&err.into(), filename));
|
|
291
325
|
match parse_mode(mode)? {
|
|
292
|
-
CompileMode::Snail =>
|
|
293
|
-
|
|
294
|
-
|
|
326
|
+
CompileMode::Snail => {
|
|
327
|
+
let (program, begin_blocks, end_blocks) =
|
|
328
|
+
parse_program_with_begin_end(source).map_err(err_to_syntax)?;
|
|
329
|
+
let (begin_blocks, end_blocks) =
|
|
330
|
+
merge_map_cli_blocks(&begin_code, &end_code, begin_blocks, end_blocks)
|
|
331
|
+
.map_err(err_to_syntax)?;
|
|
332
|
+
|
|
333
|
+
if begin_blocks.is_empty() && end_blocks.is_empty() {
|
|
334
|
+
return Ok(format!("{:#?}", program));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let snail_ast = SnailAst {
|
|
338
|
+
program,
|
|
339
|
+
begin_blocks,
|
|
340
|
+
end_blocks,
|
|
341
|
+
};
|
|
342
|
+
Ok(format!("{:#?}", snail_ast))
|
|
343
|
+
}
|
|
295
344
|
CompileMode::Awk => {
|
|
296
345
|
let program = if begin_code.is_empty() && end_code.is_empty() {
|
|
297
346
|
parse_awk_program(source).map_err(err_to_syntax)?
|
|
@@ -328,7 +377,7 @@ fn parse_ast_py(
|
|
|
328
377
|
#[pyo3(signature = (source, *, mode = "snail", filename = "<snail>"))]
|
|
329
378
|
fn parse_py(source: &str, mode: &str, filename: &str) -> PyResult<()> {
|
|
330
379
|
match parse_mode(mode)? {
|
|
331
|
-
CompileMode::Snail =>
|
|
380
|
+
CompileMode::Snail => parse_program_with_begin_end(source)
|
|
332
381
|
.map(|_| ())
|
|
333
382
|
.map_err(|err| PySyntaxError::new_err(format_snail_error(&err.into(), filename))),
|
|
334
383
|
CompileMode::Awk => parse_awk_program(source)
|
|
@@ -270,23 +270,36 @@ pub(crate) fn lower_awk_line_loop_with_auto_print(
|
|
|
270
270
|
.call_node(
|
|
271
271
|
"Call",
|
|
272
272
|
vec![
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
273
|
+
name_expr(
|
|
274
|
+
builder,
|
|
275
|
+
SNAIL_AWK_SPLIT_HELPER,
|
|
276
|
+
span,
|
|
277
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
278
|
+
)?,
|
|
279
|
+
PyList::new_bound(
|
|
280
|
+
builder.py(),
|
|
281
|
+
vec![
|
|
282
|
+
name_expr(
|
|
283
|
+
builder,
|
|
284
|
+
SNAIL_AWK_LINE_PYVAR,
|
|
285
|
+
span,
|
|
284
286
|
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
287
|
+
)?,
|
|
288
|
+
name_expr(
|
|
289
|
+
builder,
|
|
290
|
+
SNAIL_AWK_FIELD_SEPARATORS_PYVAR,
|
|
291
|
+
span,
|
|
292
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
293
|
+
)?,
|
|
294
|
+
name_expr(
|
|
295
|
+
builder,
|
|
296
|
+
SNAIL_AWK_INCLUDE_WHITESPACE_PYVAR,
|
|
297
|
+
span,
|
|
298
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
299
|
+
)?,
|
|
300
|
+
],
|
|
301
|
+
)
|
|
302
|
+
.into_py(builder.py()),
|
|
290
303
|
PyList::empty_bound(builder.py()).into_py(builder.py()),
|
|
291
304
|
],
|
|
292
305
|
span,
|
|
@@ -13,6 +13,7 @@ pub const SNAIL_INCR_ATTR: &str = "__snail_incr_attr";
|
|
|
13
13
|
pub const SNAIL_INCR_INDEX: &str = "__snail_incr_index";
|
|
14
14
|
pub const SNAIL_AUG_ATTR: &str = "__snail_aug_attr";
|
|
15
15
|
pub const SNAIL_AUG_INDEX: &str = "__snail_aug_index";
|
|
16
|
+
pub(crate) const SNAIL_AWK_SPLIT_HELPER: &str = "__snail_awk_split";
|
|
16
17
|
pub(crate) const SNAIL_LET_VALUE: &str = "__snail_let_value";
|
|
17
18
|
pub(crate) const SNAIL_LET_OK: &str = "__snail_let_ok";
|
|
18
19
|
pub(crate) const SNAIL_LET_KEEP: &str = "__snail_let_keep";
|
|
@@ -28,6 +29,8 @@ pub(crate) const SNAIL_AWK_MATCH: &str = "$m";
|
|
|
28
29
|
pub(crate) const SNAIL_AWK_FIELDS: &str = "$f";
|
|
29
30
|
pub(crate) const SNAIL_AWK_LINE_PYVAR: &str = "__snail_line";
|
|
30
31
|
pub(crate) const SNAIL_AWK_FIELDS_PYVAR: &str = "__snail_fields";
|
|
32
|
+
pub(crate) const SNAIL_AWK_FIELD_SEPARATORS_PYVAR: &str = "__snail_awk_field_separators";
|
|
33
|
+
pub(crate) const SNAIL_AWK_INCLUDE_WHITESPACE_PYVAR: &str = "__snail_awk_include_whitespace";
|
|
31
34
|
pub(crate) const SNAIL_AWK_NR_PYVAR: &str = "__snail_nr_user";
|
|
32
35
|
pub(crate) const SNAIL_AWK_FNR_PYVAR: &str = "__snail_fnr_user";
|
|
33
36
|
pub(crate) const SNAIL_AWK_PATH_PYVAR: &str = "__snail_path_user";
|