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.
Files changed (54) hide show
  1. {snail_lang-0.4.0 → snail_lang-0.4.1}/Cargo.lock +7 -7
  2. {snail_lang-0.4.0 → snail_lang-0.4.1}/PKG-INFO +2 -2
  3. {snail_lang-0.4.0 → snail_lang-0.4.1}/README.md +1 -1
  4. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/Cargo.toml +1 -1
  5. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-core/Cargo.toml +1 -1
  6. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-error/Cargo.toml +1 -1
  7. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/Cargo.toml +1 -1
  8. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/awk.rs +6 -1
  9. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/program.rs +4 -3
  10. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/py_ast.rs +2 -2
  11. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/stmt.rs +23 -14
  12. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/Cargo.toml +1 -1
  13. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/snail.pest +1 -1
  14. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/errors.rs +7 -1
  15. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/parser.rs +1 -1
  16. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-python/Cargo.toml +1 -1
  17. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-python/src/lib.rs +57 -4
  18. {snail_lang-0.4.0 → snail_lang-0.4.1}/pyproject.toml +1 -1
  19. snail_lang-0.4.1/python/snail/cli.py +142 -0
  20. snail_lang-0.4.0/python/snail/cli.py +0 -69
  21. {snail_lang-0.4.0 → snail_lang-0.4.1}/Cargo.toml +0 -0
  22. {snail_lang-0.4.0 → snail_lang-0.4.1}/LICENSE +0 -0
  23. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/README.md +0 -0
  24. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/src/ast.rs +0 -0
  25. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/src/awk.rs +0 -0
  26. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-ast/src/lib.rs +0 -0
  27. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-core/README.md +0 -0
  28. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-core/src/lib.rs +0 -0
  29. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-error/README.md +0 -0
  30. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-error/src/lib.rs +0 -0
  31. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/README.md +0 -0
  32. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/constants.rs +0 -0
  33. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/expr.rs +0 -0
  34. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/helpers.rs +0 -0
  35. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/lib.rs +0 -0
  36. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-lower/src/operators.rs +0 -0
  37. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/README.md +0 -0
  38. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/awk.rs +0 -0
  39. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/expr.rs +0 -0
  40. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/lib.rs +0 -0
  41. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/literal.rs +0 -0
  42. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/stmt.rs +0 -0
  43. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/string.rs +0 -0
  44. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/src/util.rs +0 -0
  45. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/common.rs +0 -0
  46. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/statements.rs +0 -0
  47. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
  48. {snail_lang-0.4.0 → snail_lang-0.4.1}/crates/snail-parser/tests/syntax_strings.rs +0 -0
  49. {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/__init__.py +0 -0
  50. {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/__init__.py +0 -0
  51. {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/compact_try.py +0 -0
  52. {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/regex.py +0 -0
  53. {snail_lang-0.4.0 → snail_lang-0.4.1}/python/snail/runtime/structured_accessor.py +0 -0
  54. {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.0"
488
+ version = "0.4.1"
489
489
 
490
490
  [[package]]
491
491
  name = "snail-core"
492
- version = "0.4.0"
492
+ version = "0.4.1"
493
493
  dependencies = [
494
494
  "pyo3",
495
495
  "snail-ast",
@@ -500,14 +500,14 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "snail-error"
503
- version = "0.4.0"
503
+ version = "0.4.1"
504
504
  dependencies = [
505
505
  "snail-ast",
506
506
  ]
507
507
 
508
508
  [[package]]
509
509
  name = "snail-lower"
510
- version = "0.4.0"
510
+ version = "0.4.1"
511
511
  dependencies = [
512
512
  "pyo3",
513
513
  "snail-ast",
@@ -516,7 +516,7 @@ dependencies = [
516
516
 
517
517
  [[package]]
518
518
  name = "snail-parser"
519
- version = "0.4.0"
519
+ version = "0.4.1"
520
520
  dependencies = [
521
521
  "pest",
522
522
  "pest_derive",
@@ -526,7 +526,7 @@ dependencies = [
526
526
 
527
527
  [[package]]
528
528
  name = "snail-proptest"
529
- version = "0.4.0"
529
+ version = "0.4.1"
530
530
  dependencies = [
531
531
  "proptest",
532
532
  "pyo3",
@@ -540,7 +540,7 @@ dependencies = [
540
540
 
541
541
  [[package]]
542
542
  name = "snail-python"
543
- version = "0.4.0"
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.0
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
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-ast"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-core"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition.workspace = true
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-error"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-lower"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -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(builder, rule.action.as_ref().unwrap(), 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 = lower_block_with_auto_print(&builder, &program.stmts, auto_print_last)?;
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",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-parser"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -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 = { "{" ~ (dict_entry ~ ("," ~ dict_entry)*)? ~ "}" }
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, 7);
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 with set literals
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);
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-python"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition.workspace = true
5
5
 
6
6
  [lib]
@@ -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, filename, "exec"))?;
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, filename, "exec"))?;
101
- let globals = prepare_globals(py, filename, &argv, auto_import)?;
153
+ .call1((python_ast, display, "exec"))?;
154
+ let globals = prepare_globals(py, strip_display_prefix(filename), &argv, auto_import)?;
102
155
 
103
156
  match builtins.getattr("exec")?.call1((code.as_any(), &globals)) {
104
157
  Ok(_) => Ok(0),
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "snail-lang"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Snail programming language interpreter"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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