koatl 0.3.13__tar.gz → 0.3.15__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 (155) hide show
  1. {koatl-0.3.13 → koatl-0.3.15}/Cargo.lock +2 -2
  2. {koatl-0.3.13 → koatl-0.3.15}/PKG-INFO +2 -1
  3. {koatl-0.3.13 → koatl-0.3.15}/koatl/Cargo.toml +2 -1
  4. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/prelude/__init__.tl +10 -0
  5. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/runtime/record.py +44 -5
  6. koatl-0.3.15/koatl/python/koatl/runtime/vattr.py +66 -0
  7. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/iter.tl +6 -4
  8. {koatl-0.3.13 → koatl-0.3.15}/koatl/src/emit_py.rs +6 -0
  9. koatl-0.3.15/koatl/tests/conftest.py +62 -0
  10. {koatl-0.3.13/koatl/tests/e2e/prelude → koatl-0.3.15/koatl/tests/e2e/base}/imports.tl +3 -3
  11. koatl-0.3.15/koatl/tests/e2e/base/imports2.tl +15 -0
  12. koatl-0.3.15/koatl/tests/fail-parse/unmatched_closing_delimiter.tl +1 -0
  13. koatl-0.3.15/koatl/tests/parse/operators.tl +1 -0
  14. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/test_e2e.py +1 -1
  15. koatl-0.3.15/koatl/tests/test_iterable.tl +176 -0
  16. koatl-0.3.15/koatl/tests/util/__init__.py +5 -0
  17. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/py/ast.rs +1 -1
  18. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/transform.rs +10 -15
  19. {koatl-0.3.13 → koatl-0.3.15}/koatl-parser/src/lexer.rs +16 -7
  20. {koatl-0.3.13 → koatl-0.3.15}/koatl-parser/src/parser.rs +24 -2
  21. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/prelude/__init__.tl +10 -0
  22. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/runtime/record.py +44 -5
  23. koatl-0.3.15/python/koatl/runtime/vattr.py +66 -0
  24. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/iter.tl +6 -4
  25. koatl-0.3.13/koatl/python/koatl/runtime/vattr.py +0 -67
  26. koatl-0.3.13/koatl/tests/e2e/util/__init__.py +0 -2
  27. koatl-0.3.13/python/koatl/runtime/vattr.py +0 -67
  28. {koatl-0.3.13 → koatl-0.3.15}/Cargo.toml +0 -0
  29. {koatl-0.3.13 → koatl-0.3.15}/README.md +0 -0
  30. {koatl-0.3.13 → koatl-0.3.15}/koatl/.github/workflows/CI.yml +0 -0
  31. {koatl-0.3.13 → koatl-0.3.15}/koatl/.gitignore +0 -0
  32. {koatl-0.3.13 → koatl-0.3.15}/koatl/LICENSE +0 -0
  33. {koatl-0.3.13 → koatl-0.3.15}/koatl/README.md +0 -0
  34. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/__init__.py +0 -0
  35. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/__main__.py +0 -0
  36. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/cli.py +0 -0
  37. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/notebook/__init__.py +0 -0
  38. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/notebook/magic.py +0 -0
  39. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/runtime/__init__.py +0 -0
  40. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  41. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/__init__.tl +0 -0
  42. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/control/__init__.tl +0 -0
  43. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/control/async.tl +0 -0
  44. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/control/base.tl +0 -0
  45. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/control/do.tl +0 -0
  46. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/control/env.tl +0 -0
  47. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/control/memo.tl +0 -0
  48. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/control/result.tl +0 -0
  49. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/data/__init__.tl +0 -0
  50. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/data/list.tl +0 -0
  51. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/data/record.tl +0 -0
  52. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/ext.tl +0 -0
  53. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/io.tl +0 -0
  54. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/json.tl +0 -0
  55. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/lazy_module.tl +0 -0
  56. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/pickle.tl +0 -0
  57. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/re.tl +0 -0
  58. {koatl-0.3.13 → koatl-0.3.15}/koatl/python/koatl/std/trait.py +0 -0
  59. {koatl-0.3.13 → koatl-0.3.15}/koatl/requirements.txt +0 -0
  60. {koatl-0.3.13 → koatl-0.3.15}/koatl/src/lib.rs +0 -0
  61. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/arguments.tl +0 -0
  62. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/containers.tl +0 -0
  63. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/data.txt +0 -0
  64. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/decorators.tl +0 -0
  65. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  66. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/destructure.tl +0 -0
  67. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  68. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/fstr.tl +0 -0
  69. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/functions.tl +0 -0
  70. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/generator.tl +0 -0
  71. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/if_expr.tl +0 -0
  72. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/loops.tl +0 -0
  73. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/match.tl +0 -0
  74. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/nary-list.tl +0 -0
  75. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/placeholder.tl +0 -0
  76. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/precedence.tl +0 -0
  77. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/scopes.tl +0 -0
  78. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  79. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/short_circuit.tl +0 -0
  80. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/base/with.tl +0 -0
  81. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/async.tl +0 -0
  82. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
  83. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/coal.tl +0 -0
  84. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/env.tl +0 -0
  85. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  86. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/list.tl +0 -0
  87. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/memo.tl +0 -0
  88. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/record.tl +0 -0
  89. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/result.tl +0 -0
  90. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/slice.tl +0 -0
  91. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/try.tl +0 -0
  92. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  93. {koatl-0.3.13/koatl/tests/e2e/util → koatl-0.3.15/koatl/tests/e2e/test_modules}/module0.tl +0 -0
  94. {koatl-0.3.13/koatl/tests/e2e/util → koatl-0.3.15/koatl/tests/e2e/test_modules}/module1.tl +0 -0
  95. {koatl-0.3.13/koatl/tests/e2e/util → koatl-0.3.15/koatl/tests/e2e/test_modules}/module2.tl +0 -0
  96. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/fail-parse/arguments_mixed_defaults.tl +0 -0
  97. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/fail-parse/arguments_multiple_kwargs.tl +0 -0
  98. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/fail-parse/arguments_multiple_kwonly_markers.tl +0 -0
  99. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/fail-parse/arguments_multiple_posonly_markers.tl +0 -0
  100. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/fail-parse/arguments_multiple_varargs.tl +0 -0
  101. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/fail-parse/arguments_posonly_after_kwonly.tl +0 -0
  102. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/fail-parse/arguments_vararg_and_kwonly_marker.tl +0 -0
  103. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/arith.tl +0 -0
  104. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/assign.tl +0 -0
  105. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/block-comments.tl +0 -0
  106. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/deco.tl +0 -0
  107. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/func.tl +0 -0
  108. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/matches.tl +0 -0
  109. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/mutual-recursion.tl +0 -0
  110. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/parse/numbers.tl +0 -0
  111. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/test_parse.py +0 -0
  112. {koatl-0.3.13 → koatl-0.3.15}/koatl/tests/test_parse_fail.py +0 -0
  113. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/Cargo.toml +0 -0
  114. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/ast.rs +0 -0
  115. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/ast_builder.rs +0 -0
  116. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/inference.rs +0 -0
  117. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/lib.rs +0 -0
  118. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/lift_cst.rs +0 -0
  119. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/main.rs +0 -0
  120. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/py/ast_builder.rs +0 -0
  121. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/py/emit.rs +0 -0
  122. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/py/mod.rs +0 -0
  123. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/resolve_scopes.rs +0 -0
  124. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/types.rs +0 -0
  125. {koatl-0.3.13 → koatl-0.3.15}/koatl-core/src/util.rs +0 -0
  126. {koatl-0.3.13 → koatl-0.3.15}/koatl-parser/Cargo.toml +0 -0
  127. {koatl-0.3.13 → koatl-0.3.15}/koatl-parser/src/cst.rs +0 -0
  128. {koatl-0.3.13 → koatl-0.3.15}/koatl-parser/src/lib.rs +0 -0
  129. {koatl-0.3.13 → koatl-0.3.15}/koatl-parser/src/simple_fmt.rs +0 -0
  130. {koatl-0.3.13 → koatl-0.3.15}/pyproject.toml +0 -0
  131. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/__init__.py +0 -0
  132. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/__main__.py +0 -0
  133. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/cli.py +0 -0
  134. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/notebook/__init__.py +0 -0
  135. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/notebook/magic.py +0 -0
  136. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/runtime/__init__.py +0 -0
  137. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/runtime/meta_finder.py +0 -0
  138. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/__init__.tl +0 -0
  139. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/control/__init__.tl +0 -0
  140. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/control/async.tl +0 -0
  141. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/control/base.tl +0 -0
  142. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/control/do.tl +0 -0
  143. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/control/env.tl +0 -0
  144. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/control/memo.tl +0 -0
  145. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/control/result.tl +0 -0
  146. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/data/__init__.tl +0 -0
  147. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/data/list.tl +0 -0
  148. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/data/record.tl +0 -0
  149. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/ext.tl +0 -0
  150. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/io.tl +0 -0
  151. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/json.tl +0 -0
  152. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/lazy_module.tl +0 -0
  153. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/pickle.tl +0 -0
  154. {koatl-0.3.13 → koatl-0.3.15}/python/koatl/std/re.tl +0 -0
  155. {koatl-0.3.13 → koatl-0.3.15}/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.13"
