Somewhat working launcher with prelude!

This commit is contained in:
Reid 'arrdem' McKenzie 2021-08-21 18:58:33 -06:00
parent 15c7417516
commit d02f53dc52
11 changed files with 284 additions and 41 deletions

View file

@ -2,6 +2,8 @@ py_project(
name = "lib",
lib_deps = [
py_requirement("lark"),
py_requirement("pyyaml"),
py_requirement("markdown"),
],
test_deps = [
py_requirement("hypothesis"),
@ -13,7 +15,10 @@ zapp_binary(
main = "src/python/lilith/__main__.py",
deps = [
":lib",
py_requirement("lark"), # FIXME: Absolutely a zapp bug
# FIXME: Due to zapp bug(s), replicating requirements here.
py_requirement("lark"),
py_requirement("pyyaml"),
py_requirement("markdown"),
py_requirement("prompt_toolkit"),
],
)

View file

@ -1,4 +1,4 @@
!def[pitch, frag[lang: md]]
!def[pitch, md[]]
# The Lilith Pitch
Code is more than .. just code for the compiler.
@ -14,8 +14,7 @@ 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?)
!def[sytnax]
!frag[lang: md]
!def[sytnax, md[]]
\![] bang-brackets is a meta-syntactic directive used both by the lil tangler and the lil object language.
@ -35,10 +34,9 @@ FIXME: we need bare words, we need strings
FIXME: We need the object language
!def[openapi, frag[lang: yaml]]
!def[openapi, yaml[]]
!def[main, frag[lang: lil]]
!def[main, lilith[]]
; is importing a bang-operation?
import[tagle]
print[str.join["", list[pitch, syntax]]]
print[str.join["", [pitch, syntax]]]

View file

@ -1,15 +1,18 @@
"""A simple Lilith shell."""
"""The Lilith runner."""
import argparse
from importlib.resources import read_text as resource_text
import sys
import traceback
from lilith.interpreter import Bindings, eval, Runtime
from lilith.parser import Apply, Args, parse_expr
from lilith.reader import Module
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
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.history import FileHistory
from prompt_toolkit.styles import Style
import yaml
STYLE = Style.from_dict(
@ -27,17 +30,13 @@ def print_(fmt, **kwargs):
print_formatted_text(FormattedText(fmt), **kwargs)
if __name__ == "__main__":
def repl(opts, args, runtime):
"""Run an interactive Lilith repl, supporting only the Lilith language itself."""
session = PromptSession(history=FileHistory(".lilith.history"))
runtime = Runtime("test", dict())
module = Module(
"__repl__",
{
"print": print,
"int": int,
"string": str,
"float": float,
},
{},
)
while True:
@ -60,3 +59,88 @@ if __name__ == "__main__":
except Exception as e:
traceback.print_exc()
continue
def batch(opts, args, runtime):
"""Run a Lilith program 'for real' in non-interactive batch mode."""
if opts.file:
mod = read_file(opts.file)
main = "main"
elif opts.main:
mod, main = opts.main.split(":") if ":" in opts.main else opts.main, "main"
dirmod, rname = ".".join(mod.split(".")[:-1]), mod.split(".")[-1] + ".lil"
mod = read_buffer(resource_text(dirmod, rname), mod)
main = Symbol(main)
# Register
runtime.modules[mod.name] = mod
print("DEBUG: batch mode")
print(
yaml.dump(
{
"type": "runtime",
"name": runtime.name,
"modules": {
m.name: {
"defs": {dname.name: repr(d) for dname, d in m.defs.items()}
}
for m in runtime.modules.values()
},
}
)
)
if main in mod.defs:
eval(runtime, mod, Bindings(main, None), mod.defs.get(main))
else:
raise NameError(f"entry point {main} not found in {mod.name.name}")
parser = argparse.ArgumentParser()
parser.add_argument(
"-m", "--main", default="main", help="The name of an entry point eg <module>:<name>"
)
parser.add_argument(
"-p",
"--path",
default=[],
action="append",
help="Append something to the module path.",
)
parser.add_argument(
"--prelude", default="lilith.prelude", help="Select a module prelude."
)
parser.add_argument("file", nargs="?", help="A file to start executing from")
if __name__ == "__main__":
opts, args = parser.parse_known_args()
# 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(":"):
sys.path.insert(0, _e)
# Building up a bootstrap interface for going out to many Python features.
runtime = Runtime("__runtime__", Symbol(opts.prelude), {})
bootstrap = Module(
"lilith.bootstrap",
[],
{Symbol("py"): lambda *args, body=None, **kwargs: eval(body)},
)
runtime.modules[Symbol(bootstrap.name)] = bootstrap
prelude_mod = read_buffer(
resource_text(
".".join(opts.prelude.split(".")[:-1]), opts.prelude.split(".")[-1] + ".lil"
),
opts.prelude,
)
runtime.modules[prelude_mod.name] = prelude_mod
if (opts.path and opts.main) or opts.file:
batch(opts, args, runtime)
else:
repl(opts, args, runtime)

View file

@ -1,16 +1,17 @@
"""
A quick and dirty recursive interpreter for Lilith.
"""
import typing as t
from .parser import Apply, Block, Symbol
from .reader import Module
from lilith.parser import Apply, Block, Symbol
from lilith.reader import Module
class Runtime(t.NamedTuple):
name: str
modules: t.Dict[str, Module]
prelude: Symbol
modules: t.Dict[Symbol, Module]
class BindingNotFound(KeyError):

View file

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

View file

@ -4,21 +4,30 @@ Lilith's reader takes parsed blocks and applies languages, building a module str
import logging
import typing as t
from .parser import Block, Args, parse_buffer, Symbol
from warnings import warn
from lilith.parser import Args, Block, parse_buffer, Symbol
log = logging.getLogger(__name__)
class Import(t.NamedTuple):
src: Symbol
names: t.Dict[Symbol, Symbol]
wild: bool = False
class Module(t.NamedTuple):
name: str
name: Symbol
imports: t.List[Import]
defs: t.Dict[str, Block]
def read_buffer(buffer: str, name: str = "&buff") -> Module:
"""Read a module out of a string [or file]"""
m = Module(name, {})
m = Module(Symbol(name), [], {})
for block in parse_buffer(buffer, name):
if block.app.target == Symbol("def"):
if len(block.args.positionals) == 2:
@ -30,6 +39,20 @@ def read_buffer(buffer: str, name: str = "&buff") -> Module:
if block.args.kwargs:
warn("!def[<kwargs>] are ignored")
elif block.app.target == Symbol("import"):
# FIXME (arrdem 2021-08-21):
# This doesn't simplify imports as it goes.
# Multiple imports from the same source will wind up with multiple importlist entries.
iname = block.args.positionals[0]
wild = block.args.kwargs.get(Symbol("wild"), False)
rename = block.args.kwargs.get(Symbol("as"), {})
imports = (
block.args.positionals[1] if len(block.args.positionals) == 2 else []
)
m.imports.append(
Import(iname, {rename.get(i, i): i for i in imports}, wild)
)
else:
raise SyntaxError(f"Unsupported block !{block.tag}[..]")

View file

@ -2,8 +2,7 @@
Pytest fixtures.
"""
from lilith.parser import Block, parser_with_transformer, GRAMMAR
from lilith.parser import Block, GRAMMAR, parser_with_transformer
import pytest

View file

@ -2,13 +2,17 @@
"""
from lilith.interpreter import Bindings, Runtime, eval
from lilith.interpreter import Bindings, eval, Runtime
from lilith.parser import Apply, Args, Symbol
from lilith.reader import Module
from lilith.parser import Args, Apply, Symbol
import pytest
@pytest.fixture
def runtime():
return Runtime("test", None, {})
@pytest.mark.parametrize(
"expr, expected",
[
@ -17,11 +21,11 @@ import pytest
({"foo": "bar"}, {"foo": "bar"}),
],
)
def test_eval(expr, expected):
def test_eval(runtime, expr, expected):
assert (
eval(
Runtime("test", dict()),
Module("__repl__", dict()),
runtime,
Module("__repl__", [], dict()),
Bindings("__root__", None),
expr,
)
@ -29,11 +33,11 @@ def test_eval(expr, expected):
)
def test_hello_world(capsys):
def test_hello_world(capsys, runtime):
assert (
eval(
Runtime("test", {}),
Module("__repl__", {Symbol("print"): print}),
runtime,
Module("__repl__", [], {Symbol("print"): print}),
Bindings("__root__", None),
Apply(Symbol("print"), Args(["hello, world"], {})),
)

View file

@ -9,7 +9,6 @@ from lilith.parser import (
parser_with_transformer,
Symbol,
)
import pytest

View file

@ -2,7 +2,6 @@
from lilith.parser import Apply, Args, Block, Symbol
from lilith.reader import Module, read_buffer
import pytest
@ -12,7 +11,8 @@ import pytest
(
"""!def[main, lang[lil]]\nprint["hello, world"]\n""",
Module(
"&buff",
Symbol("&buff"),
[],
{
Symbol("main"): Block(
Apply(Symbol("lang"), Args([Symbol("lil")], {})),

View file

@ -27,6 +27,7 @@ lark==0.11.1
livereload==2.6.3
lxml==4.6.3
m2r==0.2.1
Markdown==3.3.4
MarkupSafe==2.0.1
meraki==1.7.2
mirakuru==2.4.1