koatl 0.3.7__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.7 → koatl-0.3.8}/Cargo.lock +1 -1
  2. {koatl-0.3.7 → koatl-0.3.8}/PKG-INFO +1 -1
  3. {koatl-0.3.7 → koatl-0.3.8}/koatl/Cargo.toml +1 -1
  4. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/notebook/magic.py +1 -2
  5. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/coal.tl +6 -3
  6. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/ast.rs +2 -0
  7. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/inference.rs +3 -1
  8. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/lift_cst.rs +12 -0
  9. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/resolve_scopes.rs +14 -0
  10. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/transform.rs +30 -1
  11. {koatl-0.3.7 → koatl-0.3.8}/koatl-parser/src/cst.rs +7 -0
  12. {koatl-0.3.7 → koatl-0.3.8}/koatl-parser/src/parser.rs +25 -0
  13. {koatl-0.3.7 → koatl-0.3.8}/koatl-parser/src/simple_fmt.rs +14 -0
  14. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/notebook/magic.py +1 -2
  15. {koatl-0.3.7 → koatl-0.3.8}/Cargo.toml +0 -0
  16. {koatl-0.3.7 → koatl-0.3.8}/README.md +0 -0
  17. {koatl-0.3.7 → koatl-0.3.8}/koatl/.github/workflows/CI.yml +0 -0
  18. {koatl-0.3.7 → koatl-0.3.8}/koatl/.gitignore +0 -0
  19. {koatl-0.3.7 → koatl-0.3.8}/koatl/LICENSE +0 -0
  20. {koatl-0.3.7 → koatl-0.3.8}/koatl/README.md +0 -0
  21. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/__init__.py +0 -0
  22. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/__main__.py +0 -0
  23. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/cli.py +0 -0
  24. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/notebook/__init__.py +0 -0
  25. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/prelude/__init__.tl +0 -0
  26. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/runtime/__init__.py +0 -0
  27. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  28. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/runtime/record.py +0 -0
  29. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/runtime/vattr.py +0 -0
  30. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/__init__.tl +0 -0
  31. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/alg/__init__.tl +0 -0
  32. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/alg/async.tl +0 -0
  33. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/alg/base.tl +0 -0
  34. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/alg/do.tl +0 -0
  35. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/alg/env.tl +0 -0
  36. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/alg/memo.tl +0 -0
  37. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/alg/result.tl +0 -0
  38. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/data/__init__.tl +0 -0
  39. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/data/list.tl +0 -0
  40. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/data/record.tl +0 -0
  41. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/ext.tl +0 -0
  42. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/io.tl +0 -0
  43. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/iter.tl +0 -0
  44. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/json.tl +0 -0
  45. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/lazy_module.tl +0 -0
  46. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/pickle.tl +0 -0
  47. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/re.tl +0 -0
  48. {koatl-0.3.7 → koatl-0.3.8}/koatl/python/koatl/std/trait.py +0 -0
  49. {koatl-0.3.7 → koatl-0.3.8}/koatl/requirements.txt +0 -0
  50. {koatl-0.3.7 → koatl-0.3.8}/koatl/src/emit_py.rs +0 -0
  51. {koatl-0.3.7 → koatl-0.3.8}/koatl/src/lib.rs +0 -0
  52. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/containers.tl +0 -0
  53. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/data.txt +0 -0
  54. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/decorators.tl +0 -0
  55. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  56. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/destructure.tl +0 -0
  57. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  58. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/fstr.tl +0 -0
  59. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/functions.tl +0 -0
  60. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/generator.tl +0 -0
  61. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/if_expr.tl +0 -0
  62. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/loops.tl +0 -0
  63. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/match.tl +0 -0
  64. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/nary-list.tl +0 -0
  65. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/placeholder.tl +0 -0
  66. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/precedence.tl +0 -0
  67. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/scopes.tl +0 -0
  68. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  69. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/short_circuit.tl +0 -0
  70. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/base/with.tl +0 -0
  71. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/async.tl +0 -0
  72. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
  73. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/env.tl +0 -0
  74. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/imports.tl +0 -0
  75. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  76. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/list.tl +0 -0
  77. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/memo.tl +0 -0
  78. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/record.tl +0 -0
  79. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/result.tl +0 -0
  80. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/slice.tl +0 -0
  81. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/try.tl +0 -0
  82. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  83. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/util/__init__.py +0 -0
  84. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/util/module0.tl +0 -0
  85. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/util/module1.tl +0 -0
  86. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/e2e/util/module2.tl +0 -0
  87. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/parse/arith.tl +0 -0
  88. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/parse/assign.tl +0 -0
  89. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/parse/block-comments.tl +0 -0
  90. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/parse/deco.tl +0 -0
  91. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/parse/func.tl +0 -0
  92. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/parse/matches.tl +0 -0
  93. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/parse/numbers.tl +0 -0
  94. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/test_e2e.py +0 -0
  95. {koatl-0.3.7 → koatl-0.3.8}/koatl/tests/test_parse.py +0 -0
  96. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/Cargo.toml +0 -0
  97. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/ast_builder.rs +0 -0
  98. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/lib.rs +0 -0
  99. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/main.rs +0 -0
  100. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/py/ast.rs +0 -0
  101. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/py/ast_builder.rs +0 -0
  102. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/py/emit.rs +0 -0
  103. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/py/mod.rs +0 -0
  104. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/types.rs +0 -0
  105. {koatl-0.3.7 → koatl-0.3.8}/koatl-core/src/util.rs +0 -0
  106. {koatl-0.3.7 → koatl-0.3.8}/koatl-parser/Cargo.toml +0 -0
  107. {koatl-0.3.7 → koatl-0.3.8}/koatl-parser/src/lexer.rs +0 -0
  108. {koatl-0.3.7 → koatl-0.3.8}/koatl-parser/src/lib.rs +0 -0
  109. {koatl-0.3.7 → koatl-0.3.8}/pyproject.toml +0 -0
  110. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/__init__.py +0 -0
  111. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/__main__.py +0 -0
  112. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/cli.py +0 -0
  113. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/notebook/__init__.py +0 -0
  114. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/prelude/__init__.tl +0 -0
  115. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/runtime/__init__.py +0 -0
  116. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/runtime/meta_finder.py +0 -0
  117. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/runtime/record.py +0 -0
  118. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/runtime/vattr.py +0 -0
  119. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/__init__.tl +0 -0
  120. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/alg/__init__.tl +0 -0
  121. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/alg/async.tl +0 -0
  122. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/alg/base.tl +0 -0
  123. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/alg/do.tl +0 -0
  124. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/alg/env.tl +0 -0
  125. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/alg/memo.tl +0 -0
  126. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/alg/result.tl +0 -0
  127. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/data/__init__.tl +0 -0
  128. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/data/list.tl +0 -0
  129. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/data/record.tl +0 -0
  130. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/ext.tl +0 -0
  131. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/io.tl +0 -0
  132. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/iter.tl +0 -0
  133. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/json.tl +0 -0
  134. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/lazy_module.tl +0 -0
  135. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/pickle.tl +0 -0
  136. {koatl-0.3.7 → koatl-0.3.8}/python/koatl/std/re.tl +0 -0
  137. {koatl-0.3.7 → 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.7"
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.7
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.7"
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
  }
