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.
- {koatl-0.1.18 → koatl-0.1.19}/Cargo.lock +1 -1
- {koatl-0.1.18 → koatl-0.1.19}/PKG-INFO +1 -1
- {koatl-0.1.18 → koatl-0.1.19}/koatl/Cargo.toml +1 -1
- {koatl-0.1.18 → koatl-0.1.19/koatl}/python/koatl/prelude/functional/__init__.tl +1 -0
- koatl-0.1.19/koatl/python/koatl/prelude/functional/memo.tl +57 -0
- {koatl-0.1.18 → koatl-0.1.19/koatl}/python/koatl/runtime/__init__.py +7 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/src/lib.rs +2 -2
- koatl-0.1.19/koatl/tests/e2e/prelude/memo.tl +30 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/ast.rs +1 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/parser.rs +9 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/inference.rs +1 -9
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/lib.rs +8 -3
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/main.rs +1 -1
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/util.rs +2 -2
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/resolve_scopes.rs +47 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/transform.rs +79 -10
- {koatl-0.1.18/koatl → koatl-0.1.19}/python/koatl/prelude/functional/__init__.tl +1 -0
- koatl-0.1.19/python/koatl/prelude/functional/memo.tl +57 -0
- {koatl-0.1.18/koatl → koatl-0.1.19}/python/koatl/runtime/__init__.py +7 -0
- {koatl-0.1.18 → koatl-0.1.19}/Cargo.toml +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/README.md +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/.github/workflows/CI.yml +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/.gitignore +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/LICENSE +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/README.md +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/__init__.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/__main__.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/cli.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/notebook/__init__.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/notebook/magic.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/__init__.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/async.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/async_util.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/monad.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/reader.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/functional/result.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/prelude/iterable.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/helpers.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/meta_finder.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/record.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/python/koatl/runtime/virtual.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/requirements.txt +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/src/emit_py.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/coal.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/containers.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/decorators.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/destructure.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/escape_ident.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/fstr.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/functions.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/generator.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/if_expr.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/imports.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/loops.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/match.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/nary-list.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/placeholder.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/precedence.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/scopes.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/slice.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/base/try.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/destructure.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/async.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/iterables.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/reader.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/result.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/prelude/virtual.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/__init__.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/module0.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/module1.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/e2e/util/module2.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/arith.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/assign.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/block-comments.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/deco.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/func.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/parse/matches.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/test_e2e.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl/tests/test_parse.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/Cargo.toml +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/Cargo.toml +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/lexer.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/lib.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/src/util.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/parser/tests/lexer.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/parser.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/ast.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/emit.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/py/mod.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/types.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/koatl-core/src/util.rs +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/pyproject.toml +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/__init__.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/__main__.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/cli.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/notebook/__init__.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/notebook/magic.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/__init__.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/async.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/async_util.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/monad.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/reader.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/functional/result.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/prelude/iterable.tl +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/runtime/helpers.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/runtime/meta_finder.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/runtime/record.py +0 -0
- {koatl-0.1.18 → koatl-0.1.19}/python/koatl/runtime/virtual.py +0 -0
|
@@ -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)
|
|
@@ -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(
|
|
177
|
-
|
|
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
|
|
337
|
+
pub fn list_item<'src>(&self, expr: SPyExpr<'src>) -> PyListItem<'src> {
|
|
338
338
|
PyListItem::Item(expr)
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
pub fn
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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)?);
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|