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.""" """The Lilith runner."""
import logging
import argparse import argparse
from importlib.resources import read_text as resource_text from importlib.resources import read_text as resource_text
import sys import sys
import traceback 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.parser import Apply, Args, parse_expr, Symbol
from lilith.reader import Import, Module, read_buffer, read_file from lilith.reader import Import, Module, read_buffer, read_file
from prompt_toolkit import print_formatted_text, prompt, PromptSession from prompt_toolkit import print_formatted_text, prompt, PromptSession
@ -15,6 +16,9 @@ from prompt_toolkit.styles import Style
import yaml import yaml
log = logging.getLogger(__name__)
STYLE = Style.from_dict( STYLE = Style.from_dict(
{ {
# User input (default text). # User input (default text).
@ -54,7 +58,7 @@ def repl(opts, args, runtime):
continue continue
try: 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) print_([("class:result", f"{result!r}")], style=STYLE)
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
@ -77,8 +81,7 @@ def batch(opts, args, runtime):
# Register # Register
runtime.modules.update({mod.name: mod}) runtime.modules.update({mod.name: mod})
print("DEBUG: batch mode") log.debug(
print(
yaml.dump( yaml.dump(
{ {
"type": "runtime", "type": "runtime",
@ -96,7 +99,7 @@ def batch(opts, args, runtime):
) )
if main in mod.defs: 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: 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}")
@ -115,12 +118,21 @@ parser.add_argument(
parser.add_argument( parser.add_argument(
"--prelude", default="lilith.prelude", help="Select a module prelude." "--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") parser.add_argument("file", nargs="?", help="A file to start executing from")
if __name__ == "__main__": if __name__ == "__main__":
opts, args = parser.parse_known_args() 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. # 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 opts.path:
for _e in e.split(":"): for _e in e.split(":"):
@ -129,27 +141,26 @@ if __name__ == "__main__":
# Building up a bootstrap interface for going out to many Python features. # Building up a bootstrap interface for going out to many Python features.
runtime = Runtime(Symbol("__runtime__"), Symbol(opts.prelude), {}) runtime = Runtime(Symbol("__runtime__"), Symbol(opts.prelude), {})
def _lil(runtime=None, def py(runtime=None, module=None, expr=None, body=None, name=None):
module=None, """The implementation of the Python lang as an eval type."""
body=None, return eval(body)
name=None):
def lil(runtime=None, module=None, expr=None, body=None, name=None):
"""The implementation of the Lilith lang as an eval type.""" """The implementation of the Lilith lang as an eval type."""
expr = parse_expr(body) expr = parse_expr(body)
return eval(runtime, module, Bindings(), expr) return lil_eval(runtime, module, Bindings(), expr)
bootstrap = Module( bootstrap = Module(
Symbol("lilith.bootstrap"), Symbol("lilith.bootstrap"),
[], [],
{ {
# The foundational meaning of lang[]
Symbol("lang"): lambda evaluator, body=None: evaluator(body),
# The Python FFI escape hatch # The Python FFI escape hatch
Symbol("py"): lambda *args, body=None, **kwargs: eval(body), Symbol("py"): py,
# The Lilith self-interpreter # 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( prelude_mod = read_buffer(
resource_text( resource_text(
".".join(opts.prelude.split(".")[:-1]), opts.prelude.split(".")[-1] + ".lil" ".".join(opts.prelude.split(".")[:-1]), opts.prelude.split(".")[-1] + ".lil"

View file

@ -2,10 +2,14 @@
A quick and dirty recursive interpreter for Lilith. A quick and dirty recursive interpreter for Lilith.
""" """
import logging
import typing as t import typing as t
from lilith.parser import Apply, Block, Symbol from lilith.parser import Apply, Block, Symbol, Args
from lilith.reader import Module from lilith.reader import Module, Import
log = logging.getLogger(__name__)
class Runtime(t.NamedTuple): class Runtime(t.NamedTuple):
@ -21,10 +25,10 @@ class BindingNotFound(KeyError):
class Bindings(object): class Bindings(object):
def __init__(self, name=None, parent=None): def __init__(self, name=None, bindings={}, parent=None):
self._name = None self._name = None
self._parent = None self._parent = None
self._bindings = {} self._bindings = bindings or {}
def get(self, key): def get(self, key):
if key in self._bindings: if key in self._bindings:
@ -38,24 +42,41 @@ class Bindings(object):
raise BindingNotFound(f"No binding key {key}", self) 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.""" """Implement resolving a name against multiple namespaces."""
# First locals
err = None err = None
try: try:
# Locals have already been evaluated
return locals.get(name) return locals.get(name)
except BindingNotFound as e: except BindingNotFound as e:
err = 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: 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 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): def eval(ctx: Runtime, mod: Module, locals: Bindings, expr):
"""Eval. """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, # Pointedly not assert that the module is ACTUALLY in the runtime,
# We're just going to assume this for convenience. # We're just going to assume this for convenience.
log.debug(f"eval[] {expr}")
if isinstance(expr, Symbol): if isinstance(expr, Symbol):
return lookup(ctx, mod, locals, expr) 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): elif isinstance(expr, Apply):
# Evaluate the call target # Evaluate the call target
fun = eval(ctx, mod, locals, expr.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) 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()}]")
return fun(*args, **kwargs) return fun(*args, **kwargs)
elif isinstance(expr, (int, float, str)):
return expr
elif isinstance(expr, list): elif isinstance(expr, list):
return [eval(ctx, mod, locals, i) for i in expr] return [eval(ctx, mod, locals, i) for i in expr]
@ -96,4 +156,4 @@ def eval(ctx: Runtime, mod: Module, locals: Bindings, expr):
} }
else: else:
raise RuntimeError(f"Can't eval {expr}") return expr

