Somewhat working launcher with prelude!
This commit is contained in:
parent
d2309e1ac4
commit
8dd071625f
11 changed files with 284 additions and 41 deletions
|
@ -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"),
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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]]]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
129
projects/lilith/src/python/lilith/prelude.lil
Normal file
129
projects/lilith/src/python/lilith/prelude.lil
Normal 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
|
|
@ -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}[..]")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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"], {})),
|
||||
)
|
||||
|
|
|
@ -9,7 +9,6 @@ from lilith.parser import (
|
|||
parser_with_transformer,
|
||||
Symbol,
|
||||
)
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -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")], {})),
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue