Somewhat working launcher with prelude!

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

View file

@ -2,6 +2,8 @@ py_project(
name = "lib", name = "lib",
lib_deps = [ lib_deps = [
py_requirement("lark"), py_requirement("lark"),
py_requirement("pyyaml"),
py_requirement("markdown"),
], ],
test_deps = [ test_deps = [
py_requirement("hypothesis"), py_requirement("hypothesis"),
@ -13,7 +15,10 @@ zapp_binary(
main = "src/python/lilith/__main__.py", main = "src/python/lilith/__main__.py",
deps = [ deps = [
":lib", ":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"), py_requirement("prompt_toolkit"),
], ],
) )

View file

@ -1,4 +1,4 @@
!def[pitch, frag[lang: md]] !def[pitch, md[]]
# The Lilith Pitch # The Lilith Pitch
Code is more than .. just code for the compiler. 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?) 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] !def[sytnax, md[]]
!frag[lang: md]
\![] bang-brackets is a meta-syntactic directive used both by the lil tangler and the lil object language. \![] 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 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? ; is importing a bang-operation?
import[tagle] print[str.join["", [pitch, syntax]]]
print[str.join["", list[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 import traceback
from lilith.interpreter import Bindings, eval, Runtime from lilith.interpreter import Bindings, eval, Runtime
from lilith.parser import Apply, Args, parse_expr from lilith.parser import Apply, Args, parse_expr, Symbol
from lilith.reader import Module 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
from prompt_toolkit.formatted_text import FormattedText from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.history import FileHistory from prompt_toolkit.history import FileHistory
from prompt_toolkit.styles import Style from prompt_toolkit.styles import Style
import yaml
STYLE = Style.from_dict( STYLE = Style.from_dict(
@ -27,17 +30,13 @@ def print_(fmt, **kwargs):
print_formatted_text(FormattedText(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")) session = PromptSession(history=FileHistory(".lilith.history"))
runtime = Runtime("test", dict())
module = Module( module = Module(
"__repl__", "__repl__",
{ {},
"print": print,
"int": int,
"string": str,
"float": float,
},
) )
while True: while True:
@ -60,3 +59,88 @@ if __name__ == "__main__":
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
continue 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 import typing as t
from .parser import Apply, Block, Symbol from lilith.parser import Apply, Block, Symbol
from .reader import Module from lilith.reader import Module
class Runtime(t.NamedTuple): class Runtime(t.NamedTuple):
name: str name: str
modules: t.Dict[str, Module] prelude: Symbol
modules: t.Dict[Symbol, Module]
class BindingNotFound(KeyError): 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 logging
import typing as t import typing as t
from .parser import Block, Args, parse_buffer, Symbol
from warnings import warn from warnings import warn
from lilith.parser import Args, Block, parse_buffer, Symbol
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Import(t.NamedTuple):
src: Symbol
names: t.Dict[Symbol, Symbol]
wild: bool = False
class Module(t.NamedTuple): class Module(t.NamedTuple):
name: str name: Symbol
imports: t.List[Import]
defs: t.Dict[str, Block] defs: t.Dict[str, Block]
def read_buffer(buffer: str, name: str = "&buff") -> Module: def read_buffer(buffer: str, name: str = "&buff") -> Module:
"""Read a module out of a string [or file]""" """Read a module out of a string [or file]"""
m = Module(name, {}) m = Module(Symbol(name), [], {})
for block in parse_buffer(buffer, name): for block in parse_buffer(buffer, name):
if block.app.target == Symbol("def"): if block.app.target == Symbol("def"):
if len(block.args.positionals) == 2: if len(block.args.positionals) == 2:
@ -30,6 +39,20 @@ def read_buffer(buffer: str, name: str = "&buff") -> Module:
if block.args.kwargs: if block.args.kwargs:
warn("!def[<kwargs>] are ignored") 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: else:
raise SyntaxError(f"Unsupported block !{block.tag}[..]") raise SyntaxError(f"Unsupported block !{block.tag}[..]")

View file

@ -2,8 +2,7 @@
Pytest fixtures. Pytest fixtures.
""" """
from lilith.parser import Block, parser_with_transformer, GRAMMAR from lilith.parser import Block, GRAMMAR, parser_with_transformer
import pytest 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.reader import Module
from lilith.parser import Args, Apply, Symbol
import pytest import pytest
@pytest.fixture
def runtime():
return Runtime("test", None, {})
@pytest.mark.parametrize( @pytest.mark.parametrize(
"expr, expected", "expr, expected",
[ [
@ -17,11 +21,11 @@ import pytest
({"foo": "bar"}, {"foo": "bar"}), ({"foo": "bar"}, {"foo": "bar"}),
], ],
) )
def test_eval(expr, expected): def test_eval(runtime, expr, expected):
assert ( assert (
eval( eval(
Runtime("test", dict()), runtime,
Module("__repl__", dict()), Module("__repl__", [], dict()),
Bindings("__root__", None), Bindings("__root__", None),
expr, expr,
) )
@ -29,11 +33,11 @@ def test_eval(expr, expected):
) )
def test_hello_world(capsys): def test_hello_world(capsys, runtime):
assert ( assert (
eval( eval(
Runtime("test", {}), runtime,
Module("__repl__", {Symbol("print"): print}), Module("__repl__", [], {Symbol("print"): print}),
Bindings("__root__", None), Bindings("__root__", None),
Apply(Symbol("print"), Args(["hello, world"], {})), Apply(Symbol("print"), Args(["hello, world"], {})),
) )

View file

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

View file

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

View file

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