Working lexical/module scope & hello world
This commit is contained in:
parent
4a5262c95c
commit
a6ce37de82
4 changed files with 171 additions and 96 deletions
2
projects/lilith/src/lilith/hello.lil
Normal file
2
projects/lilith/src/lilith/hello.lil
Normal file
|
@ -0,0 +1,2 @@
|
|||
!def[main, lil]
|
||||
print["hello, world!"]
|
|
@ -1,11 +1,12 @@
|
|||
"""The Lilith runner."""
|
||||
|
||||
import logging
|
||||
import argparse
|
||||
from importlib.resources import read_text as resource_text
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from lilith.interpreter import Bindings, eval, Runtime
|
||||
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 prompt_toolkit import print_formatted_text, prompt, PromptSession
|
||||
|
@ -15,6 +16,9 @@ from prompt_toolkit.styles import Style
|
|||
import yaml
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
STYLE = Style.from_dict(
|
||||
{
|
||||
# User input (default text).
|
||||
|
@ -54,7 +58,7 @@ def repl(opts, args, runtime):
|
|||
continue
|
||||
|
||||
try:
|
||||
result = eval(runtime, module, Bindings("__root__", None), expr)
|
||||
result = lil_eval(runtime, module, Bindings("__root__", None), expr)
|
||||
print_([("class:result", f"⇒ {result!r}")], style=STYLE)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
|
@ -77,8 +81,7 @@ def batch(opts, args, runtime):
|
|||
# Register
|
||||
runtime.modules.update({mod.name: mod})
|
||||
|
||||
print("DEBUG: batch mode")
|
||||
print(
|
||||
log.debug(
|
||||
yaml.dump(
|
||||
{
|
||||
"type": "runtime",
|
||||
|
@ -96,7 +99,7 @@ def batch(opts, args, runtime):
|
|||
)
|
||||
|
||||
if main in mod.defs:
|
||||
eval(runtime, mod, Bindings(main, None), mod.defs.get(main))
|
||||
lil_eval(runtime, mod, Bindings(main, None), mod.defs.get(main))
|
||||
else:
|
||||
raise NameError(f"entry point {main} not found in {mod.name.name}")
|
||||
|
||||
|
@ -115,12 +118,21 @@ parser.add_argument(
|
|||
parser.add_argument(
|
||||
"--prelude", default="lilith.prelude", help="Select a module prelude."
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="count", default=0)
|
||||
parser.add_argument("file", nargs="?", help="A file to start executing from")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
opts, args = parser.parse_known_args()
|
||||
|
||||
if opts.verbose == 0:
|
||||
level = logging.WARN
|
||||
elif opts.verbose == 1:
|
||||
level = logging.INFO
|
||||
elif opts.verbose > 1:
|
||||
level = 0
|
||||
|
||||
logging.basicConfig(level=level)
|
||||
|
||||
# Bash anything the user says is the path onto the PYTHONPATH so we can use importlib for our loading machinery.
|
||||
for e in opts.path:
|
||||
for _e in e.split(":"):
|
||||
|
@ -129,27 +141,26 @@ if __name__ == "__main__":
|
|||
# Building up a bootstrap interface for going out to many Python features.
|
||||
runtime = Runtime(Symbol("__runtime__"), Symbol(opts.prelude), {})
|
||||
|
||||
def _lil(runtime=None,
|
||||
module=None,
|
||||
body=None,
|
||||
name=None):
|
||||
def py(runtime=None, module=None, expr=None, body=None, name=None):
|
||||
"""The implementation of the Python lang as an eval type."""
|
||||
return eval(body)
|
||||
|
||||
def lil(runtime=None, module=None, expr=None, body=None, name=None):
|
||||
"""The implementation of the Lilith lang as an eval type."""
|
||||
expr = parse_expr(body)
|
||||
return eval(runtime, module, Bindings(), expr)
|
||||
return lil_eval(runtime, module, Bindings(), expr)
|
||||
|
||||
bootstrap = Module(
|
||||
Symbol("lilith.bootstrap"),
|
||||
[],
|
||||
{
|
||||
# The foundational meaning of lang[]
|
||||
Symbol("lang"): lambda evaluator, body=None: evaluator(body),
|
||||
# The Python FFI escape hatch
|
||||
Symbol("py"): lambda *args, body=None, **kwargs: eval(body),
|
||||
Symbol("py"): py,
|
||||
# The Lilith self-interpreter
|
||||
Symbol("lilith"): _lil,
|
||||
},
|
||||
Symbol("lil"): lil,
|
||||
},
|
||||
)
|
||||
runtime.modules[Symbol(bootstrap.name)] = bootstrap
|
||||
runtime.modules[bootstrap.name] = bootstrap
|
||||
prelude_mod = read_buffer(
|
||||
resource_text(
|
||||
".".join(opts.prelude.split(".")[:-1]), opts.prelude.split(".")[-1] + ".lil"
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
A quick and dirty recursive interpreter for Lilith.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import typing as t
|
||||
|
||||
from lilith.parser import Apply, Block, Symbol
|
||||
from lilith.reader import Module
|
||||
from lilith.parser import Apply, Block, Symbol, Args
|
||||
from lilith.reader import Module, Import
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Runtime(t.NamedTuple):
|
||||
|
@ -21,10 +25,10 @@ class BindingNotFound(KeyError):
|
|||
|
||||
|
||||
class Bindings(object):
|
||||
def __init__(self, name=None, parent=None):
|
||||
def __init__(self, name=None, bindings={}, parent=None):
|
||||
self._name = None
|
||||
self._parent = None
|
||||
self._bindings = {}
|
||||
self._bindings = bindings or {}
|
||||
|
||||
def get(self, key):
|
||||
if key in self._bindings:
|
||||
|
@ -38,24 +42,41 @@ class Bindings(object):
|
|||
raise BindingNotFound(f"No binding key {key}", self)
|
||||
|
||||
|
||||
def lookup(runtime, mod, locals, name):
|
||||
def lookup(ctx, mod, locals, name):
|
||||
"""Implement resolving a name against multiple namespaces."""
|
||||
|
||||
# First locals
|
||||
err = None
|
||||
try:
|
||||
# Locals have already been evaluated
|
||||
return locals.get(name)
|
||||
except BindingNotFound as e:
|
||||
err = e
|
||||
|
||||
# Note that module globals, and code in other modules IS DEFERRED. We have
|
||||
# to eval it before we can use it as a value. Eval IS RECURSIVE.
|
||||
|
||||
# Then module scope
|
||||
if name in mod.defs:
|
||||
return mod.defs.get(name)
|
||||
return eval(ctx, mod, Bindings(), mod.defs.get(name))
|
||||
|
||||
# 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))
|
||||
|
||||
# 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)
|
||||
|
||||
raise err or KeyError
|
||||
|
||||
# FIXME (arrdem 2021-08-21):
|
||||
# How do we ever get references to stuff in other modules?
|
||||
# !import / !require is probably The Way
|
||||
|
||||
|
||||
def eval(ctx: Runtime, mod: Module, locals: Bindings, expr):
|
||||
"""Eval.
|
||||
|
@ -68,9 +89,50 @@ def eval(ctx: Runtime, mod: Module, locals: Bindings, expr):
|
|||
# Pointedly not assert that the module is ACTUALLY in the runtime,
|
||||
# We're just going to assume this for convenience.
|
||||
|
||||
log.debug(f"eval[] {expr}")
|
||||
|
||||
if isinstance(expr, Symbol):
|
||||
return lookup(ctx, mod, locals, expr)
|
||||
|
||||
if isinstance(expr, Block):
|
||||
# Blocks are INCREDIBLY magical to the interpreter, as they have body
|
||||
# lines (understood to be body= as a kwarg) and any `lang[]` must be
|
||||
# evaluated before the body can be returned.
|
||||
|
||||
body = expr.body
|
||||
expr = expr.app
|
||||
# Do eval of a synthetic expression capturing the requisite context.
|
||||
return eval(
|
||||
ctx,
|
||||
mod,
|
||||
# In a synthetic context with all the non-evaluatable
|
||||
# objects as locals
|
||||
Bindings(
|
||||
None,
|
||||
{
|
||||
Symbol("runtime"): ctx,
|
||||
Symbol("module"): mod,
|
||||
Symbol("expr"): expr,
|
||||
Symbol("body"): body,
|
||||
},
|
||||
locals,
|
||||
),
|
||||
# Evaluate a synthetic expr using the locals to get kwargs
|
||||
# through.
|
||||
Apply(
|
||||
expr,
|
||||
Args(
|
||||
[],
|
||||
{
|
||||
"runtime": Symbol("runtime"),
|
||||
"module": Symbol("module"),
|
||||
"expr": Symbol("expr"),
|
||||
"body": Symbol("body"),
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
elif isinstance(expr, Apply):
|
||||
# Evaluate the call target
|
||||
fun = eval(ctx, mod, locals, expr.target)
|
||||
|
@ -78,11 +140,9 @@ 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()}]")
|
||||
return fun(*args, **kwargs)
|
||||
|
||||
elif isinstance(expr, (int, float, str)):
|
||||
return expr
|
||||
|
||||
elif isinstance(expr, list):
|
||||
return [eval(ctx, mod, locals, i) for i in expr]
|
||||
|
||||
|
@ -96,4 +156,4 @@ def eval(ctx: Runtime, mod: Module, locals: Bindings, expr):
|
|||
}
|
||||
|
||||
else:
|
||||
raise RuntimeError(f"Can't eval {expr}")
|
||||
return expr
|
||||
|
|
|
@ -1,129 +1,131 @@
|
|||
!import[lilith.bootstrap, [lang, py]]
|
||||
!def[abs, lang[py]]
|
||||
!import[lilith.bootstrap, [py, lil]]
|
||||
!def[lil, lilith.bootstrap.lil]
|
||||
lilith.bootstrap.lil
|
||||
!def[abs, py]
|
||||
abs
|
||||
!def[delattr, lang[py]]
|
||||
!def[delattr, py]
|
||||
delattr
|
||||
!def[hash, lang[py]]
|
||||
!def[hash, py]
|
||||
hash
|
||||
!def[set, lang[py]]
|
||||
!def[set, py]
|
||||
set
|
||||
!def[all, lang[py]]
|
||||
!def[all, py]
|
||||
all
|
||||
!def[dict, lang[py]]
|
||||
!def[dict, py]
|
||||
dict
|
||||
!def[min, lang[py]]
|
||||
!def[min, py]
|
||||
min
|
||||
!def[any, lang[py]]
|
||||
!def[any, py]
|
||||
any
|
||||
!def[dir, lang[py]]
|
||||
!def[dir, py]
|
||||
dir
|
||||
!def[hex, lang[py]]
|
||||
!def[hex, py]
|
||||
hex
|
||||
!def[next, lang[py]]
|
||||
!def[next, py]
|
||||
next
|
||||
!def[slice, lang[py]]
|
||||
!def[slice, py]
|
||||
slice
|
||||
!def[ascii, lang[py]]
|
||||
!def[ascii, py]
|
||||
ascii
|
||||
!def[divmod, lang[py]]
|
||||
!def[divmod, py]
|
||||
divmod
|
||||
!def[id, lang[py]]
|
||||
!def[id, py]
|
||||
id
|
||||
!def[object, lang[py]]
|
||||
!def[object, py]
|
||||
object
|
||||
!def[sorted, lang[py]]
|
||||
!def[sorted, py]
|
||||
sorted
|
||||
!def[bin, lang[py]]
|
||||
!def[bin, py]
|
||||
bin
|
||||
!def[enumerate, lang[py]]
|
||||
!def[enumerate, py]
|
||||
enumerate
|
||||
!def[input, lang[py]]
|
||||
!def[input, py]
|
||||
input
|
||||
!def[oct, lang[py]]
|
||||
!def[oct, py]
|
||||
oct
|
||||
!def[staticmethod, lang[py]]
|
||||
!def[staticmethod, py]
|
||||
staticmethod
|
||||
!def[bool, lang[py]]
|
||||
!def[bool, py]
|
||||
bool
|
||||
!def[eval, lang[py]]
|
||||
!def[eval, py]
|
||||
eval
|
||||
!def[int, lang[py]]
|
||||
!def[int, py]
|
||||
int
|
||||
!def[open, lang[py]]
|
||||
!def[open, py]
|
||||
open
|
||||
!def[str, lang[py]]
|
||||
!def[str, py]
|
||||
str
|
||||
!def[breakpoint, lang[py]]
|
||||
!def[breakpoint, py]
|
||||
breakpoint
|
||||
!def[exec, lang[py]]
|
||||
!def[exec, py]
|
||||
exec
|
||||
!def[isinstance, lang[py]]
|
||||
!def[isinstance, py]
|
||||
isinstance
|
||||
!def[ord, lang[py]]
|
||||
!def[ord, py]
|
||||
ord
|
||||
!def[sum, lang[py]]
|
||||
!def[sum, py]
|
||||
sum
|
||||
!def[bytearray, lang[py]]
|
||||
!def[bytearray, py]
|
||||
bytearray
|
||||
!def[filter, lang[py]]
|
||||
!def[filter, py]
|
||||
filter
|
||||
!def[issubclass, lang[py]]
|
||||
!def[issubclass, py]
|
||||
issubclass
|
||||
!def[pow, lang[py]]
|
||||
!def[pow, py]
|
||||
pow
|
||||
!def[super, lang[py]]
|
||||
!def[super, py]
|
||||
super
|
||||
!def[bytes, lang[py]]
|
||||
!def[bytes, py]
|
||||
bytes
|
||||
!def[float, lang[py]]
|
||||
!def[float, py]
|
||||
float
|
||||
!def[iter, lang[py]]
|
||||
!def[iter, py]
|
||||
iter
|
||||
!def[print, lang[py]]
|
||||
!def[print, py]
|
||||
print
|
||||
!def[tuple, lang[py]]
|
||||
!def[tuple, py]
|
||||
tuple
|
||||
!def[callable, lang[py]]
|
||||
!def[callable, py]
|
||||
callable
|
||||
!def[format, lang[py]]
|
||||
!def[format, py]
|
||||
format
|
||||
!def[len, lang[py]]
|
||||
!def[len, py]
|
||||
len
|
||||
!def[property, lang[py]]
|
||||
!def[property, py]
|
||||
property
|
||||
!def[type, lang[py]]
|
||||
!def[type, py]
|
||||
type
|
||||
!def[chr, lang[py]]
|
||||
!def[chr, py]
|
||||
chr
|
||||
!def[frozenset, lang[py]]
|
||||
!def[frozenset, py]
|
||||
frozenset
|
||||
!def[list, lang[py]]
|
||||
!def[list, py]
|
||||
list
|
||||
!def[range, lang[py]]
|
||||
!def[range, py]
|
||||
range
|
||||
!def[vars, lang[py]]
|
||||
!def[vars, py]
|
||||
vars
|
||||
!def[classmethod, lang[py]]
|
||||
!def[classmethod, py]
|
||||
classmethod
|
||||
!def[getattr, lang[py]]
|
||||
!def[getattr, py]
|
||||
getattr
|
||||
!def[locals, lang[py]]
|
||||
!def[locals, py]
|
||||
locals
|
||||
!def[repr, lang[py]]
|
||||
!def[repr, py]
|
||||
repr
|
||||
!def[zip, lang[py]]
|
||||
!def[zip, py]
|
||||
zip
|
||||
!def[compile, lang[py]]
|
||||
!def[compile, py]
|
||||
compile
|
||||
!def[map, lang[py]]
|
||||
!def[map, py]
|
||||
map
|
||||
!def[reversed, lang[py]]
|
||||
!def[reversed, py]
|
||||
reversed
|
||||
!def[complex, lang[py]]
|
||||
!def[complex, py]
|
||||
complex
|
||||
!def[hasattr, lang[py]]
|
||||
!def[hasattr, py]
|
||||
hasattr
|
||||
!def[max, lang[py]]
|
||||
!def[max, py]
|
||||
max
|
||||
!def[round, lang[py]]
|
||||
!def[round, py]
|
||||
round
|
||||
|
|
Loading…
Reference in a new issue