snail-lang 0.7.0__tar.gz → 0.7.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 (58) hide show
  1. {snail_lang-0.7.0 → snail_lang-0.7.1}/Cargo.lock +5 -5
  2. {snail_lang-0.7.0 → snail_lang-0.7.1}/Cargo.toml +6 -0
  3. {snail_lang-0.7.0 → snail_lang-0.7.1}/PKG-INFO +8 -7
  4. {snail_lang-0.7.0 → snail_lang-0.7.1}/README.md +5 -5
  5. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-ast/Cargo.toml +1 -1
  6. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-error/Cargo.toml +1 -1
  7. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/Cargo.toml +1 -1
  8. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/Cargo.toml +2 -2
  9. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/expr.rs +57 -68
  10. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/py_ast.rs +28 -0
  11. {snail_lang-0.7.0 → snail_lang-0.7.1}/pyproject.toml +3 -2
  12. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/__init__.py +2 -0
  13. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/cli.py +16 -5
  14. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/runtime/__init__.py +2 -1
  15. {snail_lang-0.7.0 → snail_lang-0.7.1}/LICENSE +0 -0
  16. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-ast/README.md +0 -0
  17. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-ast/src/ast.rs +0 -0
  18. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-ast/src/awk.rs +0 -0
  19. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-ast/src/lib.rs +0 -0
  20. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-error/README.md +0 -0
  21. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-error/src/lib.rs +0 -0
  22. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/README.md +0 -0
  23. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/awk.rs +0 -0
  24. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/expr.rs +0 -0
  25. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/lib.rs +0 -0
  26. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/literal.rs +0 -0
  27. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/snail.pest +0 -0
  28. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/stmt.rs +0 -0
  29. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/string.rs +0 -0
  30. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/src/util.rs +0 -0
  31. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/tests/common.rs +0 -0
  32. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/tests/errors.rs +0 -0
  33. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/tests/parser.rs +0 -0
  34. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/tests/statements.rs +0 -0
  35. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
  36. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-parser/tests/syntax_strings.rs +0 -0
  37. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/build.rs +0 -0
  38. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/compiler.rs +0 -0
  39. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lib.rs +0 -0
  40. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/linecache.rs +0 -0
  41. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/awk.rs +0 -0
  42. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/constants.rs +0 -0
  43. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/desugar.rs +0 -0
  44. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/helpers.rs +0 -0
  45. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/map.rs +0 -0
  46. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/mod.rs +0 -0
  47. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/operators.rs +0 -0
  48. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/program.rs +0 -0
  49. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/stmt.rs +0 -0
  50. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/lower/validate.rs +0 -0
  51. {snail_lang-0.7.0 → snail_lang-0.7.1}/crates/snail-python/src/profiling.rs +0 -0
  52. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/runtime/augmented.py +0 -0
  53. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/runtime/compact_try.py +0 -0
  54. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/runtime/lazy_file.py +0 -0
  55. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/runtime/lazy_text.py +0 -0
  56. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/runtime/regex.py +0 -0
  57. {snail_lang-0.7.0 → snail_lang-0.7.1}/python/snail/runtime/structured_accessor.py +0 -0
  58. {snail_lang-0.7.0 → snail_lang-0.7.1}/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.0"
315
+ version = "0.7.1"
316
316
 
317
317
  [[package]]
318
318
  name = "snail-error"
319
- version = "0.7.0"
319
+ version = "0.7.1"
320
320
  dependencies = [
321
321
  "snail-ast",
322
322
  ]
323
323
 
324
324
  [[package]]
325
325
  name = "snail-lower"
326
- version = "0.7.0"
326
+ version = "0.7.1"
327
327
  dependencies = [
328
328
  "snail-python",
329
329
  ]
330
330
 
331
331
  [[package]]
332
332
  name = "snail-parser"
