koatl 0.3.15__tar.gz → 0.3.17__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 (153) hide show
  1. {koatl-0.3.15 → koatl-0.3.17}/Cargo.lock +1 -1
  2. {koatl-0.3.15 → koatl-0.3.17}/PKG-INFO +1 -1
  3. {koatl-0.3.15 → koatl-0.3.17}/koatl/Cargo.toml +1 -1
  4. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/prelude/__init__.tl +5 -4
  5. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/re.tl +1 -1
  6. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/coal.tl +2 -2
  7. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/try.tl +2 -2
  8. koatl-0.3.17/koatl/tests/test_re.tl +204 -0
  9. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/util/__init__.py +6 -0
  10. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/ast.rs +1 -4
  11. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/ast_builder.rs +0 -4
  12. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/inference.rs +2 -3
  13. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/lift_cst.rs +11 -5
  14. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/resolve_scopes.rs +4 -11
  15. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/transform.rs +18 -16
  16. {koatl-0.3.15 → koatl-0.3.17}/koatl-parser/src/cst.rs +2 -3
  17. {koatl-0.3.15 → koatl-0.3.17}/koatl-parser/src/parser.rs +6 -6
  18. {koatl-0.3.15 → koatl-0.3.17}/koatl-parser/src/simple_fmt.rs +1 -1
  19. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/prelude/__init__.tl +5 -4
  20. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/re.tl +1 -1
  21. {koatl-0.3.15 → koatl-0.3.17}/Cargo.toml +0 -0
  22. {koatl-0.3.15 → koatl-0.3.17}/README.md +0 -0
  23. {koatl-0.3.15 → koatl-0.3.17}/koatl/.github/workflows/CI.yml +0 -0
  24. {koatl-0.3.15 → koatl-0.3.17}/koatl/.gitignore +0 -0
  25. {koatl-0.3.15 → koatl-0.3.17}/koatl/LICENSE +0 -0
  26. {koatl-0.3.15 → koatl-0.3.17}/koatl/README.md +0 -0
  27. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/__init__.py +0 -0
  28. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/__main__.py +0 -0
  29. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/cli.py +0 -0
  30. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/notebook/__init__.py +0 -0
  31. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/notebook/magic.py +0 -0
  32. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/runtime/__init__.py +0 -0
  33. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  34. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/runtime/record.py +0 -0
  35. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/runtime/vattr.py +0 -0
  36. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/__init__.tl +0 -0
  37. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/control/__init__.tl +0 -0
  38. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/control/async.tl +0 -0
  39. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/control/base.tl +0 -0
  40. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/control/do.tl +0 -0
  41. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/control/env.tl +0 -0
  42. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/control/memo.tl +0 -0
  43. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/control/result.tl +0 -0
  44. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/data/__init__.tl +0 -0
  45. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/data/list.tl +0 -0
  46. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/data/record.tl +0 -0
  47. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/ext.tl +0 -0
  48. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/io.tl +0 -0
  49. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/iter.tl +0 -0
  50. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/json.tl +0 -0
  51. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/lazy_module.tl +0 -0
  52. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/pickle.tl +0 -0
  53. {koatl-0.3.15 → koatl-0.3.17}/koatl/python/koatl/std/trait.py +0 -0
  54. {koatl-0.3.15 → koatl-0.3.17}/koatl/requirements.txt +0 -0
  55. {koatl-0.3.15 → koatl-0.3.17}/koatl/src/emit_py.rs +0 -0
  56. {koatl-0.3.15 → koatl-0.3.17}/koatl/src/lib.rs +0 -0
  57. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/conftest.py +0 -0
  58. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/arguments.tl +0 -0
  59. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/containers.tl +0 -0
  60. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/data.txt +0 -0
  61. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/decorators.tl +0 -0
  62. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  63. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/destructure.tl +0 -0
  64. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  65. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/fstr.tl +0 -0
  66. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/functions.tl +0 -0
  67. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/generator.tl +0 -0
  68. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/if_expr.tl +0 -0
  69. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/imports.tl +0 -0
  70. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/imports2.tl +0 -0
  71. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/loops.tl +0 -0
  72. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/match.tl +0 -0
  73. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/nary-list.tl +0 -0
  74. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/placeholder.tl +0 -0
  75. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/precedence.tl +0 -0
  76. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/scopes.tl +0 -0
  77. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  78. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/short_circuit.tl +0 -0
  79. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/base/with.tl +0 -0
  80. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/async.tl +0 -0
  81. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
  82. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/env.tl +0 -0
  83. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  84. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/list.tl +0 -0
  85. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/memo.tl +0 -0
  86. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/record.tl +0 -0
  87. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/result.tl +0 -0
  88. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/slice.tl +0 -0
  89. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  90. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/test_modules/module0.tl +0 -0
  91. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/test_modules/module1.tl +0 -0
  92. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/e2e/test_modules/module2.tl +0 -0
  93. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/arguments_mixed_defaults.tl +0 -0
  94. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/arguments_multiple_kwargs.tl +0 -0
  95. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/arguments_multiple_kwonly_markers.tl +0 -0
  96. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/arguments_multiple_posonly_markers.tl +0 -0
  97. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/arguments_multiple_varargs.tl +0 -0
  98. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/arguments_posonly_after_kwonly.tl +0 -0
  99. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/arguments_vararg_and_kwonly_marker.tl +0 -0
  100. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/fail-parse/unmatched_closing_delimiter.tl +0 -0
  101. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/arith.tl +0 -0
  102. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/assign.tl +0 -0
  103. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/block-comments.tl +0 -0
  104. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/deco.tl +0 -0
  105. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/func.tl +0 -0
  106. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/matches.tl +0 -0
  107. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/mutual-recursion.tl +0 -0
  108. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/numbers.tl +0 -0
  109. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/parse/operators.tl +0 -0
  110. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/test_e2e.py +0 -0
  111. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/test_iterable.tl +0 -0
  112. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/test_parse.py +0 -0
  113. {koatl-0.3.15 → koatl-0.3.17}/koatl/tests/test_parse_fail.py +0 -0
  114. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/Cargo.toml +0 -0
  115. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/lib.rs +0 -0
  116. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/main.rs +0 -0
  117. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/py/ast.rs +0 -0
  118. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/py/ast_builder.rs +0 -0
  119. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/py/emit.rs +0 -0
  120. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/py/mod.rs +0 -0
  121. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/types.rs +0 -0
  122. {koatl-0.3.15 → koatl-0.3.17}/koatl-core/src/util.rs +0 -0
  123. {koatl-0.3.15 → koatl-0.3.17}/koatl-parser/Cargo.toml +0 -0
  124. {koatl-0.3.15 → koatl-0.3.17}/koatl-parser/src/lexer.rs +0 -0
  125. {koatl-0.3.15 → koatl-0.3.17}/koatl-parser/src/lib.rs +0 -0
  126. {koatl-0.3.15 → koatl-0.3.17}/pyproject.toml +0 -0
  127. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/__init__.py +0 -0
  128. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/__main__.py +0 -0
  129. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/cli.py +0 -0
  130. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/notebook/__init__.py +0 -0
  131. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/notebook/magic.py +0 -0
  132. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/runtime/__init__.py +0 -0
  133. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/runtime/meta_finder.py +0 -0
  134. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/runtime/record.py +0 -0
  135. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/runtime/vattr.py +0 -0
  136. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/__init__.tl +0 -0
  137. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/control/__init__.tl +0 -0
  138. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/control/async.tl +0 -0
  139. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/control/base.tl +0 -0
  140. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/control/do.tl +0 -0
  141. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/control/env.tl +0 -0
  142. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/control/memo.tl +0 -0
  143. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/control/result.tl +0 -0
  144. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/data/__init__.tl +0 -0
  145. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/data/list.tl +0 -0
  146. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/data/record.tl +0 -0
  147. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/ext.tl +0 -0
  148. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/io.tl +0 -0
  149. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/iter.tl +0 -0
  150. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/json.tl +0 -0
  151. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/lazy_module.tl +0 -0
  152. {koatl-0.3.15 → koatl-0.3.17}/python/koatl/std/pickle.tl +0 -0
  153. {koatl-0.3.15 → koatl-0.3.17}/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.15"
193
+ version = "0.3.17"
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.15
3
+ Version: 0.3.17
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.15"
3
+ version = "0.3.17"
4
4
  edition = "2021"
5
5
  homepage = "https://koatl.org"
6
6
 
@@ -14,15 +14,14 @@ import koatl.std.(
14
14
  export std = LazyModule("koatl.std")
15
15
  export mod = RootModulesProxy()
16
16
 
17
- Extension.trait(Iterable)
18
-
19
- Extension.property(list, "len")! List.len.fget
17
+ # Extensions
20
18
 
21
- Extension.property(object, "result")! self => Result(self)
19
+ Extension.trait(Iterable)
22
20
 
23
21
  Extension.method(str, "match")! (regex, str) => Pattern(regex).match(str)
24
22
  Extension.method(str, "matches")! (str, regex) => Pattern(regex).match(str)
25
23
 
24
+ Extension.property(list, "len")! List.len.fget
26
25
  Extension.method(list, "map")! (self, f) => self.iter.map(f).list()
27
26
  Extension.method(list, "filter")! (self, f) => self.iter.filter(f).list()
28
27
 
@@ -33,6 +32,8 @@ Extension.method(dict, "filter")! (self, f) => self.iter.filter(f).dict()
33
32
  Extension.method(dict, "filter_keys")! (self, f) => self.iter.filter([k, v] => f(k)).dict()
34
33
  Extension.method(dict, "filter_values")! (self, f) => self.iter.filter([k, v] => f(v)).dict()
35
34
 
35
+ # Implementations for language features
36
+
36
37
  __tl__.do = koatl.std.control.do.do
37
38
 
38
39
  __tl__.op_map = koatl.std.control.result.op_map
@@ -20,7 +20,7 @@ export Pattern = class:
20
20
  if not isinstance(string, str):
21
21
  raise TypeError(f"Expected a string, but got {type(string).__name__}")
22
22
 
23
- self.pattern.match(string, flags).result.map(Match)
23
+ self.pattern.match(string, flags).(Result).map(Match)
24
24
 
25
25
  export Match = collections.abc.Sequence.register! class:
26
26
  __slots__ = ("_match",)
@@ -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)
@@ -24,5 +24,5 @@ except _ =>
24
24
 
