Add a Def() read-eval cache
This commit is contained in:
parent
012df53350
commit
3b03905da4
8 changed files with 67 additions and 22 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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>")
|
||||||
|
|
||||||
|
|
|
@ -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"], {})),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
Block(
|
||||||
Apply(Symbol("lang"), Args([Symbol("lil")], {})),
|
Apply(Symbol("lang"), Args([Symbol("lil")], {})),
|
||||||
['print["hello, world"]'],
|
['print["hello, world"]'],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue