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