Add a Def() read-eval cache
This commit is contained in:
parent
2fbb0f7e1c
commit
db9c0f7bf8
8 changed files with 67 additions and 22 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>")
|
||||
|
||||
|
|
|
@ -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"], {})),
|
||||
)
|
||||
|
|
|
@ -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"]'],
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue