snail-lang 0.7.2__tar.gz → 0.7.4__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.2 → snail_lang-0.7.4}/Cargo.lock +5 -5
- {snail_lang-0.7.2 → snail_lang-0.7.4}/PKG-INFO +50 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/README.md +49 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-ast/Cargo.toml +1 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-error/Cargo.toml +1 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/Cargo.toml +1 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/lib.rs +89 -4
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/snail.pest +8 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/tests/errors.rs +24 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/Cargo.toml +1 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/compiler.rs +25 -4
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lib.rs +39 -11
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/mod.rs +1 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/program.rs +42 -4
- {snail_lang-0.7.2 → snail_lang-0.7.4}/pyproject.toml +1 -1
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/cli.py +8 -21
- snail_lang-0.7.4/python/snail/runtime/structured_accessor.py +166 -0
- snail_lang-0.7.2/python/snail/runtime/structured_accessor.py +0 -74
- {snail_lang-0.7.2 → snail_lang-0.7.4}/Cargo.toml +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/LICENSE +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-ast/README.md +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-ast/src/ast.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-ast/src/awk.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-ast/src/lib.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-error/README.md +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-error/src/lib.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/README.md +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/awk.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/expr.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/literal.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/stmt.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/string.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/src/util.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/tests/common.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/tests/parser.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/tests/statements.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-parser/tests/syntax_strings.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/build.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/linecache.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/awk.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/constants.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/desugar.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/expr.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/helpers.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/map.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/operators.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/py_ast.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/stmt.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/lower/validate.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/crates/snail-python/src/profiling.rs +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/__init__.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/runtime/__init__.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/runtime/augmented.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/runtime/compact_try.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/runtime/env.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/runtime/lazy_file.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/runtime/lazy_text.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/python/snail/runtime/regex.py +0 -0
- {snail_lang-0.7.2 → snail_lang-0.7.4}/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.4"
|
|
316
316
|
|
|
317
317
|
[[package]]
|
|
318
318
|
name = "snail-error"
|
|
319
|
-
version = "0.7.
|
|
319
|
+
version = "0.7.4"
|
|
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.4"
|
|
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.4"
|
|
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.4"
|
|
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.4
|
|
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
|
|
@@ -327,3 +363,16 @@ make test
|
|
|
327
363
|
make install
|
|
328
364
|
```
|
|
329
365
|
|
|
366
|
+
### Arch Linux (PKGBUILD)
|
|
367
|
+
|
|
368
|
+
An Arch package build file is available at `extras/arch/PKGBUILD`.
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
mkdir -p /tmp/snail-pkg
|
|
372
|
+
cp extras/arch/PKGBUILD /tmp/snail-pkg/
|
|
373
|
+
cd /tmp/snail-pkg
|
|
374
|
+
|
|
375
|
+
# Update pkgver and sha256sums as needed, then build and install
|
|
376
|
+
makepkg -si
|
|
377
|
+
```
|
|
378
|
+
|
|
@@ -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
|
|
@@ -313,3 +349,16 @@ cd snail
|
|
|
313
349
|
make test
|
|
314
350
|
make install
|
|
315
351
|
```
|
|
352
|
+
|
|
353
|
+
### Arch Linux (PKGBUILD)
|
|
354
|
+
|
|
355
|
+
An Arch package build file is available at `extras/arch/PKGBUILD`.
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
mkdir -p /tmp/snail-pkg
|
|
359
|
+
cp extras/arch/PKGBUILD /tmp/snail-pkg/
|
|
360
|
+
cd /tmp/snail-pkg
|
|
361
|
+
|
|
362
|
+
# Update pkgver and sha256sums as needed, then build and install
|
|
363
|
+
makepkg -si
|
|
364
|
+
```
|
|
@@ -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)
|
|
@@ -277,6 +282,14 @@ struct MapAst {
|
|
|
277
282
|
end_blocks: Vec<Vec<snail_ast::Stmt>>,
|
|
278
283
|
}
|
|
279
284
|
|
|
285
|
+
#[allow(dead_code)]
|
|
286
|
+
#[derive(Debug)]
|
|
287
|
+
struct SnailAst {
|
|
288
|
+
program: snail_ast::Program,
|
|
289
|
+
begin_blocks: Vec<Vec<snail_ast::Stmt>>,
|
|
290
|
+
end_blocks: Vec<Vec<snail_ast::Stmt>>,
|
|
291
|
+
}
|
|
292
|
+
|
|
280
293
|
#[pyfunction(name = "parse_ast")]
|
|
281
294
|
#[pyo3(signature = (source, *, mode = "snail", filename = "<snail>", begin_code = Vec::new(), end_code = Vec::new()))]
|
|
282
295
|
fn parse_ast_py(
|
|
@@ -289,9 +302,24 @@ fn parse_ast_py(
|
|
|
289
302
|
let err_to_syntax =
|
|
290
303
|
|err: ParseError| PySyntaxError::new_err(format_snail_error(&err.into(), filename));
|
|
291
304
|
match parse_mode(mode)? {
|
|
292
|
-
CompileMode::Snail =>
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
CompileMode::Snail => {
|
|
306
|
+
let (program, begin_blocks, end_blocks) =
|
|
307
|
+
parse_program_with_begin_end(source).map_err(err_to_syntax)?;
|
|
308
|
+
let (begin_blocks, end_blocks) =
|
|
309
|
+
merge_map_cli_blocks(&begin_code, &end_code, begin_blocks, end_blocks)
|
|
310
|
+
.map_err(err_to_syntax)?;
|
|
311
|
+
|
|
312
|
+
if begin_blocks.is_empty() && end_blocks.is_empty() {
|
|
313
|
+
return Ok(format!("{:#?}", program));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let snail_ast = SnailAst {
|
|
317
|
+
program,
|
|
318
|
+
begin_blocks,
|
|
319
|
+
end_blocks,
|
|
320
|
+
};
|
|
321
|
+
Ok(format!("{:#?}", snail_ast))
|
|
322
|
+
}
|
|
295
323
|
CompileMode::Awk => {
|
|
296
324
|
let program = if begin_code.is_empty() && end_code.is_empty() {
|
|
297
325
|
parse_awk_program(source).map_err(err_to_syntax)?
|
|
@@ -328,7 +356,7 @@ fn parse_ast_py(
|
|
|
328
356
|
#[pyo3(signature = (source, *, mode = "snail", filename = "<snail>"))]
|
|
329
357
|
fn parse_py(source: &str, mode: &str, filename: &str) -> PyResult<()> {
|
|
330
358
|
match parse_mode(mode)? {
|
|
331
|
-
CompileMode::Snail =>
|
|
359
|
+
CompileMode::Snail => parse_program_with_begin_end(source)
|
|
332
360
|
.map(|_| ())
|
|
333
361
|
.map_err(|err| PySyntaxError::new_err(format_snail_error(&err.into(), filename))),
|
|
334
362
|
CompileMode::Awk => parse_awk_program(source)
|
|
@@ -8,7 +8,9 @@ use super::desugar::LambdaHoister;
|
|
|
8
8
|
use super::helpers::{assign_name, name_expr, number_expr, string_expr};
|
|
9
9
|
use super::py_ast::{AstBuilder, py_err_to_lower};
|
|
10
10
|
use super::stmt::lower_block_with_auto_print;
|
|
11
|
-
use super::validate::{
|
|
11
|
+
use super::validate::{
|
|
12
|
+
validate_yield_usage_awk, validate_yield_usage_blocks, validate_yield_usage_program,
|
|
13
|
+
};
|
|
12
14
|
|
|
13
15
|
pub fn lower_program(py: Python<'_>, program: &Program) -> Result<PyObject, LowerError> {
|
|
14
16
|
lower_program_with_auto_print(py, program, false)
|
|
@@ -18,14 +20,50 @@ pub fn lower_program_with_auto_print(
|
|
|
18
20
|
py: Python<'_>,
|
|
19
21
|
program: &Program,
|
|
20
22
|
auto_print_last: bool,
|
|
23
|
+
) -> Result<PyObject, LowerError> {
|
|
24
|
+
lower_program_with_begin_end(py, program, &[], &[], auto_print_last)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn lower_program_with_begin_end(
|
|
28
|
+
py: Python<'_>,
|
|
29
|
+
program: &Program,
|
|
30
|
+
begin_blocks: &[Vec<Stmt>],
|
|
31
|
+
end_blocks: &[Vec<Stmt>],
|
|
32
|
+
auto_print_last: bool,
|
|
21
33
|
) -> Result<PyObject, LowerError> {
|
|
22
34
|
let mut hoister = LambdaHoister::new();
|
|
35
|
+
let begin_blocks: Vec<Vec<Stmt>> = begin_blocks
|
|
36
|
+
.iter()
|
|
37
|
+
.map(|block| hoister.desugar_block(block))
|
|
38
|
+
.collect();
|
|
23
39
|
let program = hoister.desugar_program(program);
|
|
40
|
+
let end_blocks: Vec<Vec<Stmt>> = end_blocks
|
|
41
|
+
.iter()
|
|
42
|
+
.map(|block| hoister.desugar_block(block))
|
|
43
|
+
.collect();
|
|
24
44
|
validate_yield_usage_program(&program)?;
|
|
45
|
+
validate_yield_usage_blocks(&begin_blocks)?;
|
|
46
|
+
validate_yield_usage_blocks(&end_blocks)?;
|
|
25
47
|
let builder = AstBuilder::new(py).map_err(py_err_to_lower)?;
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
let span = program.span.clone();
|
|
49
|
+
let mut body = Vec::new();
|
|
50
|
+
|
|
51
|
+
for block in begin_blocks {
|
|
52
|
+
let lowered =
|
|
53
|
+
lower_block_with_auto_print(&builder, block.as_slice(), auto_print_last, &span)?;
|
|
54
|
+
body.extend(lowered);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let main_body = lower_block_with_auto_print(&builder, &program.stmts, auto_print_last, &span)?;
|
|
58
|
+
body.extend(main_body);
|
|
59
|
+
|
|
60
|
+
for block in end_blocks {
|
|
61
|
+
let lowered =
|
|
62
|
+
lower_block_with_auto_print(&builder, block.as_slice(), auto_print_last, &span)?;
|
|
63
|
+
body.extend(lowered);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
builder.module(body, &span).map_err(py_err_to_lower)
|
|
29
67
|
}
|
|
30
68
|
|
|
31
69
|
pub fn lower_awk_program(py: Python<'_>, program: &AwkProgram) -> Result<PyObject, LowerError> {
|
|
@@ -6,10 +6,7 @@ from typing import Optional
|
|
|
6
6
|
|
|
7
7
|
from . import __build_info__, compile_ast, exec
|
|
8
8
|
|
|
9
|
-
_USAGE =
|
|
10
|
-
"snail [options] -f <file> [args]...\n"
|
|
11
|
-
" snail [options] <code> [args]..."
|
|
12
|
-
)
|
|
9
|
+
_USAGE = "snail [options] -f <file> [args]...\n snail [options] <code> [args]..."
|
|
13
10
|
_DESCRIPTION = "Snail programming language interpreter"
|
|
14
11
|
_BOOLEAN_FLAGS = frozenset("amPIvh")
|
|
15
12
|
_VALUE_FLAGS = frozenset("fbe")
|
|
@@ -119,11 +116,11 @@ def _print_help(file=None) -> None:
|
|
|
119
116
|
print(" -a, --awk awk mode", file=file)
|
|
120
117
|
print(" -m, --map map mode (process files one at a time)", file=file)
|
|
121
118
|
print(
|
|
122
|
-
" -b, --begin <code> begin block code (
|
|
119
|
+
" -b, --begin <code> begin block code (repeatable)",
|
|
123
120
|
file=file,
|
|
124
121
|
)
|
|
125
122
|
print(
|
|
126
|
-
" -e, --end <code> end block code (
|
|
123
|
+
" -e, --end <code> end block code (repeatable)",
|
|
127
124
|
file=file,
|
|
128
125
|
)
|
|
129
126
|
print(
|
|
@@ -131,7 +128,9 @@ def _print_help(file=None) -> None:
|
|
|
131
128
|
file=file,
|
|
132
129
|
)
|
|
133
130
|
print(" -I, --no-auto-import disable auto-imports", file=file)
|
|
134
|
-
print(
|
|
131
|
+
print(
|
|
132
|
+
" --debug parse and compile, then print, do not run", file=file
|
|
133
|
+
)
|
|
135
134
|
print(" --debug-snail-ast parse and print Snail AST, do not run", file=file)
|
|
136
135
|
print(" --debug-python-ast parse and print Python AST, do not run", file=file)
|
|
137
136
|
print(" -v, --version show version and exit", file=file)
|
|
@@ -170,9 +169,7 @@ def _expand_short_options(argv: list[str]) -> list[str]:
|
|
|
170
169
|
expanded.append(f"-{flag}")
|
|
171
170
|
pos += 1
|
|
172
171
|
continue
|
|
173
|
-
if all(
|
|
174
|
-
ch in _BOOLEAN_FLAGS or ch in _VALUE_FLAGS for ch in remainder
|
|
175
|
-
):
|
|
172
|
+
if all(ch in _BOOLEAN_FLAGS or ch in _VALUE_FLAGS for ch in remainder):
|
|
176
173
|
raise ValueError(
|
|
177
174
|
f"option -{flag} requires an argument and must be last in a "
|
|
178
175
|
"combined flag group"
|
|
@@ -290,9 +287,7 @@ def _get_version() -> str:
|
|
|
290
287
|
|
|
291
288
|
def _format_python_runtime() -> str:
|
|
292
289
|
version = (
|
|
293
|
-
f"{sys.version_info.major}."
|
|
294
|
-
f"{sys.version_info.minor}."
|
|
295
|
-
f"{sys.version_info.micro}"
|
|
290
|
+
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
296
291
|
)
|
|
297
292
|
executable = sys.executable or "<unknown>"
|
|
298
293
|
if executable != "<unknown>":
|
|
@@ -325,14 +320,6 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|
|
325
320
|
print("error: --awk and --map cannot be used together", file=sys.stderr)
|
|
326
321
|
return 2
|
|
327
322
|
|
|
328
|
-
# Validate -b/--begin and -e/--end only with --awk or --map mode
|
|
329
|
-
if (namespace.begin_code or namespace.end_code) and not (namespace.awk or namespace.map):
|
|
330
|
-
print(
|
|
331
|
-
"error: -b/--begin and -e/--end options require --awk or --map mode",
|
|
332
|
-
file=sys.stderr,
|
|
333
|
-
)
|
|
334
|
-
return 2
|
|
335
|
-
|
|
336
323
|
mode = "map" if namespace.map else ("awk" if namespace.awk else "snail")
|
|
337
324
|
|
|
338
325
|
if namespace.file:
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json as _json
|
|
4
|
+
import os as _os
|
|
5
|
+
import sys as _sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _append_transpiled_char(out: list[str], ch: str) -> None:
|
|
9
|
+
if ch == "'":
|
|
10
|
+
out.append("\\")
|
|
11
|
+
out.append("'")
|
|
12
|
+
return
|
|
13
|
+
if ch == "\\":
|
|
14
|
+
out.append("\\")
|
|
15
|
+
out.append("\\")
|
|
16
|
+
return
|
|
17
|
+
out.append(ch)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _transpile_jmespath_query(query: str) -> str:
|
|
21
|
+
out: list[str] = []
|
|
22
|
+
state = "normal"
|
|
23
|
+
i = 0
|
|
24
|
+
while i < len(query):
|
|
25
|
+
ch = query[i]
|
|
26
|
+
if state == "normal":
|
|
27
|
+
if ch == "\\" and i + 1 < len(query) and query[i + 1] == '"':
|
|
28
|
+
out.append('"')
|
|
29
|
+
i += 2
|
|
30
|
+
continue
|
|
31
|
+
if ch == "'":
|
|
32
|
+
state = "single"
|
|
33
|
+
out.append(ch)
|
|
34
|
+
i += 1
|
|
35
|
+
continue
|
|
36
|
+
if ch == "`":
|
|
37
|
+
state = "backtick"
|
|
38
|
+
out.append(ch)
|
|
39
|
+
i += 1
|
|
40
|
+
continue
|
|
41
|
+
if ch == '"':
|
|
42
|
+
state = "double"
|
|
43
|
+
out.append("'")
|
|
44
|
+
i += 1
|
|
45
|
+
continue
|
|
46
|
+
out.append(ch)
|
|
47
|
+
i += 1
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
if state == "single":
|
|
51
|
+
out.append(ch)
|
|
52
|
+
if ch == "\\" and i + 1 < len(query):
|
|
53
|
+
out.append(query[i + 1])
|
|
54
|
+
i += 2
|
|
55
|
+
continue
|
|
56
|
+
if ch == "'":
|
|
57
|
+
state = "normal"
|
|
58
|
+
i += 1
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
if state == "backtick":
|
|
62
|
+
out.append(ch)
|
|
63
|
+
if ch == "\\" and i + 1 < len(query):
|
|
64
|
+
out.append(query[i + 1])
|
|
65
|
+
i += 2
|
|
66
|
+
continue
|
|
67
|
+
if ch == "`":
|
|
68
|
+
state = "normal"
|
|
69
|
+
i += 1
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
if state == "double":
|
|
73
|
+
if ch == '"':
|
|
74
|
+
out.append("'")
|
|
75
|
+
state = "normal"
|
|
76
|
+
i += 1
|
|
77
|
+
continue
|
|
78
|
+
if ch == "\\" and i + 1 < len(query):
|
|
79
|
+
nxt = query[i + 1]
|
|
80
|
+
if nxt == '"':
|
|
81
|
+
_append_transpiled_char(out, '"')
|
|
82
|
+
i += 2
|
|
83
|
+
continue
|
|
84
|
+
if nxt == "\\":
|
|
85
|
+
_append_transpiled_char(out, "\\")
|
|
86
|
+
i += 2
|
|
87
|
+
continue
|
|
88
|
+
_append_transpiled_char(out, "\\")
|
|
89
|
+
i += 1
|
|
90
|
+
continue
|
|
91
|
+
_append_transpiled_char(out, ch)
|
|
92
|
+
i += 1
|
|
93
|
+
|
|
94
|
+
return "".join(out)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def __snail_jmespath_query(query: str):
|
|
98
|
+
"""Create a callable that applies JMESPath query.
|
|
99
|
+
|
|
100
|
+
Used by the $[query] syntax which lowers to __snail_jmespath_query(query).
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
import jmespath as _jmespath
|
|
104
|
+
|
|
105
|
+
transpiled = _transpile_jmespath_query(query)
|
|
106
|
+
|
|
107
|
+
def apply(data):
|
|
108
|
+
return _jmespath.search(transpiled, data)
|
|
109
|
+
|
|
110
|
+
return apply
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _parse_jsonl(content: str):
|
|
114
|
+
lines = [line for line in content.splitlines() if line.strip()]
|
|
115
|
+
if not lines:
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
items = []
|
|
119
|
+
for line in lines:
|
|
120
|
+
try:
|
|
121
|
+
items.append(_json.loads(line))
|
|
122
|
+
except _json.JSONDecodeError as exc:
|
|
123
|
+
raise _json.JSONDecodeError(
|
|
124
|
+
f"Invalid JSONL line: {exc.msg}",
|
|
125
|
+
line,
|
|
126
|
+
exc.pos,
|
|
127
|
+
) from exc
|
|
128
|
+
return items
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def js(input_data=None):
|
|
132
|
+
"""Parse JSON from various input sources.
|
|
133
|
+
|
|
134
|
+
Returns the parsed Python object (dict, list, etc.) directly.
|
|
135
|
+
If called with no arguments, reads from stdin.
|
|
136
|
+
"""
|
|
137
|
+
if input_data is None:
|
|
138
|
+
input_data = _sys.stdin
|
|
139
|
+
|
|
140
|
+
if isinstance(input_data, str):
|
|
141
|
+
try:
|
|
142
|
+
return _json.loads(input_data)
|
|
143
|
+
except _json.JSONDecodeError:
|
|
144
|
+
if _os.path.exists(input_data):
|
|
145
|
+
with open(input_data, "r", encoding="utf-8") as handle:
|
|
146
|
+
content = handle.read()
|
|
147
|
+
try:
|
|
148
|
+
return _json.loads(content)
|
|
149
|
+
except _json.JSONDecodeError:
|
|
150
|
+
return _parse_jsonl(content)
|
|
151
|
+
else:
|
|
152
|
+
return _parse_jsonl(input_data)
|
|
153
|
+
elif hasattr(input_data, "read"):
|
|
154
|
+
content = input_data.read()
|
|
155
|
+
if isinstance(content, bytes):
|
|
156
|
+
content = content.decode("utf-8")
|
|
157
|
+
try:
|
|
158
|
+
return _json.loads(content)
|
|
159
|
+
except _json.JSONDecodeError:
|
|
160
|
+
return _parse_jsonl(content)
|
|
161
|
+
elif isinstance(input_data, (dict, list, int, float, bool)) or input_data is None:
|
|
162
|
+
return input_data
|
|
163
|
+
else:
|
|
164
|
+
raise TypeError(
|
|
165
|
+
f"js() input must be JSON-compatible, got {type(input_data).__name__}"
|
|
166
|
+
)
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json as _json
|
|
4
|
-
import os as _os
|
|
5
|
-
import sys as _sys
|
|
6
|
-
|
|
7
|
-
def __snail_jmespath_query(query: str):
|
|
8
|
-
"""Create a callable that applies JMESPath query.
|
|
9
|
-
|
|
10
|
-
Used by the $[query] syntax which lowers to __snail_jmespath_query(query).
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import jmespath as _jmespath
|
|
14
|
-
|
|
15
|
-
def apply(data):
|
|
16
|
-
return _jmespath.search(query, data)
|
|
17
|
-
|
|
18
|
-
return apply
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _parse_jsonl(content: str):
|
|
22
|
-
lines = [line for line in content.splitlines() if line.strip()]
|
|
23
|
-
if not lines:
|
|
24
|
-
return []
|
|
25
|
-
|
|
26
|
-
items = []
|
|
27
|
-
for line in lines:
|
|
28
|
-
try:
|
|
29
|
-
items.append(_json.loads(line))
|
|
30
|
-
except _json.JSONDecodeError as exc:
|
|
31
|
-
raise _json.JSONDecodeError(
|
|
32
|
-
f"Invalid JSONL line: {exc.msg}",
|
|
33
|
-
line,
|
|
34
|
-
exc.pos,
|
|
35
|
-
) from exc
|
|
36
|
-
return items
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def js(input_data=None):
|
|
40
|
-
"""Parse JSON from various input sources.
|
|
41
|
-
|
|
42
|
-
Returns the parsed Python object (dict, list, etc.) directly.
|
|
43
|
-
If called with no arguments, reads from stdin.
|
|
44
|
-
"""
|
|
45
|
-
if input_data is None:
|
|
46
|
-
input_data = _sys.stdin
|
|
47
|
-
|
|
48
|
-
if isinstance(input_data, str):
|
|
49
|
-
try:
|
|
50
|
-
return _json.loads(input_data)
|
|
51
|
-
except _json.JSONDecodeError:
|
|
52
|
-
if _os.path.exists(input_data):
|
|
53
|
-
with open(input_data, "r", encoding="utf-8") as handle:
|
|
54
|
-
content = handle.read()
|
|
55
|
-
try:
|
|
56
|
-
return _json.loads(content)
|
|
57
|
-
except _json.JSONDecodeError:
|
|
58
|
-
return _parse_jsonl(content)
|
|
59
|
-
else:
|
|
60
|
-
return _parse_jsonl(input_data)
|
|
61
|
-
elif hasattr(input_data, "read"):
|
|
62
|
-
content = input_data.read()
|
|
63
|
-
if isinstance(content, bytes):
|
|
64
|
-
content = content.decode("utf-8")
|
|
65
|
-
try:
|
|
66
|
-
return _json.loads(content)
|
|
67
|
-
except _json.JSONDecodeError:
|
|
68
|
-
return _parse_jsonl(content)
|
|
69
|
-
elif isinstance(input_data, (dict, list, int, float, bool)) or input_data is None:
|
|
70
|
-
return input_data
|
|
71
|
-
else:
|
|
72
|
-
raise TypeError(
|
|
73
|
-
f"js() input must be JSON-compatible, got {type(input_data).__name__}"
|
|
74
|
-
)
|
|
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
|
|
File without changes
|