From 3b03905da4bd4c55be7e79056e89a1364cace6fd Mon Sep 17 00:00:00 2001 From: Reid 'arrdem' McKenzie Date: Sun, 22 Aug 2021 11:15:28 -0600 Subject: [PATCH] Add a Def() read-eval cache --- projects/lilith/README.md | 10 ++++++- projects/lilith/src/lilith/designdoc.lil | 1 - projects/lilith/src/python/lilith/__main__.py | 8 +++--- .../lilith/src/python/lilith/interpreter.py | 25 +++++++++++------ projects/lilith/src/python/lilith/prelude.lil | 3 ++ projects/lilith/src/python/lilith/reader.py | 28 +++++++++++++++++-- .../lilith/test/python/test_interpreter.py | 4 +-- projects/lilith/test/python/test_reader.py | 10 ++++--- 8 files changed, 67 insertions(+), 22 deletions(-) diff --git a/projects/lilith/README.md b/projects/lilith/README.md index c15fd11..78627f6 100644 --- a/projects/lilith/README.md +++ b/projects/lilith/README.md @@ -85,6 +85,13 @@ return lambda x, y: x > y !def[sub, py] return lambda x, y: x - y +!def[fib_tests, doctest] +fib[1] = 1 +fib[2] = 1 +fib[3] = 2 + +!md[] + !def[fib, lil] lambda[[x] , cond[[gt[x, 1], @@ -143,10 +150,11 @@ Most of Lilith's standard library (`src/python/lilith/prelude.lil`) consists of **Currently missing:** - `eval[]` - `apply[]` (although this is trivial to implement) -- `if[]` +- `or[]` - `not[]` - `=[]` - `let[]` +- `if[]` ## License diff --git a/projects/lilith/src/lilith/designdoc.lil b/projects/lilith/src/lilith/designdoc.lil index 6fc5375..8b34766 100644 --- a/projects/lilith/src/lilith/designdoc.lil +++ b/projects/lilith/src/lilith/designdoc.lil @@ -15,7 +15,6 @@ idea: the parser is going to IGNORE stuff by default. Let's build an M-expression syntax with support for multiple co-equal languages and fragments/scopes with a meta-syntax allowing for references between them and consuming fragment-notation-defined blocks? (FIXME: better word?) !def[syntax, md] - \![] bang-brackets is a meta-syntactic directive used both by the lil tangler and the lil object language. Programs consist of many fragments, which can reference each-other and from which executable framents are woven. diff --git a/projects/lilith/src/python/lilith/__main__.py b/projects/lilith/src/python/lilith/__main__.py index a0b2acf..50ac271 100644 --- a/projects/lilith/src/python/lilith/__main__.py +++ b/projects/lilith/src/python/lilith/__main__.py @@ -8,7 +8,7 @@ import traceback from lilith.interpreter import Bindings, eval as lil_eval, Runtime from lilith.parser import Apply, Args, parse_expr, Symbol -from lilith.reader import Import, Module, read_buffer, read_file +from lilith.reader import Def, Import, Module, read_buffer, read_file from prompt_toolkit import print_formatted_text, prompt, PromptSession from prompt_toolkit.formatted_text import FormattedText from prompt_toolkit.history import FileHistory @@ -100,7 +100,7 @@ def batch(opts, args, runtime): ) if main in mod.defs: - lil_eval(runtime, mod, Bindings(main, None), mod.defs.get(main)) + lil_eval(runtime, mod, Bindings(main, None), mod.defs.get(main).block) else: raise NameError(f"entry point {main} not found in {mod.name.name}") @@ -165,9 +165,9 @@ def main(): [], { # The Python FFI escape hatch - Symbol("py"): py, + Symbol("py"): Def(None, py), # The Lilith self-interpreter - Symbol("lil"): lil, + Symbol("lil"): Def(None, lil), }, ) runtime.modules[bootstrap.name] = bootstrap diff --git a/projects/lilith/src/python/lilith/interpreter.py b/projects/lilith/src/python/lilith/interpreter.py index a982124..b0d4f43 100644 --- a/projects/lilith/src/python/lilith/interpreter.py +++ b/projects/lilith/src/python/lilith/interpreter.py @@ -6,7 +6,7 @@ import logging import typing as t from lilith.parser import Apply, Block, Symbol, Args -from lilith.reader import Module, Import +from lilith.reader import Def, Module, Import log = logging.getLogger(__name__) @@ -57,23 +57,32 @@ def lookup(ctx, mod, locals, name): # to eval it before we can use it as a value. Eval IS RECURSIVE. # Then module scope - if name in mod.defs: - return eval(ctx, mod, Bindings(), mod.defs.get(name)) + if d := mod.defs.get(name): + # read-eval cache + if d.get() is Def.UNSET: + d.set(eval(ctx, mod, Bindings(), d.block)) + return d.get() # Then module imports # Note that we include the prelude as a fallback import here for i in mod.imports + [Import(ctx.prelude, {}, wild=True)]: im = ctx.modules.get(i.src) iname = i.names.get(name, name if i.wild else None) - if iname in im.defs: - return eval(ctx, im, Bindings(), im.defs.get(iname)) + if d := im.defs.get(iname): + # read-eval cache + if d.get() is Def.UNSET: + d.set(eval(ctx, im, Bindings(), d.block)) + return d.get() # Finally try a global reference mod = Symbol(".".join(name.name.split(".")[:-1])) name = Symbol(name.name.split(".")[-1]) if mod := ctx.modules.get(mod): - if binding := mod.defs.get(name): - return eval(ctx, mod, Bindings(), binding) + if d := mod.defs.get(name): + # read-eval cache + if d.get() is Def.UNSET: + d.set(eval(ctx, mod, Bindings(), d.block)) + return d.get() raise err or KeyError @@ -140,7 +149,7 @@ def eval(ctx: Runtime, mod: Module, locals: Bindings, expr): args = eval(ctx, mod, locals, expr.args.positionals) kwargs = eval(ctx, mod, locals, expr.args.kwargs) # Use Python's __call__ protocol - log.debug(f"eval[]; app; {fun}[*{args or []}, **{kwargs or dict()}]") + # log.debug(f"eval[]; app; {fun}[*{args or []}, **{kwargs or dict()}]") return fun(*args, **kwargs) elif isinstance(expr, list): diff --git a/projects/lilith/src/python/lilith/prelude.lil b/projects/lilith/src/python/lilith/prelude.lil index 26f01d2..44cc8e7 100644 --- a/projects/lilith/src/python/lilith/prelude.lil +++ b/projects/lilith/src/python/lilith/prelude.lil @@ -1,8 +1,11 @@ !import[lilith.bootstrap, [py, lil]] + !def[lil, lilith.bootstrap.lil] lilith.bootstrap.lil + !def[py, lilith.bootstrap.lil] lilith.bootstrap.py + !def[abs, py] return abs !def[delattr, py] diff --git a/projects/lilith/src/python/lilith/reader.py b/projects/lilith/src/python/lilith/reader.py index 997d94c..ce447eb 100644 --- a/projects/lilith/src/python/lilith/reader.py +++ b/projects/lilith/src/python/lilith/reader.py @@ -18,10 +18,34 @@ class Import(t.NamedTuple): wild: bool = False +class Def(object): + UNSET = object() + + def __init__(self, block, value=UNSET): + self._block = block + self._value = value + + def __eq__(self, other): + return self.block == other.block + + def __repr__(self): + return f"Def(block={self._block!r}, set={self._value is not self.UNSET})" + + @property + def block(self): + return self._block + + def get(self): + return self._value + + def set(self, value): + self._value = value + + class Module(t.NamedTuple): name: Symbol imports: t.List[Import] - defs: t.Dict[str, Block] + defs: t.Dict[str, Def] def read_buffer(buffer: str, name: str = "&buff") -> Module: @@ -32,7 +56,7 @@ def read_buffer(buffer: str, name: str = "&buff") -> Module: if block.app.target == Symbol("def"): if len(block.args.positionals) == 2: def_name, expr = block.args.positionals - m.defs[def_name] = Block(expr, block.body_lines) + m.defs[def_name] = Def(Block(expr, block.body_lines)) else: raise SyntaxError("!def[, ; ] ") diff --git a/projects/lilith/test/python/test_interpreter.py b/projects/lilith/test/python/test_interpreter.py index 2a39061..f4e6856 100644 --- a/projects/lilith/test/python/test_interpreter.py +++ b/projects/lilith/test/python/test_interpreter.py @@ -4,7 +4,7 @@ from lilith.interpreter import Bindings, eval, Runtime from lilith.parser import Apply, Args, Symbol -from lilith.reader import Module +from lilith.reader import Def, Module import pytest @@ -37,7 +37,7 @@ def test_hello_world(capsys, runtime): assert ( eval( runtime, - Module("__repl__", [], {Symbol("print"): print}), + Module("__repl__", [], {Symbol("print"): Def("__builtin__.print", print)}), Bindings("__root__", None), Apply(Symbol("print"), Args(["hello, world"], {})), ) diff --git a/projects/lilith/test/python/test_reader.py b/projects/lilith/test/python/test_reader.py index 7898fa9..5671cfb 100644 --- a/projects/lilith/test/python/test_reader.py +++ b/projects/lilith/test/python/test_reader.py @@ -1,7 +1,7 @@ """Tests covering the reader.""" from lilith.parser import Apply, Args, Block, Symbol -from lilith.reader import Module, read_buffer +from lilith.reader import Def, Module, read_buffer import pytest @@ -14,9 +14,11 @@ import pytest Symbol("&buff"), [], { - Symbol("main"): Block( - Apply(Symbol("lang"), Args([Symbol("lil")], {})), - ['print["hello, world"]'], + Symbol("main"): Def( + Block( + Apply(Symbol("lang"), Args([Symbol("lil")], {})), + ['print["hello, world"]'], + ), ) }, ),