@@ -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,
@@ -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)
@@ -1314,11 +1314,13 @@ fn transform_postfix_expr<'src, 'ast>(
1314
1314
  Expr::Call(obj, _) => (false, obj),
1315
1315
  Expr::ScopedAttribute(obj, _) => (false, obj),
1316
1316
  Expr::Attribute(obj, _) => (false, obj),
1317
+ Expr::MaybeAttribute(obj, _) => (false, obj),
1317
1318
  Expr::MappedRawAttribute(obj, _) => (true, obj),
1318
1319
  Expr::MappedSubscript(obj, _) => (true, obj),
1319
1320
  Expr::MappedCall(obj, _) => (true, obj),
1320
1321
  Expr::MappedScopedAttribute(obj, _) => (true, obj),
1321
1322
  Expr::MappedAttribute(obj, _) => (true, obj),
1323
+ Expr::MappedMaybeAttribute(obj, _) => (true, obj),
1322
1324
  _ => {
1323
1325
  return Err(simple_err(
1324
1326
  "Internal error: Postfix expressions can only be attributes, subscripts, calls, or extensions",
@@ -1364,6 +1366,29 @@ fn transform_postfix_expr<'src, 'ast>(
1364
1366
  a.attribute(lhs, rhs.value.escape(), access_ctx)
1365
1367
  }
1366
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
+ }
1367
1392
  _ => {
1368
1393
  panic!()
1369
1394
  }
@@ -1905,7 +1930,11 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for SExpr<'src> {
1905
1930
  | Expr::ScopedAttribute(..)
1906
1931
  | Expr::MappedScopedAttribute(..)
1907
1932
  | Expr::Attribute(..)
1908
- | 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
+ }
1909
1938
  Expr::With(pattern, value, body) => {
1910
1939
  let temp_var = ctx.create_aux_var("with", span.start);
1911
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>,
@@ -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(
@@ -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
File without changes
File without changes