Get symbols working, styleguide

This commit is contained in:
Reid 'arrdem' McKenzie 2021-08-21 16:58:59 -06:00
parent 7e1e8b2ad4
commit 8db42aa17d
8 changed files with 181 additions and 102 deletions

View file

@ -4,7 +4,7 @@
import typing as t import typing as t
from .parser import Apply, Block from .parser import Apply, Block, Symbol
from .reader import Module from .reader import Module
@ -49,8 +49,7 @@ def lookup(runtime, mod, locals, name):
if name in mod.defs: if name in mod.defs:
return mod.defs.get(name) return mod.defs.get(name)
else: raise err or KeyError
raise err
# FIXME (arrdem 2021-08-21): # FIXME (arrdem 2021-08-21):
# How do we ever get references to stuff in other modules? # 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, # 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.
if isinstance(expr, Apply): if isinstance(expr, Symbol):
return lookup(ctx, mod, locals, expr)
elif isinstance(expr, Apply):
# FIXME (arrdem 2021-08-21): # FIXME (arrdem 2021-08-21):
# Apply should be (apply <expr> <args> <kwargs>). # Apply should be (apply <expr> <args> <kwargs>).
# Now no distinction is made between strings ("") and symbols/barewords # 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 # Evaluate the parameters
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)
@ -89,8 +91,10 @@ def eval(ctx: Runtime, mod: Module, locals: Bindings, expr):
return tuple(eval(ctx, mod, locals, i) for i in expr) return tuple(eval(ctx, mod, locals, i) for i in expr)
elif isinstance(expr, dict): elif isinstance(expr, dict):
return {eval(ctx, mod, locals, k): eval(ctx, mod, locals, v) return {
for k, v in expr.items()} eval(ctx, mod, locals, k): eval(ctx, mod, locals, v)
for k, v in expr.items()
}
else: else:
raise RuntimeError(f"Can't eval {expr}") raise RuntimeError(f"Can't eval {expr}")

View file

@ -3,12 +3,11 @@ Variously poor parsing for Lilith.
""" """
import typing as t import typing as t
import re
from importlib.resources import read_text 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] # !foo[bar]
@ -26,7 +25,7 @@ class Args(t.NamedTuple):
class Apply(t.NamedTuple): class Apply(t.NamedTuple):
name: Symbol target: object
args: Args args: Args
@ -47,19 +46,20 @@ class Block(t.NamedTuple):
return "\n".join(self.body_lines) return "\n".join(self.body_lines)
id = lambda x: x
class TreeToTuples(Transformer): class TreeToTuples(Transformer):
@v_args(inline=True) @v_args(inline=True)
def string(self, s): def string(self, s):
return s[1:-1].replace('\\"', '"') return s[1:-1].replace('\\"', '"')
def int(self, args): null = lambda self, _: None
return int(args[0]) true = lambda self, _: True
false = lambda self, _: False
def float(self, args): int = v_args(inline=True)(lambda self, x: int(x))
return float(args[0]) float = v_args(inline=True)(lambda self, x: float(x))
number = v_args(inline=True)(lambda self, x: x)
def number(self, args):
return args[0]
def word(self, args): def word(self, args):
"""args: ['a'] ['a' ['b', 'c', 'd']]""" """args: ['a'] ['a' ['b', 'c', 'd']]"""
@ -107,11 +107,7 @@ class TreeToTuples(Transformer):
def parser_with_transformer(grammar, start="header"): def parser_with_transformer(grammar, start="header"):
return lark.Lark(grammar, return Lark(grammar, start=start, parser="lalr", transformer=TreeToTuples())
start=start,
parser='lalr',
transformer=TreeToTuples())
def parse_expr(buff: str): def parse_expr(buff: str):

View file

@ -4,11 +4,12 @@ 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 from .parser import Block, Args, parse_buffer, Symbol
from warnings import warn from warnings import warn
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Module(t.NamedTuple): class Module(t.NamedTuple):
name: str name: str
defs: t.Dict[str, Block] defs: t.Dict[str, Block]
@ -19,7 +20,7 @@ def read_buffer(buffer: str, name: str = "&buff") -> Module:
m = Module(name, {}) m = Module(name, {})
for block in parse_buffer(buffer, 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: if len(block.args.positionals) == 2:
def_name, expr = block.args.positionals def_name, expr = block.args.positionals
m.defs[def_name] = Block(expr, block.body_lines) m.defs[def_name] = Block(expr, block.body_lines)

View file

@ -20,6 +20,7 @@ STYLE = Style.from_dict(
} }
) )
def print_(fmt, **kwargs): def print_(fmt, **kwargs):
print_formatted_text(FormattedText(fmt), **kwargs) print_formatted_text(FormattedText(fmt), **kwargs)
@ -27,12 +28,15 @@ def print_(fmt, **kwargs):
if __name__ == "__main__": if __name__ == "__main__":
session = PromptSession(history=FileHistory(".lilith.history")) session = PromptSession(history=FileHistory(".lilith.history"))
runtime = Runtime("test", dict()) runtime = Runtime("test", dict())
module = Module("__repl__", { module = Module(
"print": print, "__repl__",
"int": int, {
"string": str, "print": print,
"float": float, "int": int,
}) "string": str,
"float": float,
},
)
while True: while True:
try: try:
@ -49,11 +53,7 @@ if __name__ == "__main__":
continue continue
try: try:
result = eval( result = eval(runtime, module, Bindings("__root__", None), expr)
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:
print(e) print(e)

View file

@ -21,6 +21,7 @@ def kwargs_grammar():
def arguments_grammar(): def arguments_grammar():
return parser_with_transformer(GRAMMAR, "arguments") return parser_with_transformer(GRAMMAR, "arguments")
@pytest.fixture @pytest.fixture
def header_grammar(): def header_grammar():
return parser_with_transformer(GRAMMAR, "header") return parser_with_transformer(GRAMMAR, "header")

View file

@ -4,31 +4,40 @@
from lilith.interpreter import Bindings, Runtime, eval from lilith.interpreter import Bindings, Runtime, eval
from lilith.reader import Module from lilith.reader import Module
from lilith.parser import Args, Apply from lilith.parser import Args, Apply, Symbol
import pytest import pytest
@pytest.mark.parametrize('expr, expected', [ @pytest.mark.parametrize(
(1, 1), "expr, expected",
([1, 2], [1, 2]), [
({"foo": "bar"}, {"foo": "bar"}), (1, 1),
]) ([1, 2], [1, 2]),
({"foo": "bar"}, {"foo": "bar"}),
],
)
def test_eval(expr, expected): def test_eval(expr, expected):
assert eval( assert (
Runtime("test", dict()), eval(
Module("__repl__", dict()), Runtime("test", dict()),
Bindings("__root__", None), Module("__repl__", dict()),
expr Bindings("__root__", None),
) == expected expr,
)
== expected
)
def test_hello_world(capsys): def test_hello_world(capsys):
assert eval( assert (
Runtime("test", {}), eval(
Module("__repl__", {"print": print}), Runtime("test", {}),
Bindings("__root__", None), Module("__repl__", {Symbol("print"): print}),
Apply("print", Args(["hello, world"], {})) Bindings("__root__", None),
) is None Apply(Symbol("print"), Args(["hello, world"], {})),
)
is None
)
captured = capsys.readouterr() captured = capsys.readouterr()
assert captured.out == "hello, world\n" assert captured.out == "hello, world\n"

View file

@ -1,66 +1,121 @@
"""tests covering the Lilith parser.""" """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 import pytest
@pytest.mark.parametrize('example, expected', [ @pytest.mark.parametrize(
("1", [1]), "example, expected",
("1, 2", [1, 2]), [
("1, 2, 3", [1, 2, 3]), ("1", [1]),
]) ("1, 2", [1, 2]),
("1, 2, 3", [1, 2, 3]),
],
)
def test_parse_args(args_grammar, example, expected): def test_parse_args(args_grammar, example, expected):
assert args_grammar.parse(example) == expected assert args_grammar.parse(example) == expected
@pytest.mark.parametrize('example, expected', [ @pytest.mark.parametrize(
("foo: bar", {Symbol("foo"): Symbol("bar")}), "example, expected",
("foo: bar, baz: qux", {Symbol("foo"): Symbol("bar"), Symbol("baz"): Symbol("qux")}), [
]) ("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): def test_parse_kwargs(kwargs_grammar, example, expected):
assert kwargs_grammar.parse(example) == expected assert kwargs_grammar.parse(example) == expected
@pytest.mark.parametrize('example, expected', [ @pytest.mark.parametrize(
("1", ([1], {})), "example, expected",
("1, 2", ([1, 2], {})), [
("1, 2, 3", ([1, 2, 3], {})), ("1", ([1], {})),
("foo: bar", ("1, 2", ([1, 2], {})),
([], {Symbol("foo"): Symbol("bar")})), ("1, 2, 3", ([1, 2, 3], {})),
("foo: bar, baz: qux", ("foo: bar", ([], {Symbol("foo"): Symbol("bar")})),
([], {Symbol("foo"): Symbol("bar"), (
Symbol("baz"): Symbol("qux")})), "foo: bar, baz: qux",
("1; foo: bar, baz: qux", ([], {Symbol("foo"): Symbol("bar"), Symbol("baz"): Symbol("qux")}),
([1], {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): def test_parse_arguments(arguments_grammar, example, expected):
assert arguments_grammar.parse(example) == expected assert arguments_grammar.parse(example) == expected
@pytest.mark.parametrize('example, expected', [ @pytest.mark.parametrize(
('!def[syntax]', "example, expected",
Block(Apply(Symbol('def'), Args(['syntax'], {})), [])), [
('!frag[lang: md]', ("!def[syntax]", Block(Apply(Symbol("def"), Args([Symbol("syntax")], {})), [])),
Block(Apply(Symbol('frag'), Args([], {'lang': 'md'})), [])), (
('!frag[foo; lang: md]', "!frag[lang: md]",
Block(Apply(Symbol('frag'), Args(['foo'], {'lang': 'md'})), [])), Block(Apply(Symbol("frag"), Args([], {Symbol("lang"): Symbol("md")})), []),
("!int.add[1, 2]", ),
Block(Apply(Symbol('int.add'), Args([1, 2], {})), [])), (
]) "!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): def test_parse_header(header_grammar, example, expected):
assert header_grammar.parse(example) == expected assert header_grammar.parse(example) == expected
@pytest.mark.parametrize('example, expected', [ @pytest.mark.parametrize(
("!frag[lang: md]", "example, expected",
[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]",
("""!frag[lang: md]\nHello, world!\n\n!def[bar]""", [
[Block(Apply(Symbol('frag'), Args([], {Symbol("lang"): Symbol("md")})), ["Hello, world!", ""]), Block(
Block(Apply(Symbol('def'), Args([Symbol("bar")], {})), [])]), 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): def test_block_parser(example, expected):
assert parse_buffer(example) == expected assert parse_buffer(example) == expected

View file

@ -1,15 +1,28 @@
"""Tests covering the reader.""" """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 from lilith.reader import Module, read_buffer
import pytest import pytest
@pytest.mark.parametrize('example, expected', [ @pytest.mark.parametrize(
("""!def[main, lang[lil]]\nprint["hello, world"]\n""", "example, expected",
Module("&buff", {"main": Block(Apply('lang', Args(["lil"], {})), ["print[\"hello, world\"]"])})) [
]) (
"""!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): def test_read(example, expected):
got = read_buffer(example) got = read_buffer(example)
assert got == expected assert got == expected