View file

@ -1,129 +1,131 @@
!import[lilith.bootstrap, [lang, py]] !import[lilith.bootstrap, [py, lil]]
!def[abs, lang[py]] !def[lil, lilith.bootstrap.lil]
lilith.bootstrap.lil
!def[abs, py]
abs abs
!def[delattr, lang[py]] !def[delattr, py]
delattr delattr
!def[hash, lang[py]] !def[hash, py]
hash hash
!def[set, lang[py]] !def[set, py]
set set
!def[all, lang[py]] !def[all, py]
all all
!def[dict, lang[py]] !def[dict, py]
dict dict
!def[min, lang[py]] !def[min, py]
min min
!def[any, lang[py]] !def[any, py]
any any
!def[dir, lang[py]] !def[dir, py]
dir dir
!def[hex, lang[py]] !def[hex, py]
hex hex
!def[next, lang[py]] !def[next, py]
next next
!def[slice, lang[py]] !def[slice, py]
slice slice
!def[ascii, lang[py]] !def[ascii, py]
ascii ascii
!def[divmod, lang[py]] !def[divmod, py]
divmod divmod
!def[id, lang[py]] !def[id, py]
id id
!def[object, lang[py]] !def[object, py]
object object
!def[sorted, lang[py]] !def[sorted, py]
sorted sorted
!def[bin, lang[py]] !def[bin, py]
bin bin
!def[enumerate, lang[py]] !def[enumerate, py]
enumerate enumerate
!def[input, lang[py]] !def[input, py]
input input
!def[oct, lang[py]] !def[oct, py]
oct oct
!def[staticmethod, lang[py]] !def[staticmethod, py]
staticmethod staticmethod
!def[bool, lang[py]] !def[bool, py]
bool bool
!def[eval, lang[py]] !def[eval, py]
eval eval
!def[int, lang[py]] !def[int, py]
int int
!def[open, lang[py]] !def[open, py]
open open
!def[str, lang[py]] !def[str, py]
str str
!def[breakpoint, lang[py]] !def[breakpoint, py]
breakpoint breakpoint
!def[exec, lang[py]] !def[exec, py]
exec exec
!def[isinstance, lang[py]] !def[isinstance, py]
isinstance isinstance
!def[ord, lang[py]] !def[ord, py]
ord ord
!def[sum, lang[py]] !def[sum, py]
sum sum
!def[bytearray, lang[py]] !def[bytearray, py]
bytearray bytearray
!def[filter, lang[py]] !def[filter, py]
filter filter
!def[issubclass, lang[py]] !def[issubclass, py]
issubclass issubclass
!def[pow, lang[py]] !def[pow, py]
pow pow
!def[super, lang[py]] !def[super, py]
super super
!def[bytes, lang[py]] !def[bytes, py]
bytes bytes
!def[float, lang[py]] !def[float, py]
float float
!def[iter, lang[py]] !def[iter, py]
iter iter
!def[print, lang[py]] !def[print, py]
print print
!def[tuple, lang[py]] !def[tuple, py]
tuple tuple
!def[callable, lang[py]] !def[callable, py]
callable callable
!def[format, lang[py]] !def[format, py]
format format
!def[len, lang[py]] !def[len, py]
len len
!def[property, lang[py]] !def[property, py]
property property
!def[type, lang[py]] !def[type, py]
type type
!def[chr, lang[py]] !def[chr, py]
chr chr
!def[frozenset, lang[py]] !def[frozenset, py]
frozenset frozenset
!def[list, lang[py]] !def[list, py]
list list
!def[range, lang[py]] !def[range, py]
range range
!def[vars, lang[py]] !def[vars, py]
vars vars
!def[classmethod, lang[py]] !def[classmethod, py]
classmethod classmethod
!def[getattr, lang[py]] !def[getattr, py]
getattr getattr
!def[locals, lang[py]] !def[locals, py]
locals locals
!def[repr, lang[py]] !def[repr, py]
repr repr
!def[zip, lang[py]] !def[zip, py]
zip zip
!def[compile, lang[py]] !def[compile, py]
compile compile
!def[map, lang[py]] !def[map, py]
map map
!def[reversed, lang[py]] !def[reversed, py]
reversed reversed
!def[complex, lang[py]] !def[complex, py]
complex complex
!def[hasattr, lang[py]] !def[hasattr, py]
hasattr hasattr
!def[max, lang[py]] !def[max, py]
max max
!def[round, lang[py]] !def[round, py]
round round