koatl 0.3.6__tar.gz → 0.3.8__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 (137) hide show
  1. {koatl-0.3.6 → koatl-0.3.8}/Cargo.lock +1 -1
  2. {koatl-0.3.6 → koatl-0.3.8}/PKG-INFO +1 -1
  3. {koatl-0.3.6 → koatl-0.3.8}/koatl/Cargo.toml +1 -1
  4. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/notebook/magic.py +1 -2
  5. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/coal.tl +6 -3
  6. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/ast.rs +2 -0
  7. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/inference.rs +3 -1
  8. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/lib.rs +3 -3
  9. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/lift_cst.rs +13 -1
  10. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/resolve_scopes.rs +14 -0
  11. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/transform.rs +30 -2
  12. {koatl-0.3.6 → koatl-0.3.8}/koatl-parser/src/cst.rs +7 -0
  13. {koatl-0.3.6 → koatl-0.3.8}/koatl-parser/src/lexer.rs +5 -0
  14. {koatl-0.3.6 → koatl-0.3.8}/koatl-parser/src/parser.rs +35 -2
  15. {koatl-0.3.6 → koatl-0.3.8}/koatl-parser/src/simple_fmt.rs +14 -0
  16. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/notebook/magic.py +1 -2
  17. {koatl-0.3.6 → koatl-0.3.8}/Cargo.toml +0 -0
  18. {koatl-0.3.6 → koatl-0.3.8}/README.md +0 -0
  19. {koatl-0.3.6 → koatl-0.3.8}/koatl/.github/workflows/CI.yml +0 -0
  20. {koatl-0.3.6 → koatl-0.3.8}/koatl/.gitignore +0 -0
  21. {koatl-0.3.6 → koatl-0.3.8}/koatl/LICENSE +0 -0
  22. {koatl-0.3.6 → koatl-0.3.8}/koatl/README.md +0 -0
  23. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/__init__.py +0 -0
  24. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/__main__.py +0 -0
  25. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/cli.py +0 -0
  26. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/notebook/__init__.py +0 -0
  27. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/prelude/__init__.tl +0 -0
  28. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/runtime/__init__.py +0 -0
  29. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  30. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/runtime/record.py +0 -0
  31. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/runtime/vattr.py +0 -0
  32. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/__init__.tl +0 -0
  33. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/alg/__init__.tl +0 -0
  34. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/alg/async.tl +0 -0
  35. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/alg/base.tl +0 -0
  36. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/alg/do.tl +0 -0
  37. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/alg/env.tl +0 -0
  38. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/alg/memo.tl +0 -0
  39. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/alg/result.tl +0 -0
  40. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/data/__init__.tl +0 -0
  41. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/data/list.tl +0 -0
  42. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/data/record.tl +0 -0
  43. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/ext.tl +0 -0
  44. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/io.tl +0 -0
  45. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/iter.tl +0 -0
  46. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/json.tl +0 -0
  47. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/lazy_module.tl +0 -0
  48. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/pickle.tl +0 -0
  49. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/re.tl +0 -0
  50. {koatl-0.3.6 → koatl-0.3.8}/koatl/python/koatl/std/trait.py +0 -0
  51. {koatl-0.3.6 → koatl-0.3.8}/koatl/requirements.txt +0 -0
  52. {koatl-0.3.6 → koatl-0.3.8}/koatl/src/emit_py.rs +0 -0
  53. {koatl-0.3.6 → koatl-0.3.8}/koatl/src/lib.rs +0 -0
  54. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/containers.tl +0 -0
  55. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/data.txt +0 -0
  56. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/decorators.tl +0 -0
  57. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  58. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/destructure.tl +0 -0
  59. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  60. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/fstr.tl +0 -0
  61. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/functions.tl +0 -0
  62. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/generator.tl +0 -0
  63. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/if_expr.tl +0 -0
  64. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/loops.tl +0 -0
  65. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/match.tl +0 -0
  66. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/nary-list.tl +0 -0
  67. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/placeholder.tl +0 -0
  68. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/precedence.tl +0 -0
  69. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/scopes.tl +0 -0
  70. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  71. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/short_circuit.tl +0 -0
  72. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/base/with.tl +0 -0
  73. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/async.tl +0 -0
  74. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
  75. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/env.tl +0 -0
  76. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/imports.tl +0 -0
  77. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  78. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/list.tl +0 -0
  79. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/memo.tl +0 -0
  80. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/record.tl +0 -0
  81. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/result.tl +0 -0
  82. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/slice.tl +0 -0
  83. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/try.tl +0 -0
  84. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  85. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/util/__init__.py +0 -0
  86. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/util/module0.tl +0 -0
  87. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/util/module1.tl +0 -0
  88. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/e2e/util/module2.tl +0 -0
  89. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/parse/arith.tl +0 -0
  90. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/parse/assign.tl +0 -0
  91. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/parse/block-comments.tl +0 -0
  92. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/parse/deco.tl +0 -0
  93. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/parse/func.tl +0 -0
  94. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/parse/matches.tl +0 -0
  95. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/parse/numbers.tl +0 -0
  96. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/test_e2e.py +0 -0
  97. {koatl-0.3.6 → koatl-0.3.8}/koatl/tests/test_parse.py +0 -0
  98. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/Cargo.toml +0 -0
  99. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/ast_builder.rs +0 -0
  100. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/main.rs +0 -0
  101. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/py/ast.rs +0 -0
  102. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/py/ast_builder.rs +0 -0
  103. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/py/emit.rs +0 -0
  104. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/py/mod.rs +0 -0
  105. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/types.rs +0 -0
  106. {koatl-0.3.6 → koatl-0.3.8}/koatl-core/src/util.rs +0 -0
  107. {koatl-0.3.6 → koatl-0.3.8}/koatl-parser/Cargo.toml +0 -0
  108. {koatl-0.3.6 → koatl-0.3.8}/koatl-parser/src/lib.rs +0 -0
  109. {koatl-0.3.6 → koatl-0.3.8}/pyproject.toml +0 -0
  110. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/__init__.py +0 -0
  111. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/__main__.py +0 -0
  112. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/cli.py +0 -0
  113. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/notebook/__init__.py +0 -0
  114. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/prelude/__init__.tl +0 -0
  115. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/runtime/__init__.py +0 -0
  116. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/runtime/meta_finder.py +0 -0
  117. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/runtime/record.py +0 -0
  118. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/runtime/vattr.py +0 -0
  119. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/__init__.tl +0 -0
  120. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/alg/__init__.tl +0 -0
  121. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/alg/async.tl +0 -0
  122. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/alg/base.tl +0 -0
  123. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/alg/do.tl +0 -0
  124. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/alg/env.tl +0 -0
  125. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/alg/memo.tl +0 -0
  126. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/alg/result.tl +0 -0
  127. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/data/__init__.tl +0 -0
  128. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/data/list.tl +0 -0
  129. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/data/record.tl +0 -0
  130. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/ext.tl +0 -0
  131. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/io.tl +0 -0
  132. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/iter.tl +0 -0
  133. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/json.tl +0 -0
  134. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/lazy_module.tl +0 -0
  135. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/pickle.tl +0 -0
  136. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/re.tl +0 -0
  137. {koatl-0.3.6 → koatl-0.3.8}/python/koatl/std/trait.py +0 -0
@@ -190,7 +190,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
190
190
 
191
191
  [[package]]
192
192
  name = "koatl"
193
- version = "0.3.6"
193
+ version = "0.3.8"
194
194
  dependencies = [
195
195
  "ariadne",
196
196
  "koatl-core",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koatl
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "koatl"
3
- version = "0.3.6"
3
+ version = "0.3.8"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -36,8 +36,7 @@ def _make_help_call(target, esc):
36
36
 
37
37
 
38
38
  def crepr(s):
39
- assert isinstance(s, str)
40
- return '"' + repr(s)[1:-1] + '"'
39
+ return repr(s)
41
40
 
42
41
 
43
42
  def _tr_help(content):
@@ -4,7 +4,7 @@ none = None
4
4
  assert_eq(none?(1)?(2), None)
5
5
  assert_eq(none?[1]?.a, None)
6
6
  assert_eq(none?.a, None)
7
- assert_eq(none?.(a), None)
7
+ assert_eq(none?. (a), None)
8
8
 
9
9
  obj = (class:
10
10
  a = 1
@@ -13,7 +13,7 @@ obj = (class:
13
13
  assert_eq((x => x)?(1), 1)
14
14
  assert_eq([1]?[0], 1)
15
15
  assert_eq(obj?.a, 1)
16
- assert_eq(1?.($ + 1), 2)
16
+ assert_eq(1?. ($ + 1), 2)
17
17
 
18
18
  assert_eq(None ?? 1, 1)
19
19
  assert_eq(int(5) ?? 1, 5)
@@ -25,4 +25,7 @@ f = () =>
25
25
  f() ?? 1
26
26
 
27
27
  # should only call once
28
- assert_eq(counts, 1)
28
+ assert_eq(counts, 1)
29
+
30
+ assert_eq((1).?f, None)
31
+ assert_eq(None.?f, None)
@@ -186,12 +186,14 @@ pub enum Expr<'a, TTree: Tree> {
186
186
  RawAttribute(TTree::Expr, SIdent<'a>),
187
187
  ScopedAttribute(TTree::Expr, TTree::Expr),
188
188
  Attribute(TTree::Expr, SIdent<'a>),
189
+ MaybeAttribute(TTree::Expr, SIdent<'a>),
189
190
 
190
191
  MappedCall(TTree::Expr, Vec<CallItem<'a, TTree>>),
191
192
  MappedSubscript(TTree::Expr, Vec<ListItem<TTree>>),
192
193
  MappedRawAttribute(TTree::Expr, SIdent<'a>),
193
194
  MappedScopedAttribute(TTree::Expr, TTree::Expr),
194
195
  MappedAttribute(TTree::Expr, SIdent<'a>),
196
+ MappedMaybeAttribute(TTree::Expr, SIdent<'a>),
195
197
 
196
198
  Try(TTree::Expr, Vec<MatchCase<TTree>>, Option<TTree::Expr>),
197
199
  Checked(TTree::Expr, Option<TTree::Pattern>),
@@ -229,7 +229,9 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for Indirect<SExpr<'src>> {
229
229
  Expr::RawAttribute(expr, _)
230
230
  | Expr::MappedRawAttribute(expr, _)
231
231
  | Expr::Attribute(expr, _)
232
- | Expr::MappedAttribute(expr, _) => {
232
+ | Expr::MappedAttribute(expr, _)
233
+ | Expr::MaybeAttribute(expr, _)
234
+ | Expr::MappedMaybeAttribute(expr, _) => {
233
235
  expr.traverse(ctx);
234
236
  Type::Any
235
237
  }
@@ -69,12 +69,12 @@ pub fn transpile_to_py_ast<'src>(
69
69
  ) -> TlResult<PyBlock<'src>> {
70
70
  let (tl_ast, tl_errs) = parse_tl(src);
71
71
 
72
+ let mut errs = tl_errs;
73
+
72
74
  let Some(tl_ast) = tl_ast else {
73
- return Err(tl_errs);
75
+ return Err(errs);
74
76
  };
75
77
 
76
- let mut errs = tl_errs;
77
-
78
78
  let (resolve_state, errors, tl_ast) =
79
79
  resolve_scopes::resolve_names(src, tl_ast, options.allow_await);
80
80
 
@@ -317,6 +317,18 @@ impl<'src, 'tok> Lift<Indirect<ast::SExpr<'src>>> for cst::SExpr<'src, 'tok> {
317
317
  ast::Expr::Attribute(expr.lift(), attr.lift_as_ident())
318
318
  }
319
319
  }
320
+ cst::Expr::MaybeAttribute {
321
+ expr,
322
+ attr,
323
+ question,
324
+ ..
325
+ } => {
326
+ if question.is_some() {
327
+ ast::Expr::MappedMaybeAttribute(expr.lift(), attr.lift_as_ident())
328
+ } else {
329
+ ast::Expr::MaybeAttribute(expr.lift(), attr.lift_as_ident())
330
+ }
331
+ }
320
332
  cst::Expr::Call {
321
333
  expr,
322
334
  args,
@@ -468,7 +480,7 @@ impl<'src, 'tok> Lift<Indirect<ast::SStmt<'src>>> for cst::SStmt<'src, 'tok> {
468
480
  ast::Stmt::Import(tree.lift(), export.is_some())
469
481
  }
470
482
  cst::Stmt::Error { .. } => ast::Stmt::Expr(
471
- ast::Expr::Ident(ast::Ident("error".into()).spanned(self.span))
483
+ ast::Expr::Ident(ast::Ident("_ERROR_".into()).spanned(self.span))
472
484
  .spanned(self.span)
473
485
  .indirect(),
474
486
  ),
@@ -1410,6 +1410,20 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1410
1410
 
1411
1411
  return traversed;
1412
1412
  }
1413
+ Expr::MaybeAttribute(expr, attr) => {
1414
+ Expr::MaybeAttribute(expr.traverse(state), attr.clone())
1415
+ }
1416
+ Expr::MappedMaybeAttribute(expr, attr) => {
1417
+ let traversed = Expr::MappedMaybeAttribute(expr.traverse(state), attr)
1418
+ .spanned(span)
1419
+ .indirect();
1420
+
1421
+ state
1422
+ .mapped_fninfo
1423
+ .insert(traversed.as_ref().into(), FnInfo::new());
1424
+
1425
+ return traversed;
1426
+ }
1413
1427
  Expr::RawAttribute(expr, attr) => Expr::RawAttribute(expr.traverse(state), attr),
1414
1428
  Expr::MappedRawAttribute(expr, spanned) => {
1415
1429
  let traversed = Expr::MappedRawAttribute(expr.traverse(state), spanned)
@@ -1,4 +1,3 @@
1
- use core::fmt;
2
1
  use std::{
3
2
  borrow::Cow,
4
3
  collections::{HashMap, HashSet},
@@ -1315,11 +1314,13 @@ fn transform_postfix_expr<'src, 'ast>(
1315
1314
  Expr::Call(obj, _) => (false, obj),
1316
1315
  Expr::ScopedAttribute(obj, _) => (false, obj),
1317
1316
  Expr::Attribute(obj, _) => (false, obj),
1317
+ Expr::MaybeAttribute(obj, _) => (false, obj),
1318
1318
  Expr::MappedRawAttribute(obj, _) => (true, obj),
1319
1319
  Expr::MappedSubscript(obj, _) => (true, obj),
1320
1320
  Expr::MappedCall(obj, _) => (true, obj),
1321
1321
  Expr::MappedScopedAttribute(obj, _) => (true, obj),
1322
1322
  Expr::MappedAttribute(obj, _) => (true, obj),
1323
+ Expr::MappedMaybeAttribute(obj, _) => (true, obj),
1323
1324
  _ => {
1324
1325
  return Err(simple_err(
1325
1326
  "Internal error: Postfix expressions can only be attributes, subscripts, calls, or extensions",
@@ -1365,6 +1366,29 @@ fn transform_postfix_expr<'src, 'ast>(
1365
1366
  a.attribute(lhs, rhs.value.escape(), access_ctx)
1366
1367
  }
1367
1368
  }
1369
+ Expr::MaybeAttribute(_, rhs) | Expr::MappedMaybeAttribute(_, rhs) => {
1370
+ let temp_var = ctx.create_aux_var("attr", expr.span.start);
1371
+
1372
+ inner_pre.push(a.try_(
1373
+ PyBlock(vec![a.assign(
1374
+ a.ident(temp_var.clone(), PyAccessCtx::Store),
1375
+ a.call(
1376
+ a.tl_builtin("vget"),
1377
+ vec![a.call_arg(lhs), a.call_arg(a.str(rhs.value.escape()))],
1378
+ ),
1379
+ )]),
1380
+ vec![PyExceptHandler {
1381
+ typ: None,
1382
+ name: None,
1383
+ body: PyBlock(vec![
1384
+ a.assign(a.ident(temp_var.clone(), PyAccessCtx::Store), a.none()),
1385
+ ]),
1386
+ }],
1387
+ None,
1388
+ ));
1389
+
1390
+ a.load_ident(temp_var)
1391
+ }
1368
1392
  _ => {
1369
1393
  panic!()
1370
1394
  }
@@ -1906,7 +1930,11 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for SExpr<'src> {
1906
1930
  | Expr::ScopedAttribute(..)
1907
1931
  | Expr::MappedScopedAttribute(..)
1908
1932
  | Expr::Attribute(..)
1909
- | Expr::MappedAttribute(..) => pre.bind(transform_postfix_expr(ctx, self, access_ctx)?),
1933
+ | Expr::MappedAttribute(..)
1934
+ | Expr::MaybeAttribute(..)
1935
+ | Expr::MappedMaybeAttribute(..) => {
1936
+ pre.bind(transform_postfix_expr(ctx, self, access_ctx)?)
1937
+ }
1910
1938
  Expr::With(pattern, value, body) => {
1911
1939
  let temp_var = ctx.create_aux_var("with", span.start);
1912
1940
  let value = pre.bind(value.transform(ctx)?);
@@ -465,6 +465,13 @@ pub enum Expr<TTree: Tree> {
465
465
  rhs: TTree::Expr,
466
466
  rparen: TTree::Token,
467
467
  },
468
+ MaybeAttribute {
469
+ expr: TTree::Expr,
470
+ question: Option<TTree::Token>,
471
+ dot: TTree::Token,
472
+ question2: TTree::Token,
473
+ attr: TTree::Token,
474
+ },
468
475
  Attribute {
469
476
  expr: TTree::Expr,
470
477
  question: Option<TTree::Token>,
@@ -1542,6 +1542,11 @@ impl<'src> TokenizeCtx<'src> {
1542
1542
  }
1543
1543
  };
1544
1544
 
1545
+ if delim_stack.len() > 0 {
1546
+ let (_unmatched_char, span) = delim_stack.pop().unwrap();
1547
+ return Err(LexError::custom(span, "unmatched delimiter"));
1548
+ }
1549
+
1545
1550
  let len = tokens.len();
1546
1551
  for i in (0..len).rev() {
1547
1552
  // avoid putting trivia on an eol token
@@ -1276,6 +1276,11 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
1276
1276
  rhs: SExpr<'src, 'tok>,
1277
1277
  rparen: &'tok SToken<'src>,
1278
1278
  },
1279
+ MaybeAttribute {
1280
+ question: &'tok SToken<'src>,
1281
+ dot: &'tok SToken<'src>,
1282
+ attr: &'tok SToken<'src>,
1283
+ },
1279
1284
  Attribute {
1280
1285
  dot: &'tok SToken<'src>,
1281
1286
  attr: &'tok SToken<'src>,
@@ -1341,6 +1346,15 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
1341
1346
  ctx.listing("(", ")", Token::Symbol(","), |ctx| ctx.call_item())?;
1342
1347
  Ok(Postfix::MethodCall { dot, method, args })
1343
1348
  },
1349
+ |ctx| {
1350
+ let question = ctx.symbol("?")?;
1351
+ let attr = ctx.any_ident()?;
1352
+ Ok(Postfix::MaybeAttribute {
1353
+ question,
1354
+ dot,
1355
+ attr,
1356
+ })
1357
+ },
1344
1358
  |ctx| {
1345
1359
  let attr = ctx.any_ident()?;
1346
1360
  Ok(Postfix::Attribute { dot, attr })
@@ -1411,6 +1425,17 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
1411
1425
  dot,
1412
1426
  attr,
1413
1427
  },
1428
+ Postfix::MaybeAttribute {
1429
+ question: question2,
1430
+ dot,
1431
+ attr,
1432
+ } => Expr::MaybeAttribute {
1433
+ expr: expr.boxed(),
1434
+ question,
1435
+ dot,
1436
+ question2,
1437
+ attr,
1438
+ },
1414
1439
  Postfix::Decorator { op, decorator } => {
1415
1440
  if question.is_some() {
1416
1441
  return Err(self.set_error(
@@ -2455,7 +2480,7 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
2455
2480
  }
2456
2481
  None => {
2457
2482
  stmts.push(err_stmt);
2458
- break 'outer;
2483
+ return Err(());
2459
2484
  }
2460
2485
  }
2461
2486
  self.next();
@@ -2509,7 +2534,15 @@ pub fn parse_tokens<'src: 'tok, 'tok>(
2509
2534
  .map(|err| {
2510
2535
  (
2511
2536
  true_span(err.index, err.index + 1, ctx.input),
2512
- format!("{:?}. Found: {:?}", err.message, tokens.0[err.index].token),
2537
+ format!(
2538
+ "{:?}. Found: {}",
2539
+ err.message,
2540
+ tokens
2541
+ .0
2542
+ .get(err.index)
2543
+ .map(|x| format!("{:?}", x.token))
2544
+ .unwrap_or("Eof".into())
2545
+ ),
2513
2546
  )
2514
2547
  })
2515
2548
  .collect(),
@@ -190,6 +190,20 @@ impl<'src, 'tok> SimpleFmt for SExprInner<'src, 'tok> {
190
190
  attr.simple_fmt()
191
191
  )
192
192
  }
193
+ Expr::MaybeAttribute {
194
+ expr,
195
+ question,
196
+ attr,
197
+ ..
198
+ } => {
199
+ let question_str = if question.is_some() { "?" } else { "" };
200
+ format!(
201
+ "{}{}.?{}",
202
+ expr.simple_fmt(),
203
+ question_str,
204
+ attr.simple_fmt()
205
+ )
206
+ }
193
207
  Expr::Slice {
194
208
  start,
195
209
  dots: _,
@@ -36,8 +36,7 @@ def _make_help_call(target, esc):
36
36
 
37
37
 
38
38
  def crepr(s):
39
- assert isinstance(s, str)
40
- return '"' + repr(s)[1:-1] + '"'
39
+ return repr(s)
41
40
 
42
41
 
43
42
  def _tr_help(content):
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