snail-lang 0.4.0__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.4.0 → snail_lang-0.4.1}/Cargo.lock +7 -7
- {snail_lang-0.4.0 → snail_lang-0.4.1}/PKG-INFO +2 -2
- {snail_lang-0.4.0 → snail_lang-0.4.1}/README.md +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/Cargo.toml +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-core/Cargo.toml +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-error/Cargo.toml +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/Cargo.toml +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/awk.rs +6 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/program.rs +4 -3
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/py_ast.rs +2 -2
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/stmt.rs +23 -14
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/Cargo.toml +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/snail.pest +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/errors.rs +7 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/parser.rs +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-python/Cargo.toml +1 -1
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-python/src/lib.rs +57 -4
- {snail_lang-0.4.0 → snail_lang-0.4.1}/pyproject.toml +1 -1
- snail_lang-0.4.1/python/snail/cli.py +142 -0
- snail_lang-0.4.0/python/snail/cli.py +0 -69
- {snail_lang-0.4.0 → snail_lang-0.4.1}/Cargo.toml +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/LICENSE +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/README.md +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/src/ast.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/src/awk.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/src/lib.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-core/README.md +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-core/src/lib.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-error/README.md +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-error/src/lib.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/README.md +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/constants.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/expr.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/helpers.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/lib.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/operators.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/README.md +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/awk.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/expr.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/lib.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/literal.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/stmt.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/string.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/util.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/common.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/statements.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/syntax_strings.rs +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/__init__.py +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/__init__.py +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/compact_try.py +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/regex.py +0 -0
- {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/structured_accessor.py +0 -0
- {snail_lang-0.4.0 → 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.4.
|
|
488
|
+
version = "0.4.1"
|
|
489
489
|
|
|
490
490
|
[[package]]
|
|
491
491
|
name = "snail-core"
|
|
492
|
-
version = "0.4.
|
|
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.4.
|
|
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.4.
|
|
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.4.
|
|
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.4.
|
|
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.4.
|
|
543
|
+
version = "0.4.1"
|
|
544
544
|
dependencies = [
|
|
545
545
|
"pyo3",
|
|
546
546
|
"snail-core",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snail-lang
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Requires-Dist: jmespath>=1.0.1
|
|
5
5
|
Requires-Dist: maturin>=1.5 ; extra == 'dev'
|
|
6
6
|
Requires-Dist: pytest ; extra == 'dev'
|
|
@@ -72,7 +72,7 @@ err = risky()?
|
|
|
72
72
|
err = risky():$e?
|
|
73
73
|
|
|
74
74
|
# Provide a fallback value (exception available as $e)
|
|
75
|
-
value = js("malformed json"):{}?
|
|
75
|
+
value = js("malformed json"):{"error": "invalid json"}?
|
|
76
76
|
details = fetch_url("foo.com"):"default html"?
|
|
77
77
|
exception_info = fetch_url("example.com"):$e.http_response_code?
|
|
78
78
|
|
|
@@ -60,7 +60,7 @@ err = risky()?
|
|
|
60
60
|
err = risky():$e?
|
|
61
61
|
|
|
62
62
|
# Provide a fallback value (exception available as $e)
|
|
63
|
-
value = js("malformed json"):{}?
|
|
63
|
+
value = js("malformed json"):{"error": "invalid json"}?
|
|
64
64
|
details = fetch_url("foo.com"):"default html"?
|
|
65
65
|
exception_info = fetch_url("example.com"):$e.http_response_code?
|
|
66
66
|
|
|
@@ -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
|
};
|
|
@@ -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",
|
|
@@ -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
|
|
@@ -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]
|
|
@@ -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),
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import traceback
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import __version__, exec, parse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
13
|
+
return argparse.ArgumentParser(
|
|
14
|
+
prog="snail",
|
|
15
|
+
description="Snail programming language interpreter",
|
|
16
|
+
usage="snail [options] -f <file> [args]...\n snail [options] <code> [args]...",
|
|
17
|
+
add_help=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def _trim_internal_prefix(
|
|
21
|
+
stack: traceback.StackSummary,
|
|
22
|
+
internal_files: set[str],
|
|
23
|
+
) -> None:
|
|
24
|
+
if not stack:
|
|
25
|
+
return
|
|
26
|
+
trim_count = 0
|
|
27
|
+
for frame in stack:
|
|
28
|
+
filename = frame.filename
|
|
29
|
+
if filename.startswith("snail:"):
|
|
30
|
+
break
|
|
31
|
+
if filename in internal_files:
|
|
32
|
+
trim_count += 1
|
|
33
|
+
continue
|
|
34
|
+
if os.path.isabs(filename) and os.path.abspath(filename) in internal_files:
|
|
35
|
+
trim_count += 1
|
|
36
|
+
continue
|
|
37
|
+
break
|
|
38
|
+
if 0 < trim_count < len(stack):
|
|
39
|
+
del stack[:trim_count]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _trim_traceback_exception(
|
|
43
|
+
tb_exc: traceback.TracebackException,
|
|
44
|
+
internal_files: set[str],
|
|
45
|
+
) -> None:
|
|
46
|
+
_trim_internal_prefix(tb_exc.stack, internal_files)
|
|
47
|
+
cause = getattr(tb_exc, "__cause__", None)
|
|
48
|
+
if cause is not None:
|
|
49
|
+
_trim_traceback_exception(cause, internal_files)
|
|
50
|
+
context = getattr(tb_exc, "__context__", None)
|
|
51
|
+
if context is not None:
|
|
52
|
+
_trim_traceback_exception(context, internal_files)
|
|
53
|
+
for group_exc in getattr(tb_exc, "exceptions", ()) or ():
|
|
54
|
+
_trim_traceback_exception(group_exc, internal_files)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _install_trimmed_excepthook() -> None:
|
|
58
|
+
entrypoint = os.path.abspath(sys.argv[0])
|
|
59
|
+
cli_path = os.path.abspath(__file__)
|
|
60
|
+
internal_files = {entrypoint, cli_path}
|
|
61
|
+
original_excepthook = sys.excepthook
|
|
62
|
+
|
|
63
|
+
def _snail_excepthook(
|
|
64
|
+
exc_type: type[BaseException],
|
|
65
|
+
exc: BaseException,
|
|
66
|
+
tb: object,
|
|
67
|
+
) -> None:
|
|
68
|
+
if exc_type is KeyboardInterrupt:
|
|
69
|
+
return original_excepthook(exc_type, exc, tb)
|
|
70
|
+
tb_exc = traceback.TracebackException(
|
|
71
|
+
exc_type,
|
|
72
|
+
exc,
|
|
73
|
+
tb,
|
|
74
|
+
capture_locals=False,
|
|
75
|
+
)
|
|
76
|
+
_trim_traceback_exception(tb_exc, internal_files)
|
|
77
|
+
try:
|
|
78
|
+
import _colorize
|
|
79
|
+
|
|
80
|
+
colorize = _colorize.can_colorize(file=sys.stderr)
|
|
81
|
+
except Exception:
|
|
82
|
+
colorize = hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
|
|
83
|
+
for line in tb_exc.format(colorize=colorize):
|
|
84
|
+
sys.stderr.write(line)
|
|
85
|
+
|
|
86
|
+
sys.excepthook = _snail_excepthook
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main(argv: list[str] | None = None) -> int:
|
|
90
|
+
if argv is None:
|
|
91
|
+
_install_trimmed_excepthook()
|
|
92
|
+
|
|
93
|
+
parser = _build_parser()
|
|
94
|
+
parser.add_argument("-f", dest="file", metavar="file")
|
|
95
|
+
parser.add_argument("-a", "--awk", action="store_true")
|
|
96
|
+
parser.add_argument("-P", "--no-print", action="store_true")
|
|
97
|
+
parser.add_argument("-I", "--no-auto-import", action="store_true")
|
|
98
|
+
parser.add_argument("--parse-only", action="store_true")
|
|
99
|
+
parser.add_argument("-v", "--version", action="store_true")
|
|
100
|
+
parser.add_argument("args", nargs=argparse.REMAINDER)
|
|
101
|
+
|
|
102
|
+
namespace = parser.parse_args(argv)
|
|
103
|
+
|
|
104
|
+
if namespace.version:
|
|
105
|
+
print(__version__)
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
mode = "awk" if namespace.awk else "snail"
|
|
109
|
+
|
|
110
|
+
if namespace.file:
|
|
111
|
+
path = Path(namespace.file)
|
|
112
|
+
try:
|
|
113
|
+
source = path.read_text()
|
|
114
|
+
except OSError as exc:
|
|
115
|
+
print(f"failed to read {path}: {exc}", file=sys.stderr)
|
|
116
|
+
return 1
|
|
117
|
+
filename = str(path)
|
|
118
|
+
args = [filename, *namespace.args]
|
|
119
|
+
else:
|
|
120
|
+
if not namespace.args:
|
|
121
|
+
print("no input provided", file=sys.stderr)
|
|
122
|
+
return 1
|
|
123
|
+
source = namespace.args[0]
|
|
124
|
+
filename = "<cmd>"
|
|
125
|
+
args = ["--", *namespace.args[1:]]
|
|
126
|
+
|
|
127
|
+
if namespace.parse_only:
|
|
128
|
+
parse(source, mode=mode, filename=filename)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
return exec(
|
|
132
|
+
source,
|
|
133
|
+
argv=args,
|
|
134
|
+
mode=mode,
|
|
135
|
+
auto_print=not namespace.no_print,
|
|
136
|
+
auto_import=not namespace.no_auto_import,
|
|
137
|
+
filename=filename,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
raise SystemExit(main())
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import argparse
|
|
4
|
-
import sys
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from . import __version__, exec, parse
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _build_parser() -> argparse.ArgumentParser:
|
|
11
|
-
return argparse.ArgumentParser(
|
|
12
|
-
prog="snail",
|
|
13
|
-
description="Snail programming language interpreter",
|
|
14
|
-
usage="snail [options] -f <file> [args]...\n snail [options] <code> [args]...",
|
|
15
|
-
add_help=True,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def main(argv: list[str] | None = None) -> int:
|
|
20
|
-
parser = _build_parser()
|
|
21
|
-
parser.add_argument("-f", dest="file", metavar="file")
|
|
22
|
-
parser.add_argument("-a", "--awk", action="store_true")
|
|
23
|
-
parser.add_argument("-P", "--no-print", action="store_true")
|
|
24
|
-
parser.add_argument("-I", "--no-auto-import", action="store_true")
|
|
25
|
-
parser.add_argument("--parse-only", action="store_true")
|
|
26
|
-
parser.add_argument("-v", "--version", action="store_true")
|
|
27
|
-
parser.add_argument("args", nargs=argparse.REMAINDER)
|
|
28
|
-
|
|
29
|
-
namespace = parser.parse_args(argv)
|
|
30
|
-
|
|
31
|
-
if namespace.version:
|
|
32
|
-
print(__version__)
|
|
33
|
-
return 0
|
|
34
|
-
|
|
35
|
-
mode = "awk" if namespace.awk else "snail"
|
|
36
|
-
|
|
37
|
-
if namespace.file:
|
|
38
|
-
path = Path(namespace.file)
|
|
39
|
-
try:
|
|
40
|
-
source = path.read_text()
|
|
41
|
-
except OSError as exc:
|
|
42
|
-
print(f"failed to read {path}: {exc}", file=sys.stderr)
|
|
43
|
-
return 1
|
|
44
|
-
filename = str(path)
|
|
45
|
-
args = [filename, *namespace.args]
|
|
46
|
-
else:
|
|
47
|
-
if not namespace.args:
|
|
48
|
-
print("no input provided", file=sys.stderr)
|
|
49
|
-
return 1
|
|
50
|
-
source = namespace.args[0]
|
|
51
|
-
filename = "<cmd>"
|
|
52
|
-
args = ["--", *namespace.args[1:]]
|
|
53
|
-
|
|
54
|
-
if namespace.parse_only:
|
|
55
|
-
parse(source, mode=mode, filename=filename)
|
|
56
|
-
return 0
|
|
57
|
-
|
|
58
|
-
return exec(
|
|
59
|
-
source,
|
|
60
|
-
argv=args,
|
|
61
|
-
mode=mode,
|
|
62
|
-
auto_print=not namespace.no_print,
|
|
63
|
-
auto_import=not namespace.no_auto_import,
|
|
64
|
-
filename=filename,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if __name__ == "__main__":
|
|
69
|
-
raise SystemExit(main())
|
|
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
|