koatl 0.1.18__tar.gz → 0.1.19__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.19}/Cargo.lock +1 -1
  2. {koatl-0.1.18 → koatl-0.1.19}/PKG-INFO +1 -1
  3. {koatl-0.1.18 → koatl-0.1.19}/koatl/Cargo.toml +1 -1
  4. {koatl-0.1.18 → koatl-0.1.19/koatl}/python/koatl/prelude/functional/__init__.tl +1 -0
  5. koatl-0.1.19/koatl/python/koatl/prelude/functional/memo.tl +57 -0
  6. {koatl-0.1.18 → koatl-0.1.19/koatl}/python/koatl/runtime/__init__.py +7 -0
  7. {koatl-0.1.18 → koatl-0.1.19}/koatl/src/lib.rs +2 -2
  8. koatl-0.1.19/koatl/tests/e2e/prelude/memo.tl +30 -0
  9. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/ast.rs +1 -0
  10. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/parser.rs +9 -0
  11. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/inference.rs +1 -9
  12. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/lib.rs +8 -3
  13. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/main.rs +1 -1
  14. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/util.rs +2 -2
  15. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/resolve_scopes.rs +47 -0
  16. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/transform.rs +79 -10
  17. {koatl-0.1.18/koatl → koatl-0.1.19}/python/koatl/prelude/functional/__init__.tl +1 -0
  18. koatl-0.1.19/python/koatl/prelude/functional/memo.tl +57 -0
  19. {koatl-0.1.18/koatl → koatl-0.1.19}/python/koatl/runtime/__init__.py +7 -0
  20. {koatl-0.1.18 → koatl-0.1.19}/Cargo.toml +0 -0
  21. {koatl-0.1.18 → koatl-0.1.19}/README.md +0 -0
  22. {koatl-0.1.18 → koatl-0.1.19}/koatl/.github/workflows/CI.yml +0 -0
  23. {koatl-0.1.18 → koatl-0.1.19}/koatl/.gitignore +0 -0
  24. {koatl-0.1.18 → koatl-0.1.19}/koatl/LICENSE +0 -0
  25. {koatl-0.1.18 → koatl-0.1.19}/koatl/README.md +0 -0
  26. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/__init__.py +0 -0
  27. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/__main__.py +0 -0
  28. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/cli.py +0 -0
  29. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/notebook/__init__.py +0 -0
  30. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/notebook/magic.py +0 -0
  31. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/__init__.tl +0 -0
  32. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/async.tl +0 -0
  33. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/async_util.py +0 -0
  34. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/monad.tl +0 -0
  35. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/reader.tl +0 -0
  36. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/result.tl +0 -0
  37. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/iterable.tl +0 -0
  38. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/helpers.py +0 -0
  39. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  40. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/record.py +0 -0
  41. {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/virtual.py +0 -0
  42. {koatl-0.1.18 → koatl-0.1.19}/koatl/requirements.txt +0 -0
  43. {koatl-0.1.18 → koatl-0.1.19}/koatl/src/emit_py.rs +0 -0
  44. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/coal.tl +0 -0
  45. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/containers.tl +0 -0
  46. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/decorators.tl +0 -0
  47. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  48. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/destructure.tl +0 -0
  49. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  50. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/fstr.tl +0 -0
  51. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/functions.tl +0 -0
  52. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/generator.tl +0 -0
  53. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/if_expr.tl +0 -0
  54. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/imports.tl +0 -0
  55. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/loops.tl +0 -0
  56. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/match.tl +0 -0
  57. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/nary-list.tl +0 -0
  58. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/placeholder.tl +0 -0
  59. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/precedence.tl +0 -0
  60. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/scopes.tl +0 -0
  61. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  62. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/slice.tl +0 -0
  63. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/try.tl +0 -0
  64. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/destructure.tl +0 -0
  65. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/async.tl +0 -0
  66. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  67. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/reader.tl +0 -0
  68. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/result.tl +0 -0
  69. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  70. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/__init__.py +0 -0
  71. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/module0.tl +0 -0
  72. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/module1.tl +0 -0
  73. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/module2.tl +0 -0
  74. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/arith.tl +0 -0
  75. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/assign.tl +0 -0
  76. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/block-comments.tl +0 -0
  77. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/deco.tl +0 -0
  78. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/func.tl +0 -0
  79. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/matches.tl +0 -0
  80. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/test_e2e.py +0 -0
  81. {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/test_parse.py +0 -0
  82. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/Cargo.toml +0 -0
  83. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/Cargo.toml +0 -0
  84. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/lexer.rs +0 -0
  85. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/lib.rs +0 -0
  86. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/util.rs +0 -0
  87. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/tests/lexer.rs +0 -0
  88. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/parser.rs +0 -0
  89. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/ast.rs +0 -0
  90. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/emit.rs +0 -0
  91. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/mod.rs +0 -0
  92. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/types.rs +0 -0
  93. {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/util.rs +0 -0
  94. {koatl-0.1.18 → koatl-0.1.19}/pyproject.toml +0 -0
  95. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/__init__.py +0 -0
  96. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/__main__.py +0 -0
  97. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/cli.py +0 -0
  98. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/notebook/__init__.py +0 -0
  99. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/notebook/magic.py +0 -0
  100. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/__init__.tl +0 -0
  101. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/async.tl +0 -0
  102. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/async_util.py +0 -0
  103. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/monad.tl +0 -0
  104. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/reader.tl +0 -0
  105. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/result.tl +0 -0
  106. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/iterable.tl +0 -0
  107. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/runtime/helpers.py +0 -0
  108. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/runtime/meta_finder.py +0 -0
  109. {koatl-0.1.18 → koatl-0.1.19}/python/koatl/runtime/record.py +0 -0
  110. {koatl-0.1.18 → koatl-0.1.19}/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.19"
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.19
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.19"
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,57 @@
1
+ import collections.defaultdict
2
+ import functools.wraps
3
+ import .result.Ok
4
+
5
+ export MemoCtx = class:
6
+ __init__ = self =>
7
+ self.cache = defaultdict(dict)
8
+
9
+ get_or_compute = (self, name, args, f) =>
10
+ if try self.cache[name][args] except KeyError() matches (Ok() as v):
11
+ return v
12
+
13
+ let value = f()
14
+ self.cache[name][args] = value
15
+ value
16
+
17
+ __repr__ = self => f"MemoCtx({self.cache})"
18
+
19
+ export Memo = class:
20
+ __init__ = (self, id, deps, f) =>
21
+ self.id = id
22
+ self.deps = deps
23
+ self.f = f
24
+
25
+ __repr__ = self => f"Memo(...)"
26
+
27
+ run = (self, ctx=MemoCtx()) =>
28
+ ctx.get_or_compute(self.id, tuple(self.deps), self.f)
29
+
30
+ fn = staticmethod& f => wraps(f)& (*args, **kwargs) =>
31
+ let m = object.__new__(Memo)
32
+ let name = f"{f.__module__}.{f.__qualname__}"
33
+ m.run = (ctx=MemoCtx()) =>
34
+ let arg_key = (tuple(args), tuple(kwargs.items()))
35
+
36
+ ctx.get_or_compute(name, arg_key, () =>
37
+ let v = f(*args, **kwargs)
38
+ if v matches Memo():
39
+ v = v.run(ctx)
40
+ v
41
+ )
42
+ m
43
+
44
+ bind_gen = (self, gen) =>
45
+ let m = object.__new__(Memo)
46
+ m.run = (ctx=MemoCtx()) =>
47
+ self = self.run(ctx)
48
+ try:
49
+ while True:
50
+ self = gen.send(self)
51
+ if self matches Memo():
52
+ self = self.run(ctx)
53
+ except StopIteration(value=value):
54
+ return value
55
+ m
56
+
57
+ __tl__.memo = Memo
@@ -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,30 @@
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 = MemoCtx()
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(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
  }
@@ -1286,6 +1297,42 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1286
1297
 
1287
1298
  Expr::Unary(unary_op, expr.traverse(state))
1288
1299
  }
1300
+ Expr::Memo(inner) => {
1301
+ state.set_do(span);
1302
+
1303
+ let mut fn_ctx = FnInfo::new();
1304
+ fn_ctx.is_memo = true;
1305
+
1306
+ let mut scope = Scope::new();
1307
+ scope.is_fn = true;
1308
+
1309
+ // TODO it's confusing that we need to
1310
+ // set scope.is_fn = true all the time in order
1311
+ // for captures to be found properly.
1312
+ // is there any better way?
1313
+
1314
+ let scope = state.scopes.insert(scope);
1315
+ state.fn_stack.push(fn_ctx);
1316
+
1317
+ let scoped = state.scoped(scope, |state| {
1318
+ state.placeholder_guarded(span, |state| inner.traverse_expecting_scope(state))
1319
+ });
1320
+
1321
+ let inner = scoped.value;
1322
+
1323
+ let fn_ctx = state.fn_stack.pop().unwrap();
1324
+
1325
+ if fn_ctx.is_async || fn_ctx.is_generator || fn_ctx.is_do {
1326
+ state.errors.extend(simple_err(
1327
+ "Memo expressions cannot be async, generator, or do",
1328
+ span,
1329
+ ));
1330
+ }
1331
+
1332
+ state.memo_captures.insert(inner.as_ref().into(), fn_ctx);
1333
+
1334
+ Expr::Memo(inner)
1335
+ }
1289
1336
  Expr::Await(x) => {
1290
1337
  state.set_async(span);
1291
1338
  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,
@@ -1314,7 +1319,9 @@ struct PartialPyFnDef<'a> {
1314
1319
  }
1315
1320
 
1316
1321
  enum FnDef<'src, 'ast> {
1317
- // Args, body, is_do, is_async
1322
+ /**
1323
+ * Args, body, is_do, is_async
1324
+ */
1318
1325
  PyFnDef(Vec<PyArgDefItem<'src>>, PyBlock<'src>, bool, bool),
1319
1326
 
1320
1327
  // Expr::Fn, args, body
@@ -1600,7 +1607,7 @@ fn transform_postfix_expr<'src, 'ast>(
1600
1607
  expr: &'ast SExpr<'src>,
1601
1608
  access_ctx: PyAccessCtx,
1602
1609
  ) -> TlResult<SPyExprWithPre<'src>> {
1603
- let (lift_lhs, lhs_node) = match &expr.value {
1610
+ let (mapped, lhs_node) = match &expr.value {
1604
1611
  Expr::RawAttribute(obj, _) => (false, obj),
1605
1612
  Expr::Subscript(obj, _) => (false, obj),
1606
1613
  Expr::Call(obj, _) => (false, obj),
@@ -1619,20 +1626,17 @@ fn transform_postfix_expr<'src, 'ast>(
1619
1626
  }
1620
1627
  };
1621
1628
 
1622
- if let Expr::Attribute(..) | Expr::RawAttribute(..) = &expr.value {
1629
+ if let Expr::Attribute(..) | Expr::RawAttribute(..) | Expr::Subscript(..) = &expr.value {
1623
1630
  } else {
1624
1631
  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
- ));
1632
+ return Err(simple_err("Illegal assignment target", expr.span));
1629
1633
  }
1630
1634
  }
1631
1635
 
1632
1636
  let mut pre = PyBlock::new();
1633
1637
  let a = PyAstBuilder::new(expr.span);
1634
1638
 
1635
- let lhs = if lift_lhs {
1639
+ let lhs = if mapped {
1636
1640
  pre.bind(lhs_node.transform_lifted(ctx)?)
1637
1641
  } else {
1638
1642
  pre.bind(lhs_node.transform(ctx)?)
@@ -2226,6 +2230,70 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for SExpr<'src> {
2226
2230
 
2227
2231
  a.binary(py_op, lhs, rhs)
2228
2232
  }
2233
+ Expr::Memo(expr) => {
2234
+ let memo_captures =
2235
+ ctx.memo_captures
2236
+ .get(&expr.as_ref().into())
2237
+ .ok_or_else(|| {
2238
+ simple_err(
2239
+ "Internal error: Memo expression not found in memo captures",
2240
+ expr.span,
2241
+ )
2242
+ })?;
2243
+
2244
+ let py_expr = expr.transform(ctx)?;
2245
+ let mut py_body = PyBlock::new();
2246
+
2247
+ let mut nonlocals = vec![];
2248
+ let mut globals = vec![];
2249
+
2250
+ for capture in memo_captures.captures.iter() {
2251
+ if ctx.scopes[ctx.declarations[*capture].scope].is_global {
2252
+ globals.push(ctx.decl_py_ident(*capture)?);
2253
+ } else {
2254
+ nonlocals.push(ctx.decl_py_ident(*capture)?);
2255
+ }
2256
+ }
2257
+
2258
+ if !nonlocals.is_empty() {
2259
+ py_body.push(a.nonlocal(nonlocals.iter().map(|x| x.clone()).collect()));
2260
+ }
2261
+ if !globals.is_empty() {
2262
+ py_body.push(a.global(globals.iter().map(|x| x.clone()).collect()));
2263
+ }
2264
+
2265
+ py_body.extend(py_expr.pre);
2266
+ py_body.push(a.return_(py_expr.value));
2267
+
2268
+ let callback = pre.bind(make_fn_exp(
2269
+ ctx,
2270
+ FnDef::PyFnDef(vec![], py_body, false, false),
2271
+ &span,
2272
+ )?);
2273
+
2274
+ let linecol = ctx.line_cache.linecol(span.start);
2275
+
2276
+ a.yield_(a.call(
2277
+ a.tl_builtin("memo"),
2278
+ vec![
2279
+ PyCallItem::Arg(
2280
+ a.str(
2281
+ format!("{}:{}:{}", ctx.filename, linecol.0, linecol.1)
2282
+ )
2283
+ ),
2284
+ PyCallItem::Arg(
2285
+ a.tuple(
2286
+ nonlocals
2287
+ .iter()
2288
+ .map(|x| a.list_item(a.load_ident(x.clone())))
2289
+ .collect(),
2290
+ PyAccessCtx::Load,
2291
+ ),
2292
+ ),
2293
+ PyCallItem::Arg(callback),
2294
+ ],
2295
+ ))
2296
+ }
2229
2297
  Expr::Await(expr) => a.await_(pre.bind(expr.transform(ctx)?)),
2230
2298
  Expr::Yield(expr) => a.yield_(pre.bind(expr.transform(ctx)?)),
2231
2299
  Expr::YieldFrom(expr) => a.yield_from(a.call(
@@ -2344,11 +2412,12 @@ pub struct TransformOutput<'src> {
2344
2412
 
2345
2413
  pub fn transform_ast<'src, 'ast>(
2346
2414
  source: &'src str,
2415
+ filename: &'src str,
2347
2416
  block: &'ast SExpr<'src>,
2348
2417
  resolve_state: &'ast ResolveState<'src>,
2349
2418
  inference: &'ast InferenceCtx<'src, 'ast>,
2350
2419
  ) -> TlResult<TransformOutput<'src>> {
2351
- let mut ctx = TlCtx::new(source, resolve_state, inference)?;
2420
+ let mut ctx = TlCtx::new(source, filename, resolve_state, inference)?;
2352
2421
 
2353
2422
  let mut py_block = PyBlock::new();
2354
2423
  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,57 @@
1
+ import collections.defaultdict
2
+ import functools.wraps
3
+ import .result.Ok
4
+
5
+ export MemoCtx = class:
6
+ __init__ = self =>
7
+ self.cache = defaultdict(dict)
8
+
9
+ get_or_compute = (self, name, args, f) =>
10
+ if try self.cache[name][args] except KeyError() matches (Ok() as v):
11
+ return v
12
+
13
+ let value = f()
14
+ self.cache[name][args] = value
15
+ value
16
+
17
+ __repr__ = self => f"MemoCtx({self.cache})"
18
+
19
+ export Memo = class:
20
+ __init__ = (self, id, deps, f) =>
21
+ self.id = id
22
+ self.deps = deps
23
+ self.f = f
24
+
25
+ __repr__ = self => f"Memo(...)"
26
+
27
+ run = (self, ctx=MemoCtx()) =>
28
+ ctx.get_or_compute(self.id, tuple(self.deps), self.f)
29
+
30
+ fn = staticmethod& f => wraps(f)& (*args, **kwargs) =>
31
+ let m = object.__new__(Memo)
32
+ let name = f"{f.__module__}.{f.__qualname__}"
33
+ m.run = (ctx=MemoCtx()) =>
34
+ let arg_key = (tuple(args), tuple(kwargs.items()))
35
+
36
+ ctx.get_or_compute(name, arg_key, () =>
37
+ let v = f(*args, **kwargs)
38
+ if v matches Memo():
39
+ v = v.run(ctx)
40
+ v
41
+ )
42
+ m
43
+
44
+ bind_gen = (self, gen) =>
45
+ let m = object.__new__(Memo)
46
+ m.run = (ctx=MemoCtx()) =>
47
+ self = self.run(ctx)
48
+ try:
49
+ while True:
50
+ self = gen.send(self)
51
+ if self matches Memo():
52
+ self = self.run(ctx)
53
+ except StopIteration(value=value):
54
+ return value
55
+ m
56
+
57
+ __tl__.memo = Memo
@@ -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