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]
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

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?)
!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.

View file

@ -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

View file

@ -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):

View file

@ -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]

View file

@ -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[<name>, <expr>; <kwargs>] <body>")

View file

@ -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"], {})),
)

View file

@ -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(
Symbol("main"): Def(
Block(
Apply(Symbol("lang"), Args([Symbol("lil")], {})),
['print["hello, world"]'],
),
)
},
),