25
25
  assert_eq(check 1 matches Ok(1), True)
26
26
 
27
- assert_eq((check 5)?.($+1), Ok(6))
28
- assert_eq((check x)?.($+1) matches Err(), True)
27
+ assert_eq((check 5)?.($ + 1), Ok(6))
28
+ assert_eq((check x)?.($ + 1) matches Err(), True)
@@ -0,0 +1,204 @@
1
+ import util.(assert_eq, fail)
2
+ import koatl.std.re.(Pattern, Match)
3
+ import koatl.std.control.(Ok, Err)
4
+
5
+ # Basic pattern compilation and matching
6
+ test_pattern_from_string = () =>
7
+ let pat = Pattern("hello")
8
+ assert_eq(pat.pattern.pattern, "hello")
9
+
10
+ test_pattern_from_pattern = () =>
11
+ import re
12
+ let py_pat = re.compile("test")
13
+ let pat = Pattern(py_pat)
14
+ assert_eq(pat.pattern.pattern, "test")
15
+
16
+ test_pattern_from_pattern_copy = () =>
17
+ let pat1 = Pattern("copy")
18
+ let pat2 = Pattern(pat1)
19
+ assert_eq(pat2.pattern.pattern, "copy")
20
+
21
+ # Pattern.match() tests
22
+ test_pattern_match_success = () =>
23
+ let pat = Pattern("\\d+")
24
+ let result = pat.match("123abc")
25
+ assert_eq(result.ok, True)
26
+
27
+ test_pattern_match_failure = () =>
28
+ let pat = Pattern("\\d+")
29
+ let result = pat.match("abc")
30
+ assert_eq(result.ok, False)
31
+
32
+ test_pattern_match_groups = () =>
33
+ let pat = Pattern("(\\d+)\\.(\\d+)")
34
+ if pat.match("123.456") matches Ok(m):
35
+ assert_eq(list(m), ["123", "456"])
36
+ assert_eq(m.match, "123.456")
37
+
38
+ test_pattern_match_no_groups = () =>
39
+ let pat = Pattern("hello")
40
+ if pat.match("hello world") matches Ok(m):
41
+ assert_eq(list(m), [])
42
+ assert_eq(m.match, "hello")
43
+
44
+ # String extension method tests
45
+ test_str_match_success = () =>
46
+ let result = "\\w+".match("hello")
47
+ assert_eq(result.ok, True)
48
+ if result matches Ok(m):
49
+ assert_eq(m.match, "hello")
50
+
51
+ test_str_match_failure = () =>
52
+ let result = "[a-z]+".match("123")
53
+ assert_eq(result.ok, False)
54
+
55
+ test_str_match_with_groups = () =>
56
+ let result = "(\\d{3})-(\\d{4})".match("123-4567")
57
+ if result matches Ok(m):
58
+ assert_eq(m.match, "123-4567")
59
+ assert_eq(m[1], "123")
60
+ assert_eq(m[2], "4567")
61
+ assert_eq(list(m), ["123", "4567"])
62
+
63
+ test_str_matches_alias = () =>
64
+ # matches is an alias for match
65
+ let result1 = "\\w+".match("test")
66
+ let result2 = "test".matches("\\w+")
67
+ assert_eq(result1.ok, True)
68
+ assert_eq(result2.ok, True)
69
+
70
+ # Match object tests
71
+ test_match_sequence_behavior = () =>
72
+ let pat = Pattern("(\\w+)@(\\w+)\\.(\\w+)")
73
+ if pat.match("user@example.com") matches Ok(m):
74
+ assert_eq(len(m), 3)
75
+ assert_eq(m[1], "user")
76
+ assert_eq(m[2], "example")
77
+ assert_eq(m[3], "com")
78
+
79
+ test_match_iteration = () =>
80
+ let pat = Pattern("(\\d+),(\\d+),(\\d+)")
81
+ if pat.match("10,20,30") matches Ok(m):
82
+ let groups = []
83
+ for group in m:
84
+ groups.append(group)
85
+ assert_eq(groups, ["10", "20", "30"])
86
+
87
+ test_match_full_match_property = () =>
88
+ let pat = Pattern("hello (\\w+)")
89
+ if pat.match("hello world!") matches Ok(m):
90
+ assert_eq(m.match, "hello world")
91
+ assert_eq(m[1], "world")
92
+
93
+ # Complex regex patterns
94
+ test_email_pattern = () =>
95
+ let email_pat = Pattern("([\\w.-]+)@([\\w.-]+)\\.([a-z]{2,})")
96
+ if email_pat.match("user.name@example.co.uk") matches Ok(m):
97
+ assert_eq(m[1], "user.name")
98
+ assert_eq(m[2], "example.co")
99
+ assert_eq(m[3], "uk")
100
+
101
+ test_url_pattern = () =>
102
+ let url_pat = Pattern("https?://([\\w.-]+)(?:/([\\w/.-]*))?")
103
+ if url_pat.match("https://example.com/path/to/page") matches Ok(m):
104
+ assert_eq(m[1], "example.com")
105
+ assert_eq(m[2], "path/to/page")
106
+
107
+ test_phone_pattern = () =>
108
+ let phone_pat = Pattern("\\(?(\\d{3})\\)?[-.]?(\\d{3})[-.]?(\\d{4})")
109
+ if phone_pat.match("(555) 123-4567") matches Ok(m):
110
+ assert_eq(list(m), ["555", "123", "4567"])
111
+
112
+ # Edge cases
113
+ test_empty_pattern = () =>
114
+ let pat = Pattern("")
115
+ let result = pat.match("anything")
116
+ assert_eq(result.ok, True)
117
+ if result matches Ok(m):
118
+ assert_eq(m.match, "")
119
+ assert_eq(list(m), [])
120
+
121
+ test_match_at_start_only = () =>
122
+ let pat = Pattern("\\d+")
123
+ # match() only matches at the beginning of the string
124
+ assert_eq(pat.match("123abc").ok, True)
125
+ assert_eq(pat.match("abc123").ok, False)
126
+
127
+ test_special_characters = () =>
128
+ let pat = Pattern("\\$\\d+\\.\\d{2}")
129
+ if pat.match("$19.99 total") matches Ok(m):
130
+ assert_eq(m.match, "$19.99")
131
+
132
+ test_unicode_pattern = () =>
133
+ let pat = Pattern("[α-ω]+")
134
+ if pat.match("αβγδε") matches Ok(m):
135
+ assert_eq(m.match, "αβγδε")
136
+
137
+ # Named groups aren't directly exposed in Match, but the full match object is accessible
138
+ test_multiple_groups = () =>
139
+ let pat = Pattern("(\\d{4})-(\\d{2})-(\\d{2})")
140
+ if pat.match("2025-12-13") matches Ok(m):
141
+ assert_eq(m[1], "2025")
142
+ assert_eq(m[2], "12")
143
+ assert_eq(m[3], "13")
144
+ assert_eq(len(m), 3)
145
+
146
+ # Result monad integration
147
+ test_result_map = () =>
148
+ let result = "(\\d+)".match("123").map(m => m[1])
149
+ if result matches Ok(value):
150
+ assert_eq(value, "123")
151
+
152
+ test_result_chaining = () =>
153
+ let result = "(\\d+)\\.(\\d+)".match("3.14")
154
+ .map(m => [m[1], m[2]])
155
+ .map([a, b] => [int(a), int(b)])
156
+
157
+ if result matches Ok([major, minor]):
158
+ assert_eq(major, 3)
159
+ assert_eq(minor, 14)
160
+
161
+ test_result_or_else = () =>
162
+ let result = "\\d+".match("abc")
163
+ if result matches Err(_):
164
+ assert_eq(True, True)
165
+ else:
166
+ fail()
167
+
168
+ # Pattern matching with matches expression
169
+ test_matches_expression_success = () =>
170
+ if "(\\d+)".match("123") matches Ok(m):
171
+ assert_eq(m[1], "123")
172
+ else:
173
+ fail()
174
+
175
+ test_matches_expression_failure = () =>
176
+ if "(\\d+)".match("abc") matches Ok(_):
177
+ fail()
178
+ else:
179
+ assert_eq(True, True)
180
+
181
+ # Practical examples
182
+ test_extract_version_numbers = () =>
183
+ let version_pat = Pattern("v?(\\d+)\\.(\\d+)\\.(\\d+)")
184
+ if version_pat.match("v1.2.3") matches Ok(m):
185
+ let [major, minor, patch] = list(m)
186
+ assert_eq(major, "1")
187
+ assert_eq(minor, "2")
188
+ assert_eq(patch, "3")
189
+
190
+ test_parse_csv_line = () =>
191
+ let csv_pat = Pattern("([^,]+),([^,]+),([^,]+)")
192
+ if csv_pat.match("John,Doe,30") matches Ok(m):
193
+ assert_eq(list(m), ["John", "Doe", "30"])
194
+
195
+ test_validate_hex_color = () =>
196
+ let hex_pat = Pattern("#([0-9a-fA-F]{6})")
197
+ assert_eq(hex_pat.match("#FF5733").ok, True)
198
+ assert_eq(hex_pat.match("#GG5733").ok, False)
199
+ assert_eq(hex_pat.match("FF5733").ok, False)
200
+
201
+ test_extract_quoted_string = () =>
202
+ let quote_pat = Pattern('"([^"]*)"')
203
+ if quote_pat.match('"hello world"') matches Ok(m):
204
+ assert_eq(m[1], "hello world")
@@ -3,3 +3,9 @@ def assert_eq(received, expected):
3
3
 
