koatl 0.1.18__tar.gz → 0.1.20__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 (110) hide show
  1. {koatl-0.1.18 → koatl-0.1.20}/Cargo.lock +1 -1
  2. {koatl-0.1.18 → koatl-0.1.20}/PKG-INFO +1 -1
  3. {koatl-0.1.18 → koatl-0.1.20}/koatl/Cargo.toml +1 -1
  4. {koatl-0.1.18 → koatl-0.1.20/koatl}/python/koatl/prelude/functional/__init__.tl +1 -0
  5. koatl-0.1.20/koatl/python/koatl/prelude/functional/memo.tl +63 -0
  6. {koatl-0.1.18 → koatl-0.1.20/koatl}/python/koatl/runtime/__init__.py +7 -0
  7. {koatl-0.1.18 → koatl-0.1.20}/koatl/src/lib.rs +2 -2
  8. koatl-0.1.20/koatl/tests/e2e/prelude/memo.tl +31 -0
  9. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/parser/src/ast.rs +1 -0
  10. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/parser/src/parser.rs +9 -0
  11. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/inference.rs +1 -9
  12. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/lib.rs +8 -3
  13. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/main.rs +1 -1
  14. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/py/util.rs +2 -2
  15. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/resolve_scopes.rs +59 -11
  16. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/transform.rs +103 -16
  17. {koatl-0.1.18/koatl → koatl-0.1.20}/python/koatl/prelude/functional/__init__.tl +1 -0
  18. koatl-0.1.20/python/koatl/prelude/functional/memo.tl +63 -0
  19. {koatl-0.1.18/koatl → koatl-0.1.20}/python/koatl/runtime/__init__.py +7 -0
  20. {koatl-0.1.18 → koatl-0.1.20}/Cargo.toml +0 -0
  21. {koatl-0.1.18 → koatl-0.1.20}/README.md +0 -0
  22. {koatl-0.1.18 → koatl-0.1.20}/koatl/.github/workflows/CI.yml +0 -0
  23. {koatl-0.1.18 → koatl-0.1.20}/koatl/.gitignore +0 -0
  24. {koatl-0.1.18 → koatl-0.1.20}/koatl/LICENSE +0 -0
  25. {koatl-0.1.18 → koatl-0.1.20}/koatl/README.md +0 -0
  26. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/__init__.py +0 -0
  27. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/__main__.py +0 -0
  28. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/cli.py +0 -0
  29. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/notebook/__init__.py +0 -0
  30. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/notebook/magic.py +0 -0
  31. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/prelude/__init__.tl +0 -0
  32. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/prelude/functional/async.tl +0 -0
  33. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/prelude/functional/async_util.py +0 -0
  34. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/prelude/functional/monad.tl +0 -0
  35. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/prelude/functional/reader.tl +0 -0
  36. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/prelude/functional/result.tl +0 -0
  37. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/prelude/iterable.tl +0 -0
  38. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/runtime/helpers.py +0 -0
  39. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  40. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/runtime/record.py +0 -0
  41. {koatl-0.1.18 → koatl-0.1.20}/koatl/python/koatl/runtime/virtual.py +0 -0
  42. {koatl-0.1.18 → koatl-0.1.20}/koatl/requirements.txt +0 -0
  43. {koatl-0.1.18 → koatl-0.1.20}/koatl/src/emit_py.rs +0 -0
  44. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/coal.tl +0 -0
  45. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/containers.tl +0 -0
  46. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/decorators.tl +0 -0
  47. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  48. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/destructure.tl +0 -0
  49. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  50. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/fstr.tl +0 -0
  51. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/functions.tl +0 -0
  52. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/generator.tl +0 -0
  53. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/if_expr.tl +0 -0
  54. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/imports.tl +0 -0
  55. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/loops.tl +0 -0
  56. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/match.tl +0 -0
  57. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/nary-list.tl +0 -0
  58. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/placeholder.tl +0 -0
  59. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/precedence.tl +0 -0
  60. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/scopes.tl +0 -0
  61. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  62. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/slice.tl +0 -0
  63. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/base/try.tl +0 -0
  64. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/destructure.tl +0 -0
  65. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/prelude/async.tl +0 -0
  66. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  67. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/prelude/reader.tl +0 -0
  68. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/prelude/result.tl +0 -0
  69. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  70. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/util/__init__.py +0 -0
  71. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/util/module0.tl +0 -0
  72. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/util/module1.tl +0 -0
  73. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/e2e/util/module2.tl +0 -0
  74. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/parse/arith.tl +0 -0
  75. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/parse/assign.tl +0 -0
  76. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/parse/block-comments.tl +0 -0
  77. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/parse/deco.tl +0 -0
  78. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/parse/func.tl +0 -0
  79. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/parse/matches.tl +0 -0
  80. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/test_e2e.py +0 -0
  81. {koatl-0.1.18 → koatl-0.1.20}/koatl/tests/test_parse.py +0 -0
  82. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/Cargo.toml +0 -0
  83. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/parser/Cargo.toml +0 -0
  84. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/parser/src/lexer.rs +0 -0
  85. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/parser/src/lib.rs +0 -0
  86. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/parser/src/util.rs +0 -0
  87. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/parser/tests/lexer.rs +0 -0
  88. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/parser.rs +0 -0
  89. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/py/ast.rs +0 -0
  90. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/py/emit.rs +0 -0
  91. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/py/mod.rs +0 -0
  92. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/types.rs +0 -0
  93. {koatl-0.1.18 → koatl-0.1.20}/koatl-core/src/util.rs +0 -0
  94. {koatl-0.1.18 → koatl-0.1.20}/pyproject.toml +0 -0
  95. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/__init__.py +0 -0
  96. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/__main__.py +0 -0
  97. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/cli.py +0 -0
  98. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/notebook/__init__.py +0 -0
  99. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/notebook/magic.py +0 -0
  100. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/prelude/__init__.tl +0 -0
  101. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/prelude/functional/async.tl +0 -0
  102. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/prelude/functional/async_util.py +0 -0
  103. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/prelude/functional/monad.tl +0 -0
  104. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/prelude/functional/reader.tl +0 -0
  105. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/prelude/functional/result.tl +0 -0
  106. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/prelude/iterable.tl +0 -0
  107. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/runtime/helpers.py +0 -0
  108. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/runtime/meta_finder.py +0 -0
  109. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/runtime/record.py +0 -0
  110. {koatl-0.1.18 → koatl-0.1.20}/python/koatl/runtime/virtual.py +0 -0
@@ -99,7 +99,7 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
99
99
 
100
100
  [[package]]
101
101
  name = "koatl"
102
- version = "0.1.18"
102
+ version = "0.1.20"
103
103
  dependencies = [
104
104
  "ariadne",
105
105
  "koatl-core",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koatl
3
- Version: 0.1.18
3
+ Version: 0.1.20
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.1.18"
3
+ version = "0.1.20"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -2,6 +2,7 @@ export import .monad.*
2
2
  export import .result.*
3
3
  export import .async.*
4
4
  export import .reader.*
5
+ export import .memo.*
5
6
 
6
7
  export Fn = class:
7
8
  compose = staticmethod& (*args) =>
@@ -0,0 +1,63 @@
1
+ import collections.defaultdict
2
+ import functools.wraps
3
+ import .result.Ok
4
+ import .Monad
5
+
6
+ export Memo = class(Monad):
7
+ Cache = class:
8
+ __init__ = self =>
9
+ self.cache = defaultdict(dict)
10
+
11
+ get_or_compute = (self, name, deps, f) =>
12
+ if try self.cache[name][deps] except KeyError() matches (Ok() as v):
13
+ return v
14
+
15
+ let value = f()
16
+ self.cache[name][deps] = value
17
+ value
18
+
19
+ __repr__ = self => f"Memo.Cache({self.cache})"
20
+
21
+ __init__ = (self, f) => self.f = f
22
+
23
+ __repr__ = self => f"Memo(...)"
24
+
25
+ value = staticmethod& (id, deps, f) =>
26
+ Memo(ctx => ctx.get_or_compute(id, tuple(deps), f))
27
+
28
+ fn = staticmethod& f => wraps(f)& (*args, **kwargs) =>
29
+ let id = f"{f.__module__}.{f.__qualname__}"
30
+ let deps = (tuple(args), tuple(kwargs.items()))
31
+
32
+ Memo(ctx =>
33
+ ctx.get_or_compute(id, deps, () =>
34
+ let v = f(*args, **kwargs)
35
+ if v matches Memo():
36
+ v = v.run(ctx)
37
+ v
38
+ )
39
+ )
40
+
41
+ run = (self, ctx=Cache()) => self.f(ctx)
42
+
43
+ pure = staticmethod& value => Memo(ctx => value)
44
+
45
+ bind_once = (self, f) => Memo(ctx =>
46
+ let value = f(self.run(ctx))
47
+ if value matches Memo():
48
+ value = value.run(ctx)
49
+ value
50
+ )
51
+
52
+ bind_gen = (self, gen) => Memo(ctx =>
53
+ self = self.run(ctx)
54
+ try:
55
+ while True:
56
+ self = gen.send(self)
57
+ if self matches Memo():
58
+ self = self.run(ctx)
59
+ except StopIteration(value=value):
60
+ return value
61
+ )
62
+
63
+ __tl__.memo = Memo.value
@@ -21,11 +21,18 @@ from .record import *
21
21
  from .helpers import *
22
22
 
23
23
 
24
+ def dummy_memo(*args, **kwargs):
25
+ raise RuntimeError(
26
+ "memo is not available without the prelude. Please import koatl.prelude."
27
+ )
28
+
29
+
24
30
  __tl__ = SimpleNamespace(
25
31
  Exception=Exception,
26
32
  slice=slice,
27
33
  vget=virtual.vget,
28
34
  vhas=virtual.vhas,
35
+ memo=dummy_memo,
29
36
  unpack_record=helpers.unpack_record,
30
37
  set_exports=helpers.set_exports,
31
38
  do=helpers.do,
@@ -29,7 +29,7 @@ fn get_option(mode: &str) -> PyResult<TranspileOptions> {
29
29
  fn transpile(src: &str, mode: &str, filename: &str) -> PyResult<PyObject> {
30
30
  let options = get_option(mode)?;
31
31
 
32
- let py_ast = transpile_to_py_ast(src, options).map_err(|e| {
32
+ let py_ast = transpile_to_py_ast(src, filename, options).map_err(|e| {
33
33
  PyErr::new::<pyo3::exceptions::PySyntaxError, _>(format_errs(&e, filename, src))
34
34
  })?;
35
35
 
@@ -43,7 +43,7 @@ fn transpile(src: &str, mode: &str, filename: &str) -> PyResult<PyObject> {
43
43
  fn transpile_raw(src: &str, mode: &str, filename: &str) -> PyResult<PyObject> {
44
44
  let options = get_option(mode)?;
45
45
 
46
- let ctx = transpile_to_source(src, options).map_err(|e| {
46
+ let ctx = transpile_to_source(src, filename, options).map_err(|e| {
47
47
  PyErr::new::<pyo3::exceptions::PySyntaxError, _>(format_errs(&e, filename, src))
48
48
  })?;
49
49
 
@@ -0,0 +1,31 @@
1
+ import util.assert_eq
2
+
3
+ counts = 0
4
+
5
+ fib = Memo.fn& x =>
6
+ if x < 2:
7
+ return 1
8
+
9
+ counts = counts + 1
10
+
11
+ let a = @fib(x - 1)
12
+ let b = @fib(x - 2)
13
+
14
+ a + b
15
+
16
+ assert_eq(fib(10).run(), 89)
17
+ assert_eq(counts < 20, True)
18
+
19
+ counts = 0
20
+
21
+ f = x =>
22
+ memo:
23
+ counts = counts + 1
24
+ x * 2
25
+
26
+ ctx = Memo.Cache()
27
+ assert_eq(f(10).run(ctx), 20)
28
+ assert_eq(f(10).run(ctx), 20)
29
+ assert_eq(f(5).run(ctx), 10)
30
+ assert_eq(f(5).run(ctx), 10)
31
+ assert_eq(counts, 2)
@@ -203,6 +203,7 @@ pub enum Expr<'a, TTree: Tree> {
203
203
  Await(TTree::Expr),
204
204
  Yield(TTree::Expr),
205
205
  YieldFrom(TTree::Expr),
206
+ Memo(TTree::Expr),
206
207
 
207
208
  If(TTree::Expr, TTree::Expr, Option<TTree::Expr>),
208
209
  Match(TTree::Expr, Vec<MatchCase<TTree>>),
@@ -781,8 +781,17 @@ where
781
781
  })
782
782
  .labelled("control-expression");
783
783
 
784
+ let memo_expr = just(Token::Ident("memo"))
785
+ .then(just(START_BLOCK).or_not())
786
+ .ignore_then(expr_or_inline_stmt_or_block.clone())
787
+ .map(|x| Expr::Memo(x.indirect()))
788
+ .spanned_expr()
789
+ .labelled("memo-expression")
790
+ .boxed();
791
+
784
792
  atom.define(
785
793
  choice((
794
+ memo_expr,
786
795
  ident_expr.clone(),
787
796
  classic_if,
788
797
  classic_match,
@@ -152,15 +152,7 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for Indirect<SExpr<'src>> {
152
152
  b.traverse(ctx);
153
153
  Type::Any
154
154
  }
155
- Expr::Await(expr) => {
156
- expr.traverse(ctx);
157
- Type::Any
158
- }
159
- Expr::Yield(expr) => {
160
- expr.traverse(ctx);
161
- Type::Any
162
- }
163
- Expr::YieldFrom(expr) => {
155
+ Expr::Await(expr) | Expr::Yield(expr) | Expr::Memo(expr) | Expr::YieldFrom(expr) => {
164
156
  expr.traverse(ctx);
165
157
  Type::Any
166
158
  }
@@ -60,6 +60,7 @@ impl TranspileOptions {
60
60
 
61
61
  pub fn transpile_to_py_ast<'src>(
62
62
  src: &'src str,
63
+ filename: &'src str,
63
64
  options: TranspileOptions,
64
65
  ) -> TlResult<PyBlock<'src>> {
65
66
  let tl_ast = parse_tl(src)?;
@@ -79,7 +80,7 @@ pub fn transpile_to_py_ast<'src>(
79
80
  }
80
81
  };
81
82
 
82
- let output = match transform_ast(&src, &tl_ast, &resolve_state, &inference) {
83
+ let output = match transform_ast(&src, &filename, &tl_ast, &resolve_state, &inference) {
83
84
  Ok(output) => Some(output),
84
85
  Err(e) => {
85
86
  errs.extend(e);
@@ -173,8 +174,12 @@ pub fn transpile_to_py_ast<'src>(
173
174
  Ok(py_ast)
174
175
  }
175
176
 
176
- pub fn transpile_to_source(src: &str, options: TranspileOptions) -> TlResult<EmitCtx> {
177
- let mut py_ast = transpile_to_py_ast(src, options)?;
177
+ pub fn transpile_to_source(
178
+ src: &str,
179
+ filename: &str,
180
+ options: TranspileOptions,
181
+ ) -> TlResult<EmitCtx> {
182
+ let mut py_ast = transpile_to_py_ast(src, filename, options)?;
178
183
 
179
184
  let mut ctx = EmitCtx::new();
180
185
  py_ast.emit_to(&mut ctx, 0).map_err(|e| {
@@ -13,7 +13,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
13
13
  let filename = std::env::args().nth(2).ok_or("Missing filename argument")?;
14
14
  let src = std::fs::read_to_string(&filename).unwrap();
15
15
 
16
- match transpile_to_source(&src, TranspileOptions::module()) {
16
+ match transpile_to_source(&src, &filename, TranspileOptions::module()) {
17
17
  Ok(ctx) => match cmd.as_str() {
18
18
  "trans" => {
19
19
  println!("{}", ctx.source);
@@ -334,11 +334,11 @@ impl PyAstBuilder {
334
334
  }
335
335
 
336
336
  // Utility builders for tuple items
337
- pub fn tuple_item<'src>(&self, expr: SPyExpr<'src>) -> PyListItem<'src> {
337
+ pub fn list_item<'src>(&self, expr: SPyExpr<'src>) -> PyListItem<'src> {
338
338
  PyListItem::Item(expr)
339
339
  }
340
340
 
341
- pub fn tuple_spread<'src>(&self, expr: SPyExpr<'src>) -> PyListItem<'src> {
341
+ pub fn list_spread<'src>(&self, expr: SPyExpr<'src>) -> PyListItem<'src> {
342
342
  PyListItem::Spread(expr)
343
343
  }
344
344
 
@@ -37,6 +37,7 @@ pub struct ResolveState<'src> {
37
37
  pub resolutions: HashMap<RefHash, DeclarationKey>,
38
38
  pub functions: HashMap<RefHash, FnInfo>,
39
39
  pub patterns: HashMap<RefHash, PatternInfo>,
40
+ pub memo_captures: HashMap<RefHash, FnInfo>,
40
41
 
41
42
  pub declarations: SlotMap<DeclarationKey, Declaration<'src>>,
42
43
  pub scopes: SlotMap<ScopeKey, Scope>,
@@ -86,6 +87,7 @@ impl<'src> ResolveState<'src> {
86
87
  resolutions: HashMap::new(),
87
88
  functions: HashMap::new(),
88
89
  patterns: HashMap::new(),
90
+ memo_captures: HashMap::new(),
89
91
 
90
92
  declarations: SlotMap::with_key(),
91
93
  scopes,
@@ -116,6 +118,7 @@ impl<'src> ResolveState<'src> {
116
118
  }
117
119
 
118
120
  let Some(fn_ctx) = self.fn_stack.last_mut() else {
121
+ // This should never happen since if not fn_local, there must be at least one function context
119
122
  return Err(simple_err("Internal error: no function context", ident.span).into());
120
123
  };
121
124
 
@@ -374,12 +377,19 @@ impl PlaceholderGuard {
374
377
  }
375
378
  }
376
379
 
380
+ // This is a bit of a misuse, since during traversal,
381
+ // FnInfo represents a "capture context" rather than a function
382
+ // (i.e., it logs captures and monadic constructs like Async)
383
+
384
+ // ...but it becomes a real function context once it gets added
385
+ // to the hashmap.
377
386
  #[derive(Debug, Clone)]
378
387
  pub struct FnInfo {
379
388
  pub is_do: bool,
380
389
  pub is_async: bool,
381
390
  pub is_generator: bool,
382
391
  pub is_placeholder: bool,
392
+ pub is_memo: bool,
383
393
 
384
394
  pub arg_names: Vec<DeclarationKey>,
385
395
  pub captures: HashSet<DeclarationKey>,
@@ -392,6 +402,7 @@ impl FnInfo {
392
402
  is_async: false,
393
403
  is_generator: false,
394
404
  is_placeholder: false,
405
+ is_memo: false,
395
406
  arg_names: Vec::new(),
396
407
  captures: HashSet::new(),
397
408
  }
@@ -1131,17 +1142,18 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1131
1142
 
1132
1143
  let mut fn_info = FnInfo::new();
1133
1144
  for (i, decl) in decls.iter().enumerate() {
1134
- if let Some(found) = decls[..i]
1135
- .iter()
1136
- .find(|x| state.declarations[**x].name == state.declarations[*decl].name)
1137
- {
1138
- state.errors.extend(
1139
- TlErrBuilder::new()
1140
- .message("Duplicate declaration in function arguments")
1141
- .context("First declared here", state.declarations[*found].loc)
1142
- .span(state.declarations[*decl].loc)
1143
- .build(),
1144
- );
1145
+ if state.declarations[*decl].name.0 != "_" {
1146
+ if let Some(found) = decls[..i].iter().find(|x| {
1147
+ state.declarations[**x].name == state.declarations[*decl].name
1148
+ }) {
1149
+ state.errors.extend(
1150
+ TlErrBuilder::new()
1151
+ .message("Duplicate declaration in function arguments")
1152
+ .context("First declared here", state.declarations[*found].loc)
1153
+ .span(state.declarations[*decl].loc)
1154
+ .build(),
1155
+ );
1156
+ }
1145
1157
  }
1146
1158
 
1147
1159
  fn_info.arg_names.push(*decl);
@@ -1286,6 +1298,42 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1286
1298
 
1287
1299
  Expr::Unary(unary_op, expr.traverse(state))
1288
1300
  }
1301
+ Expr::Memo(inner) => {
1302
+ state.set_do(span);
1303
+
1304
+ let mut fn_ctx = FnInfo::new();
1305
+ fn_ctx.is_memo = true;
1306
+
1307
+ let mut scope = Scope::new();
1308
+ scope.is_fn = true;
1309
+
1310
+ // TODO it's confusing that we need to
1311
+ // set scope.is_fn = true all the time in order
1312
+ // for captures to be found properly.
1313
+ // is there any better way?
1314
+
1315
+ let scope = state.scopes.insert(scope);
1316
+ state.fn_stack.push(fn_ctx);
1317
+
1318
+ let scoped = state.scoped(scope, |state| {
1319
+ state.placeholder_guarded(span, |state| inner.traverse_expecting_scope(state))
1320
+ });
1321
+
1322
+ let inner = scoped.value;
1323
+
1324
+ let fn_ctx = state.fn_stack.pop().unwrap();
1325
+
1326
+ if fn_ctx.is_async || fn_ctx.is_generator || fn_ctx.is_do {
1327
+ state.errors.extend(simple_err(
1328
+ "Memo expressions cannot be async, generator, or do",
1329
+ span,
1330
+ ));
1331
+ }
1332
+
1333
+ state.memo_captures.insert(inner.as_ref().into(), fn_ctx);
1334
+
1335
+ Expr::Memo(inner)
1336
+ }
1289
1337
  Expr::Await(x) => {
1290
1338
  state.set_async(span);
1291
1339
  Expr::Await(x.traverse(state))
@@ -51,8 +51,9 @@ struct PyDecl<'src> {
51
51
  #[allow(dead_code)]
52
52
  struct TlCtx<'src, 'ast> {
53
53
  source: &'src str,
54
- export_stars: Vec<PyIdent<'src>>,
54
+ filename: &'src str,
55
55
  line_cache: LineColCache,
56
+ export_stars: Vec<PyIdent<'src>>,
56
57
 
57
58
  ident_counts: HashMap<Ident<'src>, usize>,
58
59
  py_decls: HashMap<DeclarationKey, PyDecl<'src>>,
@@ -60,6 +61,7 @@ struct TlCtx<'src, 'ast> {
60
61
  functions: &'ast HashMap<RefHash, FnInfo>,
61
62
  patterns: &'ast HashMap<RefHash, PatternInfo>,
62
63
  resolutions: &'ast HashMap<RefHash, DeclarationKey>,
64
+ memo_captures: &'ast HashMap<RefHash, FnInfo>,
63
65
 
64
66
  scopes: &'ast SlotMap<ScopeKey, Scope>,
65
67
  declarations: &'ast SlotMap<DeclarationKey, Declaration<'src>>,
@@ -70,11 +72,13 @@ struct TlCtx<'src, 'ast> {
70
72
  impl<'src, 'ast> TlCtx<'src, 'ast> {
71
73
  fn new(
72
74
  source: &'src str,
75
+ filename: &'src str,
73
76
  resolve_state: &'ast ResolveState<'src>,
74
77
  inference: &'ast InferenceCtx<'src, 'ast>,
75
78
  ) -> TlResult<Self> {
76
79
  Ok(TlCtx {
77
80
  source,
81
+ filename,
78
82
  export_stars: Vec::new(),
79
83
  line_cache: LineColCache::new(source),
80
84
 
@@ -84,6 +88,7 @@ impl<'src, 'ast> TlCtx<'src, 'ast> {
84
88
  functions: &resolve_state.functions,
85
89
  patterns: &resolve_state.patterns,
86
90
  resolutions: &resolve_state.resolutions,
91
+ memo_captures: &resolve_state.memo_captures,
87
92
 
88
93
  scopes: &resolve_state.scopes,
89
94
  declarations: &resolve_state.declarations,
@@ -1021,7 +1026,7 @@ fn create_throwing_matcher<'src, 'ast>(
1021
1026
  pattern: &'ast SPattern<'src>,
1022
1027
  pattern_meta: &'ast PatternInfo,
1023
1028
  ) -> TlResult<(PyBlock<'src>, PyIdent<'src>)> {
1024
- if let Pattern::Capture(Some(_)) = &pattern.value {
1029
+ if let Pattern::Capture(Some(ident)) = &pattern.value {
1025
1030
  if pattern_meta.decls.len() != 1 {
1026
1031
  return Err(simple_err(
1027
1032
  "Internal: expected exactly one decl",
@@ -1029,8 +1034,10 @@ fn create_throwing_matcher<'src, 'ast>(
1029
1034
  ));
1030
1035
  }
1031
1036
 
1032
- let decl = pattern_meta.decls.iter().next().unwrap();
1033
- return Ok((PyBlock::new(), ctx.decl_py_ident(*decl)?));
1037
+ if ident.value.0 != "_" {
1038
+ let decl = pattern_meta.decls.iter().next().unwrap();
1039
+ return Ok((PyBlock::new(), ctx.decl_py_ident(*decl)?));
1040
+ }
1034
1041
  }
1035
1042
 
1036
1043
  let cursor = ctx.create_aux_var("matcher", pattern.span.start);
@@ -1285,7 +1292,15 @@ fn make_arglist<'src, 'ast>(
1285
1292
  .iter()
1286
1293
  .find(|key| ctx.declarations[**key].name == name.value)
1287
1294
  .ok_or_else(|| simple_err("Internal: missing arg name", name.span))?;
1288
- PyArgDefItem::ArgSpread(ctx.decl_py_ident(*decl)?)
1295
+
1296
+ let decl_value = &ctx.declarations[*decl];
1297
+
1298
+ if decl_value.name.0 == "_" {
1299
+ // need to create a unique name to prevent python complaining
1300
+ PyArgDefItem::ArgSpread(ctx.create_aux_var("_", name.span.start))
1301
+ } else {
1302
+ PyArgDefItem::ArgSpread(ctx.decl_py_ident(*decl)?)
1303
+ }
1289
1304
  }
1290
1305
  ArgDefItem::KwargSpread(name) => {
1291
1306
  let decl = info
@@ -1293,7 +1308,14 @@ fn make_arglist<'src, 'ast>(
1293
1308
  .iter()
1294
1309
  .find(|key| ctx.declarations[**key].name == name.value)
1295
1310
  .ok_or_else(|| simple_err("Internal: missing arg name", name.span))?;
1296
- PyArgDefItem::KwargSpread(ctx.decl_py_ident(*decl)?)
1311
+
1312
+ let decl_value = &ctx.declarations[*decl];
1313
+
1314
+ if decl_value.name.0 == "_" {
1315
+ PyArgDefItem::KwargSpread(ctx.create_aux_var("_", name.span.start))
1316
+ } else {
1317
+ PyArgDefItem::KwargSpread(ctx.decl_py_ident(*decl)?)
1318
+ }
1297
1319
  }
1298
1320
  };
1299
1321
  args_vec.push(arg);
@@ -1314,7 +1336,9 @@ struct PartialPyFnDef<'a> {
1314
1336
  }
1315
1337
 
1316
1338
  enum FnDef<'src, 'ast> {
1317
- // Args, body, is_do, is_async
1339
+ /**
1340
+ * Args, body, is_do, is_async
1341
+ */
1318
1342
  PyFnDef(Vec<PyArgDefItem<'src>>, PyBlock<'src>, bool, bool),
1319
1343
 
1320
1344
  // Expr::Fn, args, body
@@ -1383,7 +1407,8 @@ fn prepare_py_fn<'src, 'ast>(
1383
1407
  let mut globals = vec![];
1384
1408
 
1385
1409
  for capture in fn_info.captures.iter() {
1386
- if ctx.scopes[ctx.declarations[*capture].scope].is_global {
1410
+ let scope = &ctx.scopes[ctx.declarations[*capture].scope];
1411
+ if scope.is_global || scope.is_class {
1387
1412
  globals.push(ctx.decl_py_ident(*capture)?);
1388
1413
  } else {
1389
1414
  nonlocals.push(ctx.decl_py_ident(*capture)?);
@@ -1600,7 +1625,7 @@ fn transform_postfix_expr<'src, 'ast>(
1600
1625
  expr: &'ast SExpr<'src>,
1601
1626
  access_ctx: PyAccessCtx,
1602
1627
  ) -> TlResult<SPyExprWithPre<'src>> {
1603
- let (lift_lhs, lhs_node) = match &expr.value {
1628
+ let (mapped, lhs_node) = match &expr.value {
1604
1629
  Expr::RawAttribute(obj, _) => (false, obj),
1605
1630
  Expr::Subscript(obj, _) => (false, obj),
1606
1631
  Expr::Call(obj, _) => (false, obj),
@@ -1619,20 +1644,17 @@ fn transform_postfix_expr<'src, 'ast>(
1619
1644
  }
1620
1645
  };
1621
1646
 
1622
- if let Expr::Attribute(..) | Expr::RawAttribute(..) = &expr.value {
1647
+ if let Expr::Attribute(..) | Expr::RawAttribute(..) | Expr::Subscript(..) = &expr.value {
1623
1648
  } else {
1624
1649
  if access_ctx != PyAccessCtx::Load {
1625
- return Err(simple_err(
1626
- "Internal error: Cannot use null-coalescing in a non-Load context",
1627
- expr.span,
1628
- ));
1650
+ return Err(simple_err("Illegal assignment target", expr.span));
1629
1651
  }
1630
1652
  }
1631
1653
 
1632
1654
  let mut pre = PyBlock::new();
1633
1655
  let a = PyAstBuilder::new(expr.span);
1634
1656
 
1635
- let lhs = if lift_lhs {
1657
+ let lhs = if mapped {
1636
1658
  pre.bind(lhs_node.transform_lifted(ctx)?)
1637
1659
  } else {
1638
1660
  pre.bind(lhs_node.transform(ctx)?)
@@ -2226,6 +2248,70 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for SExpr<'src> {
2226
2248
 
2227
2249
  a.binary(py_op, lhs, rhs)
2228
2250
  }
2251
+ Expr::Memo(expr) => {
2252
+ let memo_captures =
2253
+ ctx.memo_captures
2254
+ .get(&expr.as_ref().into())
2255
+ .ok_or_else(|| {
2256
+ simple_err(
2257
+ "Internal error: Memo expression not found in memo captures",
2258
+ expr.span,
2259
+ )
2260
+ })?;
2261
+
2262
+ let py_expr = expr.transform(ctx)?;
2263
+ let mut py_body = PyBlock::new();
2264
+
2265
+ let mut nonlocals = vec![];
2266
+ let mut globals = vec![];
2267
+
2268
+ for capture in memo_captures.captures.iter() {
2269
+ if ctx.scopes[ctx.declarations[*capture].scope].is_global {
2270
+ globals.push(ctx.decl_py_ident(*capture)?);
2271
+ } else {
2272
+ nonlocals.push(ctx.decl_py_ident(*capture)?);
2273
+ }
2274
+ }
2275
+
2276
+ if !nonlocals.is_empty() {
2277
+ py_body.push(a.nonlocal(nonlocals.iter().map(|x| x.clone()).collect()));
2278
+ }
2279
+ if !globals.is_empty() {
2280
+ py_body.push(a.global(globals.iter().map(|x| x.clone()).collect()));
2281
+ }
2282
+
2283
+ py_body.extend(py_expr.pre);
2284
+ py_body.push(a.return_(py_expr.value));
2285
+
2286
+ let callback = pre.bind(make_fn_exp(
2287
+ ctx,
2288
+ FnDef::PyFnDef(vec![], py_body, false, false),
2289
+ &span,
2290
+ )?);
2291
+
2292
+ let linecol = ctx.line_cache.linecol(span.start);
2293
+
2294
+ a.yield_(a.call(
2295
+ a.tl_builtin("memo"),
2296
+ vec![
2297
+ PyCallItem::Arg(
2298
+ a.str(
2299
+ format!("{}:{}:{}", ctx.filename, linecol.0, linecol.1)
2300
+ )
2301
+ ),
2302
+ PyCallItem::Arg(
2303
+ a.tuple(
2304
+ nonlocals
2305
+ .iter()
2306
+ .map(|x| a.list_item(a.load_ident(x.clone())))
2307
+ .collect(),
2308
+ PyAccessCtx::Load,
2309
+ ),
2310
+ ),
2311
+ PyCallItem::Arg(callback),
2312
+ ],
2313
+ ))
2314
+ }
2229
2315
  Expr::Await(expr) => a.await_(pre.bind(expr.transform(ctx)?)),
2230
2316
  Expr::Yield(expr) => a.yield_(pre.bind(expr.transform(ctx)?)),
2231
2317
  Expr::YieldFrom(expr) => a.yield_from(a.call(
@@ -2344,11 +2430,12 @@ pub struct TransformOutput<'src> {
2344
2430
 
2345
2431
  pub fn transform_ast<'src, 'ast>(
2346
2432
  source: &'src str,
2433
+ filename: &'src str,
2347
2434
  block: &'ast SExpr<'src>,
2348
2435
  resolve_state: &'ast ResolveState<'src>,
2349
2436
  inference: &'ast InferenceCtx<'src, 'ast>,
2350
2437
  ) -> TlResult<TransformOutput<'src>> {
2351
- let mut ctx = TlCtx::new(source, resolve_state, inference)?;
2438
+ let mut ctx = TlCtx::new(source, filename, resolve_state, inference)?;
2352
2439
 
2353
2440
  let mut py_block = PyBlock::new();
2354
2441
  let expr = py_block.bind(block.transform(&mut ctx)?);
@@ -2,6 +2,7 @@ export import .monad.*
2
2
  export import .result.*
3
3
  export import .async.*
4
4
  export import .reader.*
5
+ export import .memo.*
5
6
 
6
7
  export Fn = class:
7
8
  compose = staticmethod& (*args) =>
@@ -0,0 +1,63 @@
1
+ import collections.defaultdict
2
+ import functools.wraps
3
+ import .result.Ok
4
+ import .Monad
5
+
6
+ export Memo = class(Monad):
7
+ Cache = class:
8
+ __init__ = self =>
9
+ self.cache = defaultdict(dict)
10
+
11
+ get_or_compute = (self, name, deps, f) =>
12
+ if try self.cache[name][deps] except KeyError() matches (Ok() as v):
13
+ return v
14
+
15
+ let value = f()
16
+ self.cache[name][deps] = value
17
+ value
18
+
19
+ __repr__ = self => f"Memo.Cache({self.cache})"
20
+
21
+ __init__ = (self, f) => self.f = f
22
+
23
+ __repr__ = self => f"Memo(...)"
24
+
25
+ value = staticmethod& (id, deps, f) =>
26
+ Memo(ctx => ctx.get_or_compute(id, tuple(deps), f))
27
+
28
+ fn = staticmethod& f => wraps(f)& (*args, **kwargs) =>
29
+ let id = f"{f.__module__}.{f.__qualname__}"
30
+ let deps = (tuple(args), tuple(kwargs.items()))
31
+
32
+ Memo(ctx =>
33
+ ctx.get_or_compute(id, deps, () =>
34
+ let v = f(*args, **kwargs)
35
+ if v matches Memo():
36
+ v = v.run(ctx)
37
+ v
38
+ )
39
+ )
40
+
41
+ run = (self, ctx=Cache()) => self.f(ctx)
42
+
43
+ pure = staticmethod& value => Memo(ctx => value)
44
+
45
+ bind_once = (self, f) => Memo(ctx =>
46
+ let value = f(self.run(ctx))
47
+ if value matches Memo():
48
+ value = value.run(ctx)
49
+ value
50
+ )
51
+
52
+ bind_gen = (self, gen) => Memo(ctx =>
53
+ self = self.run(ctx)
54
+ try:
55
+ while True:
56
+ self = gen.send(self)
57
+ if self matches Memo():
58
+ self = self.run(ctx)
59
+ except StopIteration(value=value):
60
+ return value
61
+ )
62
+
63
+ __tl__.memo = Memo.value
@@ -21,11 +21,18 @@ from .record import *
21
21
  from .helpers import *
22
22
 
23
23
 
24
+ def dummy_memo(*args, **kwargs):
25
+ raise RuntimeError(
26
+ "memo is not available without the prelude. Please import koatl.prelude."
27
+ )
28
+
29
+
24
30
  __tl__ = SimpleNamespace(
25
31
  Exception=Exception,
26
32
  slice=slice,
27
33
  vget=virtual.vget,
28
34
  vhas=virtual.vhas,
35
+ memo=dummy_memo,
29
36
  unpack_record=helpers.unpack_record,
30
37
  set_exports=helpers.set_exports,
31
38
  do=helpers.do,
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