333
- version = "0.7.0"
333
+ version = "0.7.1"
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.0"
343
+ version = "0.7.1"
344
344
  dependencies = [
345
345
  "pyo3",
346
346
  "snail-ast",
@@ -4,3 +4,9 @@ members = ["crates/snail-python"]
4
4
 
5
5
  [workspace.package]
6
6
  edition = "2024"
7
+
8
+ [workspace.lints.rust]
9
+ warnings = "deny"
10
+
11
+ [workspace.lints.clippy]
12
+ all = "deny"
@@ -1,13 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snail-lang
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
+ Requires-Dist: astunparse>=1.6.3 ; python_full_version < '3.9'
4
5
  Requires-Dist: jmespath>=1.0.1
5
6
  Requires-Dist: maturin>=1.5 ; extra == 'dev'
6
7
  Requires-Dist: pytest ; extra == 'dev'
7
8
  Provides-Extra: dev
8
9
  License-File: LICENSE
9
10
  Summary: Snail programming language interpreter
10
- Requires-Python: >=3.10
11
+ Requires-Python: >=3.8
11
12
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
12
13
 
13
14
  <p align="center">
@@ -26,10 +27,10 @@ way into something good, but its certainly not there yet.
26
27
 
27
28
  ## Installing Snail
28
29
 
29
- Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
30
-
31
30
  ```bash
32
- uv tool install -p 3.12 snail-lang
31
+ pip install snail-lang
32
+ -or-
33
+ uv tool install snail-lang
33
34
  ```
34
35
 
35
36
  That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
@@ -243,7 +244,7 @@ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
243
244
  ### Full Python Interoperability
244
245
 
245
246
  Snail compiles to Python AST—import any Python module, use any library, in any
246
- environment. Assuming that you are using Python 3.10 or later.
247
+ environment. Assuming that you are using Python 3.8 or later.
247
248
 
248
249
  ## 🚀 Quick Start
249
250
 
@@ -294,7 +295,7 @@ machine snail adds 5 ms of overhead above the regular python3 interpreter.
294
295
 
295
296
  ### Prerequisites
296
297
 
297
- **Python 3.10+** (required at runtime)
298
+ **Python 3.8+** (required at runtime)
298
299
 
299
300
  Snail runs in-process via a Pyo3 extension module, so it uses the active Python environment.
300
301
 
@@ -14,10 +14,10 @@ way into something good, but its certainly not there yet.
14
14
 
15
15
  ## Installing Snail
16
16
 
17
- Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
18
-
19
17
  ```bash
20
- uv tool install -p 3.12 snail-lang
18
+ pip install snail-lang
19
+ -or-
20
+ uv tool install snail-lang
21
21
  ```
22
22
 
23
23
  That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
@@ -231,7 +231,7 @@ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
231
231
  ### Full Python Interoperability
232
232
 
233
233
  Snail compiles to Python AST—import any Python module, use any library, in any
234
- environment. Assuming that you are using Python 3.10 or later.
234
+ environment. Assuming that you are using Python 3.8 or later.
235
235
 
236
236
  ## 🚀 Quick Start
237
237
 
@@ -282,7 +282,7 @@ machine snail adds 5 ms of overhead above the regular python3 interpreter.
282
282
 
283
283
  ### Prerequisites
284
284
 
285
- **Python 3.10+** (required at runtime)
285
+ **Python 3.8+** (required at runtime)
286
286
 
287
287
  Snail runs in-process via a Pyo3 extension module, so it uses the active Python environment.
288
288
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-ast"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-error"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-parser"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-python"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  edition.workspace = true
5
5
  build = "build.rs"
6
6
 
@@ -9,7 +9,7 @@ name = "snail_python"
9
9
  crate-type = ["cdylib", "rlib"]
10
10
 
11
11
  [dependencies]
12
- pyo3 = { version = "0.21", features = ["extension-module", "abi3-py310"] }
12
+ pyo3 = { version = "0.21", features = ["extension-module", "abi3-py38"] }
13
13
  snail-ast = { path = "../snail-ast" }
14
14
  snail-error = { path = "../snail-error" }
15
15
  snail-parser = { path = "../snail-parser" }
@@ -36,9 +36,7 @@ pub(crate) fn lower_assign_target(
36
36
  AssignTarget::Index { value, index, span } => {
37
37
  let value_expr = lower_expr_with_exception(builder, value, None)?;
38
38
  let index_expr = lower_expr_with_exception(builder, index, None)?;
39
- builder
40
- .call_node("Subscript", vec![value_expr, index_expr, store_ctx], span)
41
- .map_err(py_err_to_lower)
39
+ subscript_expr(builder, value_expr, index_expr, store_ctx, span)
42
40
  }
43
41
  AssignTarget::Starred { target, span } => {
44
42
  let value = lower_assign_target(builder, target)?;
@@ -101,9 +99,7 @@ pub(crate) fn lower_delete_target(
101
99
  AssignTarget::Index { value, index, span } => {
102
100
  let value_expr = lower_expr_with_exception(builder, value, None)?;
103
101
  let index_expr = lower_expr_with_exception(builder, index, None)?;
104
- builder
105
- .call_node("Subscript", vec![value_expr, index_expr, del_ctx], span)
106
- .map_err(py_err_to_lower)
102
+ subscript_expr(builder, value_expr, index_expr, del_ctx, span)
107
103
  }
108
104
  AssignTarget::Starred { .. } => Err(LowerError::new(
109
105
  "starred targets are not valid in del statements",
@@ -143,6 +139,19 @@ pub(crate) fn lower_delete_target(
143
139
  }
144
140
  }
145
141
 
142
+ fn subscript_expr(
143
+ builder: &AstBuilder<'_>,
144
+ value: PyObject,
145
+ index: PyObject,
146
+ ctx: PyObject,
147
+ span: &SourceSpan,
148
+ ) -> Result<PyObject, LowerError> {
149
+ let slice = builder.wrap_index(index, span).map_err(py_err_to_lower)?;
150
+ builder
151
+ .call_node("Subscript", vec![value, slice, ctx], span)
152
+ .map_err(py_err_to_lower)
153
+ }
154
+
146
155
  pub(crate) fn lower_regex_match(
147
156
  builder: &AstBuilder<'_>,
148
157
  value: &Expr,
@@ -450,17 +459,13 @@ pub(crate) fn lower_expr_with_exception(
450
459
  builder.load_ctx().map_err(py_err_to_lower)?,
451
460
  )?;
452
461
  let index_expr = number_expr(builder, &python_index.to_string(), span)?;
453
- builder
454
- .call_node(
455
- "Subscript",
456
- vec![
457
- value,
458
- index_expr,
459
- builder.load_ctx().map_err(py_err_to_lower)?,
460
- ],
461
- span,
462
- )
463
- .map_err(py_err_to_lower)
462
+ subscript_expr(
463
+ builder,
464
+ value,
465
+ index_expr,
466
+ builder.load_ctx().map_err(py_err_to_lower)?,
467
+ span,
468
+ )
464
469
  }
465
470
  Expr::Number { value, span } => number_expr(builder, value, span),
466
471
  Expr::String {
@@ -805,17 +810,13 @@ pub(crate) fn lower_expr_with_exception(
805
810
  span,
806
811
  )
807
812
  .map_err(py_err_to_lower)?;
808
- builder
809
- .call_node(
810
- "Subscript",
811
- vec![
812
- tuple_expr,
813
- index_expr,
814
- builder.load_ctx().map_err(py_err_to_lower)?,
815
- ],
816
- span,
817
- )
818
- .map_err(py_err_to_lower)
813
+ subscript_expr(
814
+ builder,
815
+ tuple_expr,
816
+ index_expr,
817
+ builder.load_ctx().map_err(py_err_to_lower)?,
818
+ span,
819
+ )
819
820
  }
820
821
  Expr::Regex { pattern, span } => {
821
822
  let func = name_expr(
@@ -908,17 +909,13 @@ pub(crate) fn lower_expr_with_exception(
908
909
  .parse::<i32>()
909
910
  .map_err(|_| LowerError::new(format!("Invalid match group index: .{attr}")))?;
910
911
  let index_expr = number_expr(builder, &index.to_string(), span)?;
911
- return builder
912
- .call_node(
913
- "Subscript",
914
- vec![
915
- value,
916
- index_expr,
917
- builder.load_ctx().map_err(py_err_to_lower)?,
918
- ],
919
- span,
920
- )
921
- .map_err(py_err_to_lower);
912
+ return subscript_expr(
913
+ builder,
914
+ value,
915
+ index_expr,
916
+ builder.load_ctx().map_err(py_err_to_lower)?,
917
+ span,
918
+ );
922
919
  }
923
920
  builder
924
921
  .call_node(
@@ -935,13 +932,13 @@ pub(crate) fn lower_expr_with_exception(
935
932
  Expr::Index { value, index, span } => {
936
933
  let value = lower_expr_with_exception(builder, value, exception_name)?;
937
934
  let index = lower_expr_with_exception(builder, index, exception_name)?;
938
- builder
939
- .call_node(
940
- "Subscript",
941
- vec![value, index, builder.load_ctx().map_err(py_err_to_lower)?],
942
- span,
943
- )
944
- .map_err(py_err_to_lower)
935
+ subscript_expr(
936
+ builder,
937
+ value,
938
+ index,
939
+ builder.load_ctx().map_err(py_err_to_lower)?,
940
+ span,
941
+ )
945
942
  }
946
943
  Expr::Paren { expr, .. } => lower_expr_with_exception(builder, expr, exception_name),
947
944
  Expr::List { elements, span } => {
@@ -1344,17 +1341,13 @@ fn lower_postfix_name_incr(
1344
1341
  span,
1345
1342
  )
1346
1343
  .map_err(py_err_to_lower)?;
1347
- builder
1348
- .call_node(
1349
- "Subscript",
1350
- vec![
1351
- tuple_expr,
1352
- index_expr,
1353
- builder.load_ctx().map_err(py_err_to_lower)?,
1354
- ],
1355
- span,
1356
- )
1357
- .map_err(py_err_to_lower)
1344
+ subscript_expr(
1345
+ builder,
1346
+ tuple_expr,
1347
+ index_expr,
1348
+ builder.load_ctx().map_err(py_err_to_lower)?,
1349
+ span,
1350
+ )
1358
1351
  }
1359
1352
 
1360
1353
  fn incr_delta(op: IncrOp) -> &'static str {
@@ -1506,17 +1499,13 @@ fn lower_lambda_body_expr(
1506
1499
  span,
1507
1500
  )
1508
1501
  .map_err(py_err_to_lower)?;
1509
- builder
1510
- .call_node(
1511
- "Subscript",
1512
- vec![
1513
- tuple_expr,
1514
- index_expr,
1515
- builder.load_ctx().map_err(py_err_to_lower)?,
1516
- ],
1517
- span,
1518
- )
1519
- .map_err(py_err_to_lower)
1502
+ subscript_expr(
1503
+ builder,
1504
+ tuple_expr,
1505
+ index_expr,
1506
+ builder.load_ctx().map_err(py_err_to_lower)?,
1507
+ span,
1508
+ )
1520
1509
  }
1521
1510
 
1522
1511
  fn lower_call_arguments(
@@ -6,13 +6,19 @@ use snail_error::LowerError;
6
6
  pub struct AstBuilder<'py> {
7
7
  py: Python<'py>,
8
8
  ast: Bound<'py, PyModule>,
9
+ needs_index_wrapper: bool,
9
10
  }
10
11
 
11
12
  impl<'py> AstBuilder<'py> {
12
13
  pub fn new(py: Python<'py>) -> PyResult<Self> {
14
+ let version_info = py.import_bound("sys")?.getattr("version_info")?;
15
+ let major: u8 = version_info.get_item(0)?.extract()?;
16
+ let minor: u8 = version_info.get_item(1)?.extract()?;
17
+ let needs_index_wrapper = major == 3 && minor < 9;
13
18
  Ok(Self {
14
19
  py,
15
20
  ast: py.import_bound("ast")?,
21
+ needs_index_wrapper,
16
22
  })
17
23
  }
18
24
 
@@ -65,6 +71,28 @@ impl<'py> AstBuilder<'py> {
65
71
  let node = self.ast.getattr(name)?.call1(tuple)?;
66
72
  Ok(node.into_py(self.py))
67
73
  }
74
+
75
+ pub fn wrap_index(&self, slice: PyObject, span: &SourceSpan) -> PyResult<PyObject> {
76
+ if self.needs_index_wrapper {
77
+ let slice_obj = slice.bind(self.py);
78
+ if let Ok(slice_type) = self.ast.getattr("Slice")
79
+ && slice_obj.is_instance(&slice_type)?
80
+ {
81
+ return Ok(slice);
82
+ }
83
+ if let Ok(ext_slice_type) = self.ast.getattr("ExtSlice")
84
+ && slice_obj.is_instance(&ext_slice_type)?
85
+ {
86
+ return Ok(slice);
87
+ }
88
+ if let Ok(index_type) = self.ast.getattr("Index") {
89
+ let index = index_type.call1((slice,))?;
90
+ set_location(&index, span)?;
91
+ return Ok(index.into_py(self.py));
92
+ }
93
+ }
94
+ Ok(slice)
95
+ }
68
96
  }
69
97
 
70
98
  pub fn set_location(node: &Bound<'_, PyAny>, span: &SourceSpan) -> PyResult<()> {
@@ -4,12 +4,13 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "snail-lang"
7
- version = "0.7.0"
7
+ version = "0.7.1"
8
8
  description = "Snail programming language interpreter"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.8"
11
11
  license = { file = "LICENSE" }
12
12
  dependencies = [
13
+ "astunparse>=1.6.3; python_version < '3.9'",
13
14
  "jmespath>=1.0.1",
14
15
  ]
15
16
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from ._native import __build_info__, compile, compile_ast, exec, parse, parse_ast
2
4
 
3
5
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import sys
5
+ from typing import Optional
5
6
 
6
7
  from . import __build_info__, compile_ast, exec
7
8
 
@@ -79,7 +80,11 @@ def _install_trimmed_excepthook() -> None:
79
80
  colorize = _colorize.can_colorize(file=sys.stderr)
80
81
  except Exception:
81
82
  colorize = hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
82
- for line in tb_exc.format(colorize=colorize):
83
+ try:
84
+ formatted = tb_exc.format(colorize=colorize)
85
+ except TypeError:
86
+ formatted = tb_exc.format()
87
+ for line in formatted:
83
88
  sys.stderr.write(line)
84
89
 
85
90
  sys.excepthook = _snail_excepthook
@@ -87,7 +92,7 @@ def _install_trimmed_excepthook() -> None:
87
92
 
88
93
  class _Args:
89
94
  def __init__(self) -> None:
90
- self.file: str | None = None
95
+ self.file: Optional[str] = None
91
96
  self.awk = False
92
97
  self.map = False
93
98
  self.no_print = False
@@ -252,7 +257,7 @@ def _parse_args(argv: list[str]) -> _Args:
252
257
  return args
253
258
 
254
259
 
255
- def _format_version(version: str, build_info: dict[str, object] | None) -> str:
260
+ def _format_version(version: str, build_info: Optional[dict[str, object]]) -> str:
256
261
  display_version = version if version.startswith("v") else f"v{version}"
257
262
  if not build_info:
258
263
  return display_version
@@ -277,7 +282,7 @@ def _get_version() -> str:
277
282
  return version
278
283
 
279
284
 
280
- def main(argv: list[str] | None = None) -> int:
285
+ def main(argv: Optional[list[str]] = None) -> int:
281
286
  if argv is None:
282
287
  _install_trimmed_excepthook()
283
288
  argv = sys.argv[1:]
@@ -356,7 +361,13 @@ def main(argv: list[str] | None = None) -> int:
356
361
  end_code=namespace.end_code,
357
362
  )
358
363
  builtins.compile(python_ast, _display_filename(filename), "exec")
359
- print(ast.unparse(python_ast))
364
+ try:
365
+ output = ast.unparse(python_ast)
366
+ except AttributeError:
367
+ import astunparse
368
+
369
+ output = astunparse.unparse(python_ast).rstrip("\n")
370
+ print(output)
360
371
  return 0
361
372
 
362
373
  return exec(
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
+ from typing import Optional
4
5
 
5
6
  __all__ = ["install_helpers", "AutoImportDict", "AUTO_IMPORT_NAMES"]
6
7
 
7
8
  # Names that can be auto-imported when first referenced.
8
9
  # Maps name -> (module, attribute) where attribute is None for whole-module imports.
9
- AUTO_IMPORT_NAMES: dict[str, tuple[str, str | None]] = {
10
+ AUTO_IMPORT_NAMES: dict[str, tuple[str, Optional[str]]] = {
10
11
  # Whole module imports: import X
11
12
  "sys": ("sys", None),
12
13
  "os": ("os", None),
File without changes