Get symbols working, styleguide
This commit is contained in:
parent
7e1e8b2ad4
commit
8db42aa17d
8 changed files with 181 additions and 102 deletions
|
@ -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}")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue