Working lexical/module scope & hello world

This commit is contained in:
Reid 'arrdem' McKenzie 2021-08-21 21:17:48 -06:00
parent 4a5262c95c
commit a6ce37de82
4 changed files with 171 additions and 96 deletions

View file

@ -0,0 +1,2 @@
!def[main, lil]
print["hello, world!"]

View file

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

View file

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

View file

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