4
4
  def assert_contains(haystack, needle):
5
5
  assert needle in haystack, f"Expected to find {needle} in {haystack}"
6
+
7
+ def assert_true(value, message=None):
8
+ assert value is True, message or f"Expected True, but got {value}"
9
+
10
+ def fail(message):
11
+ raise AssertionError(message)
@@ -186,16 +186,15 @@ pub enum Expr<'a, TTree: Tree> {
186
186
  Call(TTree::Expr, Vec<CallItem<'a, TTree>>),
187
187
  Subscript(TTree::Expr, Vec<ListItem<TTree>>),
188
188
  RawAttribute(TTree::Expr, SIdent<'a>),
189
- ScopedAttribute(TTree::Expr, TTree::Expr),
190
189
  Attribute(TTree::Expr, SIdent<'a>),
191
190
  MaybeAttribute(TTree::Expr, SIdent<'a>),
192
191
 
193
192
  MappedCall(TTree::Expr, Vec<CallItem<'a, TTree>>),
194
193
  MappedSubscript(TTree::Expr, Vec<ListItem<TTree>>),
195
194
  MappedRawAttribute(TTree::Expr, SIdent<'a>),
196
- MappedScopedAttribute(TTree::Expr, TTree::Expr),
197
195
  MappedAttribute(TTree::Expr, SIdent<'a>),
198
196
  MappedMaybeAttribute(TTree::Expr, SIdent<'a>),
197
+ CallNullable(TTree::Expr, TTree::Expr),
199
198
 
200
199
  Try(TTree::Expr, Vec<MatchCase<TTree>>, Option<TTree::Expr>),
201
200
  Checked(TTree::Expr, Option<TTree::Pattern>),
@@ -205,8 +204,6 @@ pub enum Expr<'a, TTree: Tree> {
205
204
  Fn(Vec<ArgDefItem<'a, TTree>>, TTree::Expr),
206
205
  Fstr(Spanned<String>, Vec<(FmtExpr<TTree>, Spanned<String>)>),
207
206
 
208
- // these are removed during desugaring
209
- Decorated(TTree::Expr, TTree::Expr),
210
207
  Placeholder,
211
208
  }
212
209
 
@@ -162,10 +162,6 @@ impl AstBuilder {
162
162
  Expr::RawAttribute(value.indirect(), attr.into().spanned(self.span)).spanned(self.span)
163
163
  }
164
164
 
165
- pub fn then<'src>(&self, left: SExpr<'src>, right: SExpr<'src>) -> SExpr<'src> {
166
- Expr::ScopedAttribute(left.indirect(), right.indirect()).spanned(self.span)
167
- }
168
-
169
165
  pub fn function<'src>(&self, args: Vec<SArgDefItem<'src>>, body: SExpr<'src>) -> SExpr<'src> {
170
166
  Expr::Fn(args, body.indirect()).spanned(self.span)
171
167
  }
@@ -235,9 +235,9 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for Indirect<SExpr<'src>> {
235
235
  expr.traverse(ctx);
236
236
  Type::Any
237
237
  }
238
- Expr::ScopedAttribute(expr, other) | Expr::MappedScopedAttribute(expr, other) => {
238
+ Expr::CallNullable(expr, func) => {
239
239
  expr.traverse(ctx);
240
- other.traverse(ctx);
240
+ func.traverse(ctx);
241
241
  Type::Any
242
242
  }
243
243
  Expr::Checked(expr, _pattern) => {
@@ -265,7 +265,6 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for Indirect<SExpr<'src>> {
265
265
  Type::Any
266
266
  }
267
267
  Expr::Literal(..) | Expr::Ident(..) => Type::Any,
268
- Expr::Decorated(_, _) => panic!("Decorated expressions should not be traversed"),
269
268
  Expr::Placeholder => panic!("Placeholder expression should not be traversed"),
270
269
  };
271
270
 
@@ -274,9 +274,6 @@ impl<'src, 'tok> Lift<Indirect<ast::SExpr<'src>>> for cst::SExpr<'src, 'tok> {
274
274
  ast::Expr::Fn(args_list, body.lift())
275
275
  }
276
276
  cst::Expr::Fstr { head, parts, .. } => lift_fstr(head, parts),
277
- cst::Expr::Decorated {
278
- expr, decorator, ..
279
- } => ast::Expr::Decorated(expr.lift(), decorator.lift()),
280
277
  cst::Expr::Memo { async_kw, body, .. } => {
281
278
  ast::Expr::Memo(body.lift(), async_kw.is_some())
282
279
  }
@@ -382,11 +379,20 @@ impl<'src, 'tok> Lift<Indirect<ast::SExpr<'src>>> for cst::SExpr<'src, 'tok> {
382
379
  ..
383
380
  } => {
384
381
  if question.is_some() {
385
- ast::Expr::MappedScopedAttribute(expr.lift(), rhs.lift())
382
+ ast::Expr::CallNullable(expr.lift(), rhs.lift())
386
383
  } else {
387
- ast::Expr::ScopedAttribute(expr.lift(), rhs.lift())
384
+ let func = rhs.lift();
385
+ let arg = ast::CallItem::Arg(expr.lift());
386
+ ast::Expr::Call(func, vec![arg])
388
387
  }
389
388
  }
389
+ cst::Expr::Decorated {
390
+ expr, decorator, ..
391
+ } => {
392
+ let func = decorator.lift();
393
+ let arg = ast::CallItem::Arg(expr.lift());
394
+ ast::Expr::Call(func, vec![arg])
395
+ }
390
396
  cst::Expr::Checked { expr, pattern, .. } => {
391
397
  ast::Expr::Checked(expr.lift(), pattern.as_ref().map(|p| p.lift()))
392
398
  }
@@ -1055,10 +1055,6 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1055
1055
 
1056
1056
  Expr::Checked(expr.traverse_guarded(state), pattern)
1057
1057
  }
1058
- Expr::Decorated(deco, expr) => Expr::Call(
1059
- deco.traverse(state),
1060
- vec![CallItem::Arg(expr.traverse(state))],
1061
- ),
1062
1058
  Expr::Matches(x, pattern) => {
1063
1059
  let (pattern, meta) = pattern.traverse(state, false);
1064
1060
  if !meta.captures.is_empty() {
@@ -1446,14 +1442,14 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1446
1442
 
1447
1443
  return traversed;
1448
1444
  }
1449
- Expr::ScopedAttribute(expr, value) => {
1450
- Expr::ScopedAttribute(expr.traverse(state), value.traverse_guarded(state))
1445
+ Expr::Call(a, items) => {
1446
+ Expr::Call(a.traverse_guarded(state), traverse_call_items(state, items))
1451
1447
  }
1452
- Expr::MappedScopedAttribute(expr, value) => {
1448
+ Expr::CallNullable(expr, value) => {
1453
1449
  let (rhs, fn_ctx) =
1454
1450
  with_phantom_fninfo(state, span, |state| value.traverse_guarded(state));
1455
1451
 
1456
- let traversed = Expr::MappedScopedAttribute(expr.traverse(state), rhs)
1452
+ let traversed = Expr::CallNullable(expr.traverse(state), rhs)
1457
1453
  .spanned(span)
1458
1454
  .indirect();
1459
1455
 
@@ -1463,9 +1459,6 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1463
1459
 
1464
1460
  return traversed;
1465
1461
  }
1466
- Expr::Call(a, items) => {
1467
- Expr::Call(a.traverse(state), traverse_call_items(state, items))
1468
- }
1469
1462
  Expr::MappedCall(a, call_items) => {
1470
1463
  let (rhs, fn_ctx) = with_phantom_fninfo(state, span, |state| {
1471
1464
  traverse_call_items(state, call_items)
@@ -319,10 +319,6 @@ fn transform_assignment<'src, 'ast>(
319
319
  cur_node = left;
320
320
  decorators.push(right);
321
321
  }
322
- Expr::Decorated(deco, right) => {
323
- cur_node = right;
324
- decorators.push(deco);
325
- }
326
322
  Expr::Call(left, right) => {
327
323
  if right.len() != 1 {
328
324
  break;
@@ -1429,13 +1425,11 @@ fn transform_postfix_expr<'src, 'ast>(
1429
1425
  Expr::RawAttribute(obj, _) => (false, obj),
1430
1426
  Expr::Subscript(obj, _) => (false, obj),
1431
1427
  Expr::Call(obj, _) => (false, obj),
1432
- Expr::ScopedAttribute(obj, _) => (false, obj),
1433
1428
  Expr::Attribute(obj, _) => (false, obj),
1434
1429
  Expr::MaybeAttribute(obj, _) => (false, obj),
1435
1430
  Expr::MappedRawAttribute(obj, _) => (true, obj),
1436
1431
  Expr::MappedSubscript(obj, _) => (true, obj),
1437
1432
  Expr::MappedCall(obj, _) => (true, obj),
1438
- Expr::MappedScopedAttribute(obj, _) => (true, obj),
1439
1433
  Expr::MappedAttribute(obj, _) => (true, obj),
1440
1434
  Expr::MappedMaybeAttribute(obj, _) => (true, obj),
1441
1435
  _ => {
@@ -1466,10 +1460,6 @@ fn transform_postfix_expr<'src, 'ast>(
1466
1460
  let t = inner_pre.bind(transform_subscript_items(ctx, &list, &expr.span)?);
1467
1461
  a.subscript(lhs, t, access_ctx)
1468
1462
  }
1469
- Expr::ScopedAttribute(_, rhs) | Expr::MappedScopedAttribute(_, rhs) => {
1470
- let t = inner_pre.bind(rhs.transform(ctx)?);
1471
- a.call(t, vec![PyCallItem::Arg(lhs)])
1472
- }
1473
1463
  Expr::RawAttribute(_, attr) | Expr::MappedRawAttribute(_, attr) => {
1474
1464
  a.attribute(lhs, attr.value.escape(), access_ctx)
1475
1465
  }
@@ -2024,10 +2014,6 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for SExpr<'src> {
2024
2014
 
2025
2015
  a.load_ident(name)
2026
2016
  }
2027
- Expr::Decorated(deco, expr) => a.call(
2028
- pre.bind(deco.transform(ctx)?),
2029
- vec![PyCallItem::Arg(pre.bind(expr.transform(ctx)?))],
2030
- ),
2031
2017
  Expr::Literal(lit) => a.literal(lit.value.transform(ctx)?),
2032
2018
  Expr::Ident(_) => {
2033
2019
  let ident = ctx.py_ident(self);
@@ -2039,14 +2025,30 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for SExpr<'src> {
2039
2025
  | Expr::MappedCall(..)
2040
2026
  | Expr::Subscript(..)
2041
2027
  | Expr::MappedSubscript(..)
2042
- | Expr::ScopedAttribute(..)
2043
- | Expr::MappedScopedAttribute(..)
2044
2028
  | Expr::Attribute(..)
2045
2029
  | Expr::MappedAttribute(..)
2046
2030
  | Expr::MaybeAttribute(..)
2047
2031
  | Expr::MappedMaybeAttribute(..) => {
2048
2032
  pre.bind(transform_postfix_expr(ctx, self, access_ctx)?)
2049
2033
  }
2034
+ Expr::CallNullable(obj, func) => {
2035
+ let a = PyAstBuilder::new(span);
2036
+ let obj_transformed = pre.bind(obj.transform(ctx)?);
2037
+ let func_transformed = pre.bind(func.transform(ctx)?);
2038
+
2039
+ let maparg = ctx.create_aux_var("maparg", span.start);
2040
+
2041
+ a.call(
2042
+ a.tl_builtin("op_map"),
2043
+ vec![
2044
+ a.call_arg(obj_transformed),
2045
+ a.call_arg(a.lambda(
2046
+ PyArgList::simple_args(vec![(maparg.clone().into(), None)]),
2047
+ a.call(func_transformed, vec![a.call_arg(a.load_ident(maparg))]),
2048
+ )),
2049
+ ],
2050
+ )
2051
+ }
2050
2052
  Expr::With(pattern, value, body) => {
2051
2053
  let temp_var = ctx.create_aux_var("with", span.start);
2052
2054
  let value = pre.bind(value.transform(ctx)?);
@@ -509,11 +509,10 @@ pub enum Expr<TTree: Tree> {
509
509
  end: TTree::Token,
510
510
  },
511
511
 
512
- // these are removed during desugaring
513
512
  Decorated {
514
- expr: TTree::Expr,
515
- op: TTree::Token,
516
513
  decorator: TTree::Expr,
514
+ op: TTree::Token,
515
+ expr: TTree::Expr,
517
516
  },
518
517
  Placeholder {
519
518
  token: TTree::Token,
@@ -1289,7 +1289,7 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
1289
1289
  },
1290
1290
  Decorator {
1291
1291
  op: &'tok SToken<'src>,
1292
- decorator: SExpr<'src, 'tok>,
1292
+ decorated: SExpr<'src, 'tok>,
1293
1293
  },
1294
1294
  }
1295
1295
 
@@ -1332,8 +1332,8 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
1332
1332
 
1333
1333
  let decorator = |ctx: &mut Self| {
1334
1334
  let op = ctx.symbol("!")?;
1335
- let decorator = ctx.expr()?;
1336
- Ok(Postfix::Decorator { op, decorator })
1335
+ let decorated = ctx.expr()?;
1336
+ Ok(Postfix::Decorator { op, decorated })
1337
1337
  };
1338
1338
 
1339
1339
  let dot_attribute = |ctx: &mut Self| {
@@ -1438,7 +1438,7 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
1438
1438
  question2,
1439
1439
  attr,
1440
1440
  },
1441
- Postfix::Decorator { op, decorator } => {
1441
+ Postfix::Decorator { op, decorated } => {
1442
1442
  if question.is_some() {
1443
1443
  return Err(self.set_error(
1444
1444
  start,
@@ -1446,9 +1446,9 @@ impl<'src: 'tok, 'tok> ParseCtx<'src, 'tok> {
1446
1446
  ));
1447
1447
  }
1448
1448
  Expr::Decorated {
1449
- expr: expr.boxed(),
1449
+ expr: decorated.boxed(),
1450
1450
  op,
1451
- decorator: decorator.boxed(),
1451
+ decorator: expr.boxed(),
1452
1452
  }
1453
1453
  }
1454
1454
  }
@@ -350,7 +350,7 @@ impl<'src, 'tok> SimpleFmt for SExprInner<'src, 'tok> {
350
350
  Expr::Decorated {
351
351
  expr, decorator, ..
352
352
  } => {
353
- format!("{} & {}", expr.simple_fmt(), decorator.simple_fmt())
353
+ format!("{}! {}", expr.simple_fmt(), decorator.simple_fmt())
354
354
  }
355
355
  }
356
356
  }
@@ -14,15 +14,14 @@ import koatl.std.(
14
14
  export std = LazyModule("koatl.std")
15
15
  export mod = RootModulesProxy()
16
16
 
17
- Extension.trait(Iterable)
18
-
19
- Extension.property(list, "len")! List.len.fget
17
+ # Extensions
20
18
 
21
- Extension.property(object, "result")! self => Result(self)
19
+ Extension.trait(Iterable)
22
20
 
23
21
  Extension.method(str, "match")! (regex, str) => Pattern(regex).match(str)
24
22
  Extension.method(str, "matches")! (str, regex) => Pattern(regex).match(str)
25
23
 
24
+ Extension.property(list, "len")! List.len.fget
26
25
  Extension.method(list, "map")! (self, f) => self.iter.map(f).list()
27
26
  Extension.method(list, "filter")! (self, f) => self.iter.filter(f).list()
28
27
 
@@ -33,6 +32,8 @@ Extension.method(dict, "filter")! (self, f) => self.iter.filter(f).dict()
33
32
  Extension.method(dict, "filter_keys")! (self, f) => self.iter.filter([k, v] => f(k)).dict()
34
33
  Extension.method(dict, "filter_values")! (self, f) => self.iter.filter([k, v] => f(v)).dict()
35
34
 
35
+ # Implementations for language features
36
+
36
37
  __tl__.do = koatl.std.control.do.do
37
38
 
38
39
  __tl__.op_map = koatl.std.control.result.op_map
@@ -20,7 +20,7 @@ export Pattern = class:
20
20
  if not isinstance(string, str):
21
21
  raise TypeError(f"Expected a string, but got {type(string).__name__}")
22
22
 
23
- self.pattern.match(string, flags).result.map(Match)
23
+ self.pattern.match(string, flags).(Result).map(Match)
24
24
 
25
25
  export Match = collections.abc.Sequence.register! class:
26
26
  __slots__ = ("_match",)
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