diff --git a/projects/lilith/src/python/lilith/interpreter.py b/projects/lilith/src/python/lilith/interpreter.py index 50f4f6a..a4b0c9d 100644 --- a/projects/lilith/src/python/lilith/interpreter.py +++ b/projects/lilith/src/python/lilith/interpreter.py @@ -4,7 +4,7 @@ import typing as t -from .parser import Apply, Block +from .parser import Apply, Block, Symbol from .reader import Module @@ -49,8 +49,7 @@ def lookup(runtime, mod, locals, name): if name in mod.defs: return mod.defs.get(name) - else: - raise err + raise err or KeyError # FIXME (arrdem 2021-08-21): # How do we ever get references to stuff in other modules? @@ -68,11 +67,14 @@ def eval(ctx: Runtime, mod: Module, locals: Bindings, expr): # Pointedly not assert that the module is ACTUALLY in the runtime, # We're just going to assume this for convenience. - if isinstance(expr, Apply): + if isinstance(expr, Symbol): + return lookup(ctx, mod, locals, expr) + + elif isinstance(expr, Apply): # FIXME (arrdem 2021-08-21): # Apply should be (apply ). # Now no distinction is made between strings ("") and symbols/barewords - fun = lookup(ctx, mod, locals, expr.name) + fun = eval(ctx, mod, locals, expr.target) # Evaluate the parameters args = eval(ctx, mod, locals, expr.args.positionals) kwargs = eval(ctx, mod, locals, expr.args.kwargs) @@ -89,8 +91,10 @@ def eval(ctx: Runtime, mod: Module, locals: Bindings, expr): return tuple(eval(ctx, mod, locals, i) for i in expr) elif isinstance(expr, dict): - return {eval(ctx, mod, locals, k): eval(ctx, mod, locals, v) - for k, v in expr.items()} + return { + eval(ctx, mod, locals, k): eval(ctx, mod, locals, v) + for k, v in expr.items() + } else: raise RuntimeError(f"Can't eval {expr}") diff --git a/projects/lilith/src/python/lilith/parser.py b/projects/lilith/src/python/lilith/parser.py index 844564d..fb48391 100644 --- a/projects/lilith/src/python/lilith/parser.py +++ b/projects/lilith/src/python/lilith/parser.py @@ -3,12 +3,11 @@ Variously poor parsing for Lilith. """ import typing as t -import re from importlib.resources import read_text -import lark +from lark import Lark, v_args, Transformer -GRAMMAR = read_text('lilith', 'grammar.lark') +GRAMMAR = read_text("lilith", "grammar.lark") # !foo[bar] @@ -26,7 +25,7 @@ class Args(t.NamedTuple): class Apply(t.NamedTuple): - name: Symbol + target: object args: Args @@ -47,19 +46,20 @@ class Block(t.NamedTuple): return "\n".join(self.body_lines) +id = lambda x: x + + class TreeToTuples(Transformer): @v_args(inline=True) def string(self, s): return s[1:-1].replace('\\"', '"') - def int(self, args): - return int(args[0]) - - def float(self, args): - return float(args[0]) - - def number(self, args): - return args[0] + null = lambda self, _: None + true = lambda self, _: True + false = lambda self, _: False + int = v_args(inline=True)(lambda self, x: int(x)) + float = v_args(inline=True)(lambda self, x: float(x)) + number = v_args(inline=True)(lambda self, x: x) def word(self, args): """args: ['a'] ['a' ['b', 'c', 'd']]""" @@ -107,11 +107,7 @@ class TreeToTuples(Transformer): def parser_with_transformer(grammar, start="header"): - return lark.Lark(grammar, - start=start, - parser='lalr', - transformer=TreeToTuples()) - + return Lark(grammar, start=start, parser="lalr", transformer=TreeToTuples()) def parse_expr(buff: str): diff --git a/projects/lilith/src/python/lilith/reader.py b/projects/lilith/src/python/lilith/reader.py index c3ffc5c..b676837 100644 --- a/projects/lilith/src/python/lilith/reader.py +++ b/projects/lilith/src/python/lilith/reader.py @@ -4,11 +4,12 @@ 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 +from .parser import Block, Args, parse_buffer, Symbol from warnings import warn log = logging.getLogger(__name__) + class Module(t.NamedTuple): name: str defs: t.Dict[str, Block] @@ -19,7 +20,7 @@ def read_buffer(buffer: str, name: str = "&buff") -> Module: m = Module(name, {}) for block in parse_buffer(buffer, name): - if block.app.name == "def": + if block.app.target == Symbol("def"): if len(block.args.positionals) == 2: def_name, expr = block.args.positionals m.defs[def_name] = Block(expr, block.body_lines) diff --git a/projects/lilith/src/python/lilith/repl.py b/projects/lilith/src/python/lilith/repl.py index 4c173b4..bb4ff66 100644 --- a/projects/lilith/src/python/lilith/repl.py +++ b/projects/lilith/src/python/lilith/repl.py @@ -20,6 +20,7 @@ STYLE = Style.from_dict( } ) + def print_(fmt, **kwargs): print_formatted_text(FormattedText(fmt), **kwargs) @@ -27,12 +28,15 @@ def print_(fmt, **kwargs): if __name__ == "__main__": session = PromptSession(history=FileHistory(".lilith.history")) runtime = Runtime("test", dict()) - module = Module("__repl__", { - "print": print, - "int": int, - "string": str, - "float": float, - }) + module = Module( + "__repl__", + { + "print": print, + "int": int, + "string": str, + "float": float, + }, + ) while True: try: @@ -49,11 +53,7 @@ if __name__ == "__main__": continue try: - result = eval( - runtime, module, - Bindings("__root__", None), - expr - ) + result = eval(runtime, module, Bindings("__root__", None), expr) print_([("class:result", f"⇒ {result!r}")], style=STYLE) except Exception as e: print(e) diff --git a/projects/lilith/test/python/conftest.py b/projects/lilith/test/python/conftest.py index cbf694b..7be6349 100644 --- a/projects/lilith/test/python/conftest.py +++ b/projects/lilith/test/python/conftest.py @@ -21,6 +21,7 @@ def kwargs_grammar(): def arguments_grammar(): return parser_with_transformer(GRAMMAR, "arguments") + @pytest.fixture def header_grammar(): return parser_with_transformer(GRAMMAR, "header") diff --git a/projects/lilith/test/python/test_interpreter.py b/projects/lilith/test/python/test_interpreter.py index 3b89daf..3351c18 100644 --- a/projects/lilith/test/python/test_interpreter.py +++ b/projects/lilith/test/python/test_interpreter.py @@ -4,31 +4,40 @@ from lilith.interpreter import Bindings, Runtime, eval from lilith.reader import Module -from lilith.parser import Args, Apply +from lilith.parser import Args, Apply, Symbol import pytest -@pytest.mark.parametrize('expr, expected', [ - (1, 1), - ([1, 2], [1, 2]), - ({"foo": "bar"}, {"foo": "bar"}), -]) +@pytest.mark.parametrize( + "expr, expected", + [ + (1, 1), + ([1, 2], [1, 2]), + ({"foo": "bar"}, {"foo": "bar"}), + ], +) def test_eval(expr, expected): - assert eval( - Runtime("test", dict()), - Module("__repl__", dict()), - Bindings("__root__", None), - expr - ) == expected + assert ( + eval( + Runtime("test", dict()), + Module("__repl__", dict()), + Bindings("__root__", None), + expr, + ) + == expected + ) def test_hello_world(capsys): - assert eval( - Runtime("test", {}), - Module("__repl__", {"print": print}), - Bindings("__root__", None), - Apply("print", Args(["hello, world"], {})) - ) is None + assert ( + eval( + Runtime("test", {}), + Module("__repl__", {Symbol("print"): print}), + Bindings("__root__", None), + Apply(Symbol("print"), Args(["hello, world"], {})), + ) + is None + ) captured = capsys.readouterr() assert captured.out == "hello, world\n" diff --git a/projects/lilith/test/python/test_parser.py b/projects/lilith/test/python/test_parser.py index cc4514d..0ceca97 100644 --- a/projects/lilith/test/python/test_parser.py +++ b/projects/lilith/test/python/test_parser.py @@ -1,66 +1,121 @@ """tests covering the Lilith parser.""" -from lilith.parser import Apply, Args, Block, GRAMMAR, parse_buffer, parser_with_transformer, Symbol +from lilith.parser import ( + Apply, + Args, + Block, + GRAMMAR, + parse_buffer, + parser_with_transformer, + Symbol, +) import pytest -@pytest.mark.parametrize('example, expected', [ - ("1", [1]), - ("1, 2", [1, 2]), - ("1, 2, 3", [1, 2, 3]), -]) +@pytest.mark.parametrize( + "example, expected", + [ + ("1", [1]), + ("1, 2", [1, 2]), + ("1, 2, 3", [1, 2, 3]), + ], +) def test_parse_args(args_grammar, example, expected): assert args_grammar.parse(example) == expected -@pytest.mark.parametrize('example, expected', [ - ("foo: bar", {Symbol("foo"): Symbol("bar")}), - ("foo: bar, baz: qux", {Symbol("foo"): Symbol("bar"), Symbol("baz"): Symbol("qux")}), -]) +@pytest.mark.parametrize( + "example, expected", + [ + ("foo: bar", {Symbol("foo"): Symbol("bar")}), + ( + "foo: bar, baz: qux", + {Symbol("foo"): Symbol("bar"), Symbol("baz"): Symbol("qux")}, + ), + ], +) def test_parse_kwargs(kwargs_grammar, example, expected): assert kwargs_grammar.parse(example) == expected -@pytest.mark.parametrize('example, expected', [ - ("1", ([1], {})), - ("1, 2", ([1, 2], {})), - ("1, 2, 3", ([1, 2, 3], {})), - ("foo: bar", - ([], {Symbol("foo"): Symbol("bar")})), - ("foo: bar, baz: qux", - ([], {Symbol("foo"): Symbol("bar"), - Symbol("baz"): Symbol("qux")})), - ("1; foo: bar, baz: qux", - ([1], {Symbol("foo"): Symbol("bar"), - Symbol("baz"): Symbol("qux")})), -]) +@pytest.mark.parametrize( + "example, expected", + [ + ("1", ([1], {})), + ("1, 2", ([1, 2], {})), + ("1, 2, 3", ([1, 2, 3], {})), + ("foo: bar", ([], {Symbol("foo"): Symbol("bar")})), + ( + "foo: bar, baz: qux", + ([], {Symbol("foo"): Symbol("bar"), Symbol("baz"): Symbol("qux")}), + ), + ( + "1; foo: bar, baz: qux", + ([1], {Symbol("foo"): Symbol("bar"), Symbol("baz"): Symbol("qux")}), + ), + ], +) def test_parse_arguments(arguments_grammar, example, expected): assert arguments_grammar.parse(example) == expected -@pytest.mark.parametrize('example, expected', [ - ('!def[syntax]', - Block(Apply(Symbol('def'), Args(['syntax'], {})), [])), - ('!frag[lang: md]', - Block(Apply(Symbol('frag'), Args([], {'lang': 'md'})), [])), - ('!frag[foo; lang: md]', - Block(Apply(Symbol('frag'), Args(['foo'], {'lang': 'md'})), [])), - ("!int.add[1, 2]", - Block(Apply(Symbol('int.add'), Args([1, 2], {})), [])), -]) +@pytest.mark.parametrize( + "example, expected", + [ + ("!def[syntax]", Block(Apply(Symbol("def"), Args([Symbol("syntax")], {})), [])), + ( + "!frag[lang: md]", + Block(Apply(Symbol("frag"), Args([], {Symbol("lang"): Symbol("md")})), []), + ), + ( + "!frag[foo; lang: md]", + Block( + Apply( + Symbol("frag"), + Args([Symbol("foo")], {Symbol("lang"): Symbol("md")}), + ), + [], + ), + ), + ("!int.add[1, 2]", Block(Apply(Symbol("int.add"), Args([1, 2], {})), [])), + ], +) def test_parse_header(header_grammar, example, expected): assert header_grammar.parse(example) == expected -@pytest.mark.parametrize('example, expected', [ - ("!frag[lang: md]", - [Block(Apply(Symbol('frag'), Args([], {Symbol("lang"): Symbol("md")})), [])]), - ("""!frag[lang: md]\nHello, world!\n\n""", - [Block(Apply(Symbol('frag'), Args([], {Symbol("lang"): Symbol("md")})), ["Hello, world!", ""])]), - ("""!frag[lang: md]\nHello, world!\n\n!def[bar]""", - [Block(Apply(Symbol('frag'), Args([], {Symbol("lang"): Symbol("md")})), ["Hello, world!", ""]), - Block(Apply(Symbol('def'), Args([Symbol("bar")], {})), [])]), -]) +@pytest.mark.parametrize( + "example, expected", + [ + ( + "!frag[lang: md]", + [ + Block( + Apply(Symbol("frag"), Args([], {Symbol("lang"): Symbol("md")})), [] + ) + ], + ), + ( + """!frag[lang: md]\nHello, world!\n\n""", + [ + Block( + Apply(Symbol("frag"), Args([], {Symbol("lang"): Symbol("md")})), + ["Hello, world!", ""], + ) + ], + ), + ( + """!frag[lang: md]\nHello, world!\n\n!def[bar]""", + [ + Block( + Apply(Symbol("frag"), Args([], {Symbol("lang"): Symbol("md")})), + ["Hello, world!", ""], + ), + Block(Apply(Symbol("def"), Args([Symbol("bar")], {})), []), + ], + ), + ], +) def test_block_parser(example, expected): assert parse_buffer(example) == expected diff --git a/projects/lilith/test/python/test_reader.py b/projects/lilith/test/python/test_reader.py index efab4ea..4ccb1af 100644 --- a/projects/lilith/test/python/test_reader.py +++ b/projects/lilith/test/python/test_reader.py @@ -1,15 +1,28 @@ """Tests covering the reader.""" -from lilith.parser import Apply, Args, Block +from lilith.parser import Apply, Args, Block, Symbol from lilith.reader import Module, read_buffer import pytest -@pytest.mark.parametrize('example, expected', [ - ("""!def[main, lang[lil]]\nprint["hello, world"]\n""", - Module("&buff", {"main": Block(Apply('lang', Args(["lil"], {})), ["print[\"hello, world\"]"])})) -]) +@pytest.mark.parametrize( + "example, expected", + [ + ( + """!def[main, lang[lil]]\nprint["hello, world"]\n""", + Module( + "&buff", + { + Symbol("main"): Block( + Apply(Symbol("lang"), Args([Symbol("lil")], {})), + ['print["hello, world"]'], + ) + }, + ), + ) + ], +) def test_read(example, expected): got = read_buffer(example) assert got == expected