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."""
|
"""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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue