Get symbols working, styleguide

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

View file

@ -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 <expr> <args> <kwargs>).
# 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}")

View file

@ -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):

View file

@ -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)

View file

@ -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__", {
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)

View file

@ -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")

View file

@ -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', [
@pytest.mark.parametrize(
"expr, expected",
[
(1, 1),
([1, 2], [1, 2]),
({"foo": "bar"}, {"foo": "bar"}),
])
],
)
def test_eval(expr, expected):
assert eval(
assert (
eval(
Runtime("test", dict()),
Module("__repl__", dict()),
Bindings("__root__", None),
expr
) == expected
expr,
)
== expected
)
def test_hello_world(capsys):
assert eval(
assert (
eval(
Runtime("test", {}),
Module("__repl__", {"print": print}),
Module("__repl__", {Symbol("print"): print}),
Bindings("__root__", None),
Apply("print", Args(["hello, world"], {}))
) is None
Apply(Symbol("print"), Args(["hello, world"], {})),
)
is None
)
captured = capsys.readouterr()
assert captured.out == "hello, world\n"

View file

@ -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', [
@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', [
@pytest.mark.parametrize(
"example, expected",
[
("foo: bar", {Symbol("foo"): Symbol("bar")}),
("foo: bar, baz: qux", {Symbol("foo"): Symbol("bar"), Symbol("baz"): Symbol("qux")}),
])
(
"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', [
@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")})),
])
("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

View file

@ -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