193
+ version = "0.3.15"
194
194
  dependencies = [
195
195
  "ariadne",
196
196
  "koatl-core",
@@ -248,7 +248,7 @@ dependencies = [
248
248
 
249
249
  [[package]]
250
250
  name = "ohtli"
251
- version = "0.1.1"
251
+ version = "0.1.2"
252
252
  dependencies = [
253
253
  "anyhow",
254
254
  "clap",
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koatl
3
- Version: 0.3.13
3
+ Version: 0.3.15
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
7
7
  License-File: LICENSE
8
+ Home-Page: https://koatl.org
8
9
  Requires-Python: >=3.8
9
10
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
10
11
 
@@ -1,7 +1,8 @@
1
1
  [package]
2
2
  name = "koatl"
3
- version = "0.3.13"
3
+ version = "0.3.15"
4
4
  edition = "2021"
5
+ homepage = "https://koatl.org"
5
6
 
6
7
  [lib]
7
8
  name = "koatl"
@@ -23,6 +23,16 @@ Extension.property(object, "result")! self => Result(self)
23
23
  Extension.method(str, "match")! (regex, str) => Pattern(regex).match(str)
24
24
  Extension.method(str, "matches")! (str, regex) => Pattern(regex).match(str)
25
25
 
26
+ Extension.method(list, "map")! (self, f) => self.iter.map(f).list()
27
+ Extension.method(list, "filter")! (self, f) => self.iter.filter(f).list()
28
+
29
+ Extension.method(dict, "map")! (self, f) => self.iter.map(f).dict()
30
+ Extension.method(dict, "map_values")! (self, f) => self.iter.map([k, v] => [k, f(v)]).dict()
31
+ Extension.method(dict, "map_keys")! (self, f) => self.iter.map([k, v] => [f(k), v]).dict()
32
+ Extension.method(dict, "filter")! (self, f) => self.iter.filter(f).dict()
33
+ Extension.method(dict, "filter_keys")! (self, f) => self.iter.filter([k, v] => f(k)).dict()
34
+ Extension.method(dict, "filter_values")! (self, f) => self.iter.filter([k, v] => f(v)).dict()
35
+
26
36
  __tl__.do = koatl.std.control.do.do
27
37
 
28
38
  __tl__.op_map = koatl.std.control.result.op_map
@@ -4,8 +4,6 @@ import functools
4
4
  import inspect
5
5
  import re
6
6
 
7
- _property = property
8
-
9
7
  __all__ = ["Record"]
10
8
 
11
9
 
@@ -48,9 +46,50 @@ class Record:
48
46
 
49
47
  return object.__getattribute__(self, name)
50
48
 
51
- @_property
49
+ def map(self, f):
50
+ newrec = Record()
51
+ for k, v in self.items():
52
+ nk, nv = f((k, v))
53
+ newrec[nk] = nv
54
+ return newrec
55
+
56
+ def map_values(self, f):
57
+ newrec = Record()
58
+ for k, v in self.items():
59
+ newrec[k] = f(v)
60
+ return newrec
61
+
62
+ def map_keys(self, f):
63
+ newrec = Record()
64
+ for k, v in self.items():
65
+ nk = f(k)
66
+ newrec[nk] = v
67
+ return newrec
68
+
69
+ def filter(self, f):
70
+ newrec = Record()
71
+ for k, v in self.items():
72
+ if f((k, v)):
73
+ newrec[k] = v
74
+ return newrec
75
+
76
+ def filter_values(self, f):
77
+ newrec = Record()
78
+ for k, v in self.items():
79
+ if f(v):
80
+ newrec[k] = v
81
+ return newrec
82
+
83
+ def filter_keys(self, f):
84
+ newrec = Record()
85
+ for k, v in self.items():
86
+ if f(k):
87
+ newrec[k] = v
88
+ return newrec
89
+
90
+ @__builtins__["property"]
52
91
  def iter(self):
53
- return self.items()
92
+ return iter(self.items())
54
93
 
55
94
  # MutableMapping
56
95
  def __len__(self):
@@ -130,7 +169,7 @@ class Record:
130
169
  c.update(self)
131
170
  return c
132
171
 
133
- @_property
172
+ @__builtins__["property"]
134
173
  def len(self):
135
174
  return len(self)
136
175
 
@@ -0,0 +1,66 @@
1
+ from functools import partial
2
+ from itertools import count
3
+ from koatl._rs import fast_vget
4
+
5
+
6
+ def vget(obj, name, ignore_traits=False):
7
+ try:
8
+ return getattr(obj, name)
9
+ except AttributeError as e:
10
+ # special case for iter - this could be implemented using types and trait vtbls
11
+ # but this is simpler and probably faster
12
+ if name == "iter":
13
+ if isinstance(obj, slice):
14
+ start = obj.start if obj.start is not None else 0
15
+ step = obj.step if obj.step is not None else 1
16
+ if obj.stop is None:
17
+ return iter(count(start, step))
18
+ else:
19
+ return iter(range(start, obj.stop, step))
20
+
21
+ try:
22
+ return iter(obj.items())
23
+ except AttributeError:
24
+ pass
25
+
26
+ try:
27
+ return iter(obj)
28
+ except TypeError:
29
+ pass
30
+
31
+ v = fast_vget(obj, name, ignore_traits)
32
+ if v is not None:
33
+ if hasattr(v, "_property"):
34
+ return v(obj)
35
+
36
+ return partial(v, obj)
37
+
38
+ e.__traceback__ = None
39
+ raise
40
+
41
+
42
+ def vhas(obj, name, ignore_traits=False):
43
+ if hasattr(obj, name):
44
+ return True
45
+
46
+ if name == "iter":
47
+ if isinstance(obj, slice):
48
+ return True
49
+
50
+ try:
51
+ obj.items()
52
+ return True
53
+ except AttributeError:
54
+ pass
55
+
56
+ try:
57
+ iter(obj)
58
+ return True
59
+ except TypeError:
60
+ pass
61
+
62
+ v = fast_vget(obj, name, ignore_traits)
63
+ if v is not None:
64
+ return True
65
+
66
+ return False
@@ -10,7 +10,7 @@ export Iterable = class(Traversable, Trait):
10
10
 
11
11
  skip = (self, n) =>
12
12
  let it = self.iter
13
- for _ in..n:
13
+ for _ in ..n:
14
14
  next(it, None)
15
15
  it
16
16
 
@@ -27,7 +27,7 @@ export Iterable = class(Traversable, Trait):
27
27
  take = (self, n) =>
28
28
  let impl = () =>
29
29
  let it = self.iter
30
- for _ in..n:
30
+ for _ in ..n:
31
31
  try:
32
32
  yield next(it)
33
33
  except StopIteration(value=value) =>
@@ -56,6 +56,8 @@ export Iterable = class(Traversable, Trait):
56
56
  product = (self, *others) =>
57
57
  itertools.product(self.iter, *others.map($.iter))
58
58
 
59
+ cycle = self => itertools.cycle(self.iter)
60
+
59
61
  zip = (self, *others) =>
60
62
  zip(self.iter, *others.map($.iter))
61
63
 
@@ -86,7 +88,7 @@ export Iterable = class(Traversable, Trait):
86
88
 
87
89
  reverse = self => reversed(list(self.iter))
88
90
 
89
- sorted = (self, key=None, reverse=False) =>
91
+ sort = (self, key=None, reverse=False) =>
90
92
  sorted(self.iter, key=key, reverse=reverse)
91
93
 
92
94
  copy = self => itertools.tee(self.iter)
@@ -184,7 +186,7 @@ export Iterable = class(Traversable, Trait):
184
186
 
185
187
  impl()
186
188
 
187
- join_str = (self, sep="") =>
189
+ join = (self, sep="") =>
188
190
  sep.join(self.map(str))
189
191
 
190
192
  sum = self =>
@@ -631,6 +631,12 @@ impl<'src> PyExprExt<'src> for SPyExpr<'src> {
631
631
  PyBinaryOp::FloorDiv => Some("FloorDiv"),
632
632
  PyBinaryOp::Mod => Some("Mod"),
633
633
  PyBinaryOp::Pow => Some("Pow"),
634
+ PyBinaryOp::BitAnd => Some("BitAnd"),
635
+ PyBinaryOp::BitOr => Some("BitOr"),
636
+ PyBinaryOp::BitXor => Some("BitXor"),
637
+ PyBinaryOp::MatMult => Some("MatMult"),
638
+ PyBinaryOp::LShift => Some("LShift"),
639
+ PyBinaryOp::RShift => Some("RShift"),
634
640
  _ => None,
635
641
  };
636
642
 
@@ -0,0 +1,62 @@
1
+ """
2
+ Pytest configuration for Koatl tests.
3
+ This allows pytest to discover and execute .tl files as test modules.
4
+ """
5
+ import pytest
6
+ import koatl
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ # Add e2e directory to Python path so util module can be imported
11
+ sys.path.insert(0, str(Path(__file__).parent / "e2e"))
12
+
13
+
14
+ def pytest_collect_file(parent, file_path):
15
+ """
16
+ Custom collector to discover .tl files as test modules.
17
+ """
18
+ if file_path.suffix == ".tl" and file_path.name.startswith("test_"):
19
+ return TlModule.from_parent(parent, path=file_path)
20
+
21
+
22
+ class TlModule(pytest.Module):
23
+ """
24
+ Custom pytest Module for .tl files.
25
+ Transpiles the .tl file and collects test functions from it.
26
+ """
27
+
28
+ def collect(self):
29
+ """
30
+ Transpile the .tl file and collect test functions.
31
+ """
32
+ with open(self.path, "r") as f:
33
+ source = f.read()
34
+
35
+ try:
36
+ # Transpile to Python AST in module mode
37
+ py_ast = koatl.transpile(source, mode="module")
38
+
39
+ # Create a namespace for the module
40
+ module_name = self.path.stem
41
+ module_namespace = {
42
+ "__name__": module_name,
43
+ "__file__": str(self.path),
44
+ }
45
+
46
+ # Compile and execute the Python AST in the namespace
47
+ # This defines all test_* functions
48
+ code_obj = compile(py_ast, str(self.path), "exec")
49
+ exec(code_obj, module_namespace)
50
+
51
+ # Collect test functions from the namespace
52
+ for name, obj in module_namespace.items():
53
+ if name.startswith("test_") and callable(obj):
54
+ func = pytest.Function.from_parent(
55
+ self,
56
+ name=name
57
+ )
58
+ func.obj = obj
59
+ yield func
60
+ except Exception as e:
61
+ # If transpilation fails, create an error item
62
+ raise self.CollectError(f"Error transpiling {self.path}: {e}") from e
@@ -11,18 +11,18 @@ import os.*
11
11
 
12
12
  assert_eq(getcwd, os.getcwd)
13
13
 
14
- import util.module0
14
+ import e2e.test_modules.module0
15
15
 
16
16
  assert_eq(set(module0.__all__), set(["z"]))
17
17
  assert_eq(module0.z, 3)
18
18
 
19
- import util.module1
19
+ import e2e.test_modules.module1
20
20
 
21
21
  assert_eq(set(module1.__all__), set(["a", "z"]))
22
22
  assert_eq(module1.a, 1)
23
23
  assert_eq(module1.z, 3)
24
24
 
25
- import util.module2
25
+ import e2e.test_modules.module2
26
26
 
27
27
  assert_eq(set(module2.__all__), set(["b"]))
28
28
  assert_eq(module2.b, 3)
@@ -0,0 +1,15 @@
1
+ import ast
2
+ import koatl
3
+ import util.assert_contains
4
+
5
+ # Test: import a.b should emit "from a import b"
6
+ parsed = koatl.transpile("import a.b", mode="no_prelude")
7
+ assert_contains(ast.unparse(parsed), "from a import b")
8
+
9
+ # Test: import a.b.(.) should emit "import a.b as b"
10
+ parsed = koatl.transpile("import a.b.(.)", mode="no_prelude")
11
+ assert_contains(ast.unparse(parsed), "import a.b as b")
12
+
13
+ # Test: import a.b.c should emit "from a.b import c"
14
+ parsed = koatl.transpile("import a.b.c", mode="no_prelude")
15
+ assert_contains(ast.unparse(parsed), "from a.b import c")
@@ -0,0 +1 @@
1
+ 1 >> 2 << 3 ^^ 4 // 5 && 6 ** (1 || 2 @@ -3)
@@ -4,7 +4,7 @@ import os
4
4
  import koatl.cli
5
5
  from pathlib import Path
6
6
 
7
- sys.path.append(str(Path(__file__).parent / "e2e"))
7
+ sys.path.append(str(Path(__file__).parent))
8
8
  os.chdir(Path(__file__).parent / "e2e")
9
9
 
10
10
 
@@ -0,0 +1,176 @@
1
+ import util.assert_eq
2
+
3
+ test_sum = () =>
4
+ assert_eq((..10).sum(), 45)
5
+ assert_eq([1, 2, 3, 4, 5].sum(), 15)
6
+ assert_eq([].sum(), 0)
7
+ assert_eq([1.5, 2.5, 3.0].sum(), 7.0)
8
+
9
+ test_product = () =>
10
+ let prod_iter = [1, 2, 3].product([4, 5])
11
+ assert_eq(list(prod_iter), [(1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)])
12
+
13
+ test_min = () =>
14
+ assert_eq([3, 1, 4, 1, 5].min(), 1)
15
+ assert_eq([10, 5, 8].min(), 5)
16
+ assert_eq([42].min(), 42)
17
+
18
+ test_max = () =>
19
+ assert_eq([3, 1, 4, 1, 5].max(), 5)
20
+ assert_eq([10, 5, 8].max(), 10)
21
+ assert_eq([42].max(), 42)
22
+
23
+ test_map = () =>
24
+ # List.map returns a list directly (not an iterator)
25
+ assert_eq([1, 2, 3].map($ * 2), [2, 4, 6])
26
+ assert_eq([1, 2, 3].map($ + 10), [11, 12, 13])
27
+ assert_eq([].map($ * 2), [])
28
+ # Range/iterator map still returns an iterator
29
+ assert_eq((..5).map($ * 2).list(), [0, 2, 4, 6, 8])
30
+
31
+ test_filter = () =>
32
+ assert_eq([1, 2, 3, 4, 5].filter($ > 3).list(), [4, 5])
33
+ assert_eq([1, 2, 3, 4, 5].filter($ %% 2 == 0).list(), [2, 4])
34
+ assert_eq((..10).filter($ > 5).list(), [6, 7, 8, 9])
35
+ assert_eq([1, 2, 3].filter($ > 10).list(), [])
36
+
37
+ test_flat_map = () =>
38
+ assert_eq([1, 2, 3].flat_map(x => [x, x * 2]).list(), [1, 2, 2, 4, 3, 6])
39
+ assert_eq([[1, 2], [3, 4]].flat_map(x => x).list(), [1, 2, 3, 4])
40
+ assert_eq([].flat_map(x => [x, x]).list(), [])
41
+
42
+ test_find = () =>
43
+ # find() returns Ok(value) or Err()
44
+ assert_eq([1, 2, 3, 4, 5].find($ > 3).ok, True)
45
+ let result = [1, 2, 3, 4, 5].find($ > 10)
46
+ assert_eq(result.ok, False)
47
+
48
+ test_any = () =>
49
+ assert_eq([1, 2, 3].any(x => x > 2), True)
50
+ assert_eq([1, 2, 3].any(x => x > 10), False)
51
+ assert_eq([].any(x => x > 0), False)
52
+ assert_eq([False, False, True].any(x => x), True)
53
+
54
+ test_all = () =>
55
+ assert_eq([1, 2, 3].all(x => x > 0), True)
56
+ assert_eq([1, 2, 3].all(x => x > 2), False)
57
+ assert_eq([].all(x => x > 0), True)
58
+ assert_eq([True, True, True].all(x => x), True)
59
+
60
+ test_count = () =>
61
+ # count() works on Iterable trait via .iter
62
+ assert_eq([1, 2, 3, 4, 5].iter.count(x => x > 2), 3)
63
+ assert_eq([1, 2, 3, 4, 5].iter.count(x => x %% 2 == 0), 2)
64
+ assert_eq([1, 2, 3].iter.count(x => x > 10), 0)
65
+
66
+ test_take = () =>
67
+ assert_eq([1, 2, 3, 4, 5].take(3).list(), [1, 2, 3])
68
+ assert_eq((..100).take(5).list(), [0, 1, 2, 3, 4])
69
+ assert_eq([1, 2].take(10).list(), [1, 2])
70
+ assert_eq([].take(5).list(), [])
71
+
72
+ test_skip = () =>
73
+ assert_eq([1, 2, 3, 4, 5].skip(2).list(), [3, 4, 5])
74
+ assert_eq((..10).skip(5).list(), [5, 6, 7, 8, 9])
75
+ assert_eq([1, 2].skip(10).list(), [])
76
+ assert_eq([].skip(5).list(), [])
77
+
78
+ test_take_while = () =>
79
+ assert_eq([1, 2, 3, 4, 5].take_while($ < 4).list(), [1, 2, 3])
80
+ assert_eq([2, 4, 6, 7, 8].take_while($ %% 2 == 0).list(), [2, 4, 6])
81
+ assert_eq([1, 2, 3].take_while($ > 10).list(), [])
82
+
83
+ test_skip_while = () =>
84
+ # Uses skip_while() method
85
+ assert_eq([1, 2, 3, 4, 5].skip_while($ < 4).list(), [4, 5])
86
+ assert_eq([2, 4, 6, 7, 8].skip_while($ %% 2 == 0).list(), [7, 8])
87
+ assert_eq([1, 2, 3].skip_while($ > 10).list(), [1, 2, 3])
88
+
89
+ test_enumerate = () =>
90
+ assert_eq((..3).enumerate().list(), [(0, 0), (1, 1), (2, 2)])
91
+ assert_eq(["a", "b", "c"].enumerate().list(), [(0, "a"), (1, "b"), (2, "c")])
92
+
93
+ test_zip = () =>
94
+ assert_eq([1, 2, 3].zip([4, 5, 6]).list(), [(1, 4), (2, 5), (3, 6)])
95
+ assert_eq([1, 2].zip(["a", "b", "c"]).list(), [(1, "a"), (2, "b")])
96
+ assert_eq([].zip([1, 2, 3]).list(), [])
97
+
98
+ test_chain = () =>
99
+ assert_eq([1, 2].chain([3, 4]).list(), [1, 2, 3, 4])
100
+ assert_eq([1].chain([]).chain([2, 3]).list(), [1, 2, 3])
101
+ assert_eq([].chain([]).list(), [])
102
+
103
+ test_cycle = () =>
104
+ assert_eq([1, 2, 3].cycle().take(7).list(), [1, 2, 3, 1, 2, 3, 1])
105
+ assert_eq([5].cycle().take(3).list(), [5, 5, 5])
106
+
107
+ test_reverse = () =>
108
+ # reverse() is a method on Iterable trait, accessed via .iter
109
+ assert_eq(list([1, 2, 3, 4, 5].iter.reverse()), [5, 4, 3, 2, 1])
110
+ assert_eq(list([1].iter.reverse()), [1])
111
+ assert_eq(list([].iter.reverse()), [])
112
+
113
+ test_sort = () =>
114
+ assert_eq([3, 1, 4, 1, 5].iter.sort().list(), [1, 1, 3, 4, 5])
115
+ assert_eq(["c", "a", "b"].iter.sort().list(), ["a", "b", "c"])
116
+ assert_eq([].iter.sort().list(), [])
117
+
118
+ test_join = () =>
119
+ assert_eq(["a", "b", "c"].join(", "), "a, b, c")
120
+ assert_eq([1, 2, 3].map(str).join("-"), "1-2-3")
121
+ assert_eq(["hello"].join(", "), "hello")
122
+ assert_eq([].join(", "), "")
123
+
124
+ test_associate = () =>
125
+ assert_eq((..3).associate($ * 2), {0: 0, 1: 2, 2: 4})
126
+ assert_eq(["a", "bb", "ccc"].associate(len), {"a": 1, "bb": 2, "ccc": 3})
127
+ assert_eq([].associate($ + 1), {})
128
+
129
+ test_group_by = () =>
130
+ assert_eq([1, 2, 3, 4, 5].group_by($ %% 2), {0: [2, 4], 1: [1, 3, 5]})
131
+ assert_eq(["a", "bb", "ccc", "dd"].group_by(len), {1: ["a"], 2: ["bb", "dd"], 3: ["ccc"]})
132
+ assert_eq([].group_by($ > 0), {})
133
+
134
+ test_list_conversion = () =>
135
+ assert_eq((..5).list(), [0, 1, 2, 3, 4])
136
+ assert_eq([1, 2, 3].map($ * 2).list(), [2, 4, 6])
137
+
138
+ test_record_conversion = () =>
139
+ # Dict.map returns a dict directly (not an iterator)
140
+ assert_eq({1: 2, 3: 4}.map([k, v] => [k * 2, v * 3]), {2: 6, 6: 12})
141
+ assert_eq([[1, "a"], [2, "b"]].record(), {1: "a", 2: "b"})
142
+
143
+ test_record_take = () =>
144
+ assert_eq({0: 2}.take(1).list(), [(0, 2)])
145
+
146
+ test_for_each = () =>
147
+ let test_list = []
148
+ [1, 2, 3].for_each(test_list.append)
149
+ assert_eq(test_list, [1, 2, 3])
150
+
151
+ test_fold = () =>
152
+ assert_eq([1, 2, 3, 4].fold(0, (acc, x) => acc + x), 10)
153
+ assert_eq([1, 2, 3].fold(1, (acc, x) => acc * x), 6)
154
+ assert_eq([5, 3, 8, 1].fold(0, (acc, x) => max(acc, x)), 8)
155
+
156
+ # Record/dict methods - defined in both prelude/__init__.tl (Extension.method for dict)
157
+ # and runtime/record.py (Record class methods)
158
+ test_record_map = () =>
159
+ # map transforms both keys and values
160
+ assert_eq({1: 10, 2: 20}.map([k, v] => [k + 1, v + 5]), {2: 15, 3: 25})
161
+ assert_eq({"a": 1, "b": 2}.map([k, v] => [k.upper(), v * 10]), {"A": 10, "B": 20})
162
+
163
+ test_record_filter = () =>
164
+ # filter based on key-value pairs (defined in prelude/__init__.tl for dict)
165
+ assert_eq({1: 10, 2: 20, 3: 30}.filter([k, v] => v > 15), {2: 20, 3: 30})
166
+ assert_eq({1: 10, 2: 20, 3: 30}.filter([k, v] => k %% 2 == 1), {1: 10, 3: 30})
167
+
168
+ test_record_filter_keys = () =>
169
+ # filter_keys - defined in prelude/__init__.tl for dict and record.py for Record
170
+ assert_eq({1: 10, 2: 20, 3: 30}.filter_keys($ > 1), {2: 20, 3: 30})
171
+ assert_eq({"a": 1, "b": 2, "c": 3}.filter_keys($ <> "b"), {"a": 1, "c": 3})
172
+
173
+ test_record_filter_values = () =>
174
+ # filter_values - defined in prelude/__init__.tl for dict and record.py for Record
175
+ assert_eq({1: 10, 2: 20, 3: 30}.filter_values($ >= 20), {2: 20, 3: 30})
176
+ assert_eq({"a": 5, "b": 10, "c": 25}.filter_values($ %% 10 == 5), {"a": 5, "c": 25})
@@ -0,0 +1,5 @@
1
+ def assert_eq(received, expected):
2
+ assert received == expected, f"Expected {expected}, but got {received}"
3
+
4
+ def assert_contains(haystack, needle):
5
+ assert needle in haystack, f"Expected to find {needle} in {haystack}"
@@ -128,7 +128,7 @@ impl<'a> IntoIterator for PyBlock<'a> {
128
128
  }
129
129
 
130
130
  impl<'a> PyBlock<'a> {
131
- pub fn iter(&self) -> std::slice::Iter<SPyStmt<'a>> {
131
+ pub fn iter(&self) -> std::slice::Iter<'_, SPyStmt<'a>> {
132
132
  self.0.iter()
133
133
  }
134
134
  }
@@ -1829,26 +1829,21 @@ impl<'src> SStmtExt<'src> for SStmt<'src> {
1829
1829
  tree.leaf.span,
1830
1830
  ))
1831
1831
  } else {
1832
- let base_module = trunk_accum[..trunk_accum.len() - 1]
1832
+ let full_module = trunk_accum
1833
1833
  .iter()
1834
1834
  .map(|ident| ident.value.0.as_ref())
1835
1835
  .collect::<Vec<_>>()
1836
1836
  .join(".");
1837
1837
 
1838
- let aliases = vec![a.import_alias(
1839
- trunk_accum.last().unwrap().value.escape(),
1840
- alias.as_ref().map(|a| a.value.escape()),
1841
- )];
1842
-
1843
- if trunk_accum.len() == 1 && level == 0 {
1844
- Ok(PyBlock(vec![a.import(aliases)]))
1845
- } else {
1846
- Ok(PyBlock(vec![a.import_from(
1847
- Some(base_module.into()),
1848
- aliases,
1849
- level,
1850
- )]))
1851
- }
1838
+ let last_name = trunk_accum.last().unwrap().value.escape();
1839
+ let asname = alias
1840
+ .as_ref()
1841
+ .map(|a| a.value.escape())
1842
+ .unwrap_or(last_name);
1843
+
1844
+ let aliases = vec![a.import_alias(full_module, Some(asname))];
1845
+
1846
+ Ok(PyBlock(vec![a.import(aliases)]))
1852
1847
  }
1853
1848
  }
1854
1849
  ImportLeaf::Single(name, alias) => {
@@ -424,7 +424,7 @@ impl<'src> TokenizeCtx<'src> {
424
424
  fn parse_symbol(&mut self) -> TResult<'src, (Token<'src>, Span)> {
425
425
  const POLYGRAMS: &[&str] = &[
426
426
  "+=", "-=", "*=", "/=", "|=", "??=", "===", "<=>", "=>", "..", "==", "<>", "<=", ">=",
427
- "//", "%%", "@@", "**", "??", ".=", "::", "||", "&&", "^^", ">>", "<<",
427
+ "!=", "!==", "//", "%%", "@@", "**", "??", ".=", "::", "||", "&&", "^^", ">>", "<<",
428
428
  ];
429
429
  const MONOGRAMS: &str = "[](){}<>.,;:!?@$%^&*+-=|\\/`~";
430
430
 
@@ -1574,7 +1574,16 @@ impl<'src> TokenizeCtx<'src> {
1574
1574
 
1575
1575
  let mut tokens = TokenList(vec![]);
1576
1576
 
1577
- let (block_tokens, _, _) = self.parse_block(0, ParseBlockMode::BeginInput)?;
1577
+ let (block_tokens, end_type, _) = self.parse_block(0, ParseBlockMode::BeginInput)?;
1578
+
1579
+ // Check if we ended due to an unmatched closing delimiter at the top level
1580
+ if end_type == ParseBlockEndType::Delimiter {
1581
+ return Err(LexError::custom(
1582
+ self.span_since(&self.cursor()),
1583
+ "unmatched closing delimiter",
1584
+ ));
1585
+ }
1586
+
1578
1587
  tokens.0.extend(block_tokens.0);
1579
1588
 
1580
1589
  Ok(tokens)
@@ -1652,7 +1661,7 @@ mod tests {
1652
1661
  );
1653
1662
  }
1654
1663
 
1655
- fn simple_tokenize(source: &str) -> TokenList {
1664
+ fn simple_tokenize(source: &str) -> TokenList<'_> {
1656
1665
  let (tokens, errors) = tokenize(source, true);
1657
1666
  if !errors.is_empty() {
1658
1667
  assert!(false, "Errors during tokenization: {:?}", errors);
@@ -1662,7 +1671,7 @@ mod tests {
1662
1671
  tokens.unwrap()
1663
1672
  }
1664
1673
 
1665
- fn simple_trivium(typ: TrivialTokenType, value: &str) -> TrivialToken {
1674
+ fn simple_trivium(typ: TrivialTokenType, value: &'_ str) -> TrivialToken<'_> {
1666
1675
  TrivialToken {
1667
1676
  span: Span { start: 0, end: 0 },
1668
1677
  typ,
@@ -1674,15 +1683,15 @@ mod tests {
1674
1683
  simple_trivium(TrivialTokenType::Newline, "\n")
1675
1684
  }
1676
1685
 
1677
- fn whitespace_trivium(value: &str) -> TrivialToken {
1686
+ fn whitespace_trivium(value: &str) -> TrivialToken<'_> {
1678
1687
  simple_trivium(TrivialTokenType::Whitespace, value)
1679
1688
  }
1680
1689
 
1681
- fn comment_trivium(value: &str) -> TrivialToken {
1690
+ fn comment_trivium(value: &str) -> TrivialToken<'_> {
1682
1691
  simple_trivium(TrivialTokenType::LineComment, value)
1683
1692
  }
1684
1693
 
1685
- fn block_comment_trivium(value: &str) -> TrivialToken {
1694
+ fn block_comment_trivium(value: &'_ str) -> TrivialToken<'_> {
1686
1695
  simple_trivium(TrivialTokenType::BlockComment, value)
1687
1696
  }
1688
1697