Add a Def() read-eval cache

This commit is contained in:
Reid 'arrdem' McKenzie 2021-08-22 11:15:28 -06:00
parent 2fbb0f7e1c
commit db9c0f7bf8
8 changed files with 67 additions and 22 deletions

View file

@ -85,6 +85,13 @@ return lambda x, y: x > y
!def[sub, py] !def[sub, py]
return lambda x, y: x - y return lambda x, y: x - y
!def[fib_tests, doctest]
fib[1] = 1
fib[2] = 1
fib[3] = 2
!md[]
!def[fib, lil] !def[fib, lil]
lambda[[x] lambda[[x]
, cond[[gt[x, 1], , cond[[gt[x, 1],
@ -143,10 +150,11 @@ Most of Lilith's standard library (`src/python/lilith/prelude.lil`) consists of
**Currently missing:** **Currently missing:**
- `eval[]` - `eval[]`
- `apply[]` (although this is trivial to implement) - `apply[]` (although this is trivial to implement)
- `if[]` - `or[]`
- `not[]` - `not[]`
- `=[]` - `=[]`
- `let[]` - `let[]`
- `if[]`
## License ## License

View file

@ -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?) 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] !def[syntax, md]
\![] bang-brackets is a meta-syntactic directive used both by the lil tangler and the lil object language. \![] 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. Programs consist of many fragments, which can reference each-other and from which executable framents are woven.

View file

@ -8,7 +8,7 @@ import traceback
from lilith.interpreter import Bindings, eval as lil_eval, Runtime from lilith.interpreter import Bindings, eval as lil_eval, Runtime
from lilith.parser import Apply, Args, parse_expr, Symbol 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 import print_formatted_text, prompt, PromptSession
from prompt_toolkit.formatted_text import FormattedText from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.history import FileHistory from prompt_toolkit.history import FileHistory
@ -100,7 +100,7 @@ def batch(opts, args, runtime):
) )
if main in mod.defs: 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: else:
raise NameError(f"entry point {main} not found in {mod.name.name}") raise NameError(f"entry point {main} not found in {mod.name.name}")
@ -165,9 +165,9 @@ def main():
[], [],
{ {
# The Python FFI escape hatch # The Python FFI escape hatch
Symbol("py"): py, Symbol("py"): Def(None, py),
# The Lilith self-interpreter # The Lilith self-interpreter
Symbol("lil"): lil, Symbol("lil"): Def(None, lil),
}, },
) )
runtime.modules[bootstrap.name] = bootstrap runtime.modules[bootstrap.name] = bootstrap

View file

@ -6,7 +6,7 @@ import logging
import typing as t import typing as t
from lilith.parser import Apply, Block, Symbol, Args 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__) 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. # to eval it before we can use it as a value. Eval IS RECURSIVE.
# Then module scope # Then module scope
if name in mod.defs: if d := mod.defs.get(name):
return eval(ctx, mod, Bindings(), 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 # Then module imports
# Note that we include the prelude as a fallback import here # Note that we include the prelude as a fallback import here
for i in mod.imports + [Import(ctx.prelude, {}, wild=True)]: for i in mod.imports + [Import(ctx.prelude, {}, wild=True)]:
im = ctx.modules.get(i.src) im = ctx.modules.get(i.src)
iname = i.names.get(name, name if i.wild else None) iname = i.names.get(name, name if i.wild else None)
if iname in im.defs: if d := im.defs.get(iname):
return eval(ctx, im, Bindings(), 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 # Finally try a global reference
mod = Symbol(".".join(name.name.split(".")[:-1])) mod = Symbol(".".join(name.name.split(".")[:-1]))
name = Symbol(name.name.split(".")[-1]) name = Symbol(name.name.split(".")[-1])
if mod := ctx.modules.get(mod): if mod := ctx.modules.get(mod):
if binding := mod.defs.get(name): if d := mod.defs.get(name):
return eval(ctx, mod, Bindings(), binding) # read-eval cache
if d.get() is Def.UNSET:
d.set(eval(ctx, mod, Bindings(), d.block))
return d.get()
raise err or KeyError 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) args = eval(ctx, mod, locals, expr.args.positionals)
kwargs = eval(ctx, mod, locals, expr.args.kwargs) kwargs = eval(ctx, mod, locals, expr.args.kwargs)
# Use Python's __call__ protocol # 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) return fun(*args, **kwargs)
elif isinstance(expr, list): elif isinstance(expr, list):

View file

@ -1,8 +1,11 @@
!import[lilith.bootstrap, [py, lil]] !import[lilith.bootstrap, [py, lil]]
!def[lil, lilith.bootstrap.lil] !def[lil, lilith.bootstrap.lil]
lilith.bootstrap.lil lilith.bootstrap.lil
!def[py, lilith.bootstrap.lil] !def[py, lilith.bootstrap.lil]
lilith.bootstrap.py lilith.bootstrap.py
!def[abs, py] !def[abs, py]
return abs return abs
!def[delattr, py] !def[delattr, py]

View file

@ -18,10 +18,34 @@ class Import(t.NamedTuple):
wild: bool = False 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): class Module(t.NamedTuple):
name: Symbol name: Symbol
imports: t.List[Import] imports: t.List[Import]
defs: t.Dict[str, Block] defs: t.Dict[str, Def]
def read_buffer(buffer: str, name: str = "&buff") -> Module: 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 block.app.target == Symbol("def"):
if len(block.args.positionals) == 2: if len(block.args.positionals) == 2:
def_name, expr = block.args.positionals 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: else:
raise SyntaxError("!def[<name>, <expr>; <kwargs>] <body>") raise SyntaxError("!def[<name>, <expr>; <kwargs>] <body>")

View file

@ -4,7 +4,7 @@
from lilith.interpreter import Bindings, eval, Runtime from lilith.interpreter import Bindings, eval, Runtime
from lilith.parser import Apply, Args, Symbol from lilith.parser import Apply, Args, Symbol
from lilith.reader import Module from lilith.reader import Def, Module
import pytest import pytest
@ -37,7 +37,7 @@ def test_hello_world(capsys, runtime):
assert ( assert (
eval( eval(
runtime, runtime,
Module("__repl__", [], {Symbol("print"): print}), Module("__repl__", [], {Symbol("print"): Def("__builtin__.print", print)}),
Bindings("__root__", None), Bindings("__root__", None),
Apply(Symbol("print"), Args(["hello, world"], {})), Apply(Symbol("print"), Args(["hello, world"], {})),
) )

View file

@ -1,7 +1,7 @@
"""Tests covering the reader.""" """Tests covering the reader."""
from lilith.parser import Apply, Args, Block, Symbol 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 import pytest
@ -14,9 +14,11 @@ import pytest
Symbol("&buff"), Symbol("&buff"),
[], [],
{ {
Symbol("main"): Block( Symbol("main"): Def(
Apply(Symbol("lang"), Args([Symbol("lil")], {})), Block(
['print["hello, world"]'], Apply(Symbol("lang"), Args([Symbol("lil")], {})),
['print["hello, world"]'],
),
) )
}, },
), ),