diff --git a/README.md b/README.md index 7d87f48..764befa 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,7 @@ To run the test suite, `pytest` is recommended, and can be done via: ```bash pytest -v --maxfail=1 larktools/tests/test_suite.py ``` + +### Debugging + +For grammar development using an alternative, less optimized parsing strategy can help avoiding rule conflicts: Use `earley` instead of `lalr` diff --git a/src/larktools/ebnf_grammar.py b/src/larktools/ebnf_grammar.py index 7227a60..a75dc55 100644 --- a/src/larktools/ebnf_grammar.py +++ b/src/larktools/ebnf_grammar.py @@ -15,7 +15,7 @@ start: assign_var - assign_var: VARNAME "=" arith_expr + assign_var: VARNAME "=" arith_expr variable: VARNAME ("[" INDEX "]")* VARNAME: LETTER (LETTER | DIGIT)* @@ -26,6 +26,11 @@ // but without the fancy tree shaping directives explained at // https://lark-parser.readthedocs.io/en/stable/tree_construction.html + + line: arith_expr + + multi_line_block: (line _NL? | _NL )* + arith_expr: sum sum: product | addition | subtraction addition: sum "+" product @@ -57,4 +62,5 @@ %import common.WS_INLINE %ignore WS_INLINE + %import common.NEWLINE -> _NL """ diff --git a/src/larktools/evaluation.py b/src/larktools/evaluation.py index eceec1c..3bb5784 100644 --- a/src/larktools/evaluation.py +++ b/src/larktools/evaluation.py @@ -109,6 +109,22 @@ def eval_bracketed_arith_expr(node, env): assert get_name(child) == "arith_expr" return eval_arith_expr(child, env) +def eval_line(node, env): + # this is the content of a single line of input + child = get_children(node)[0] + child_name = get_name(child) + if child_name == "arith_expr": + return eval_arith_expr(child, env) + +def eval_multi_line_block(node, env): + # this can be either an arithmetic expression or + # composed lines + children = get_children(node) + for child in children: + child_name = get_name(child) + assert child_name == "line" + res = eval_line(child, env) + return res def eval_variable(node, env): children = get_children(node) diff --git a/tests/test_syntax.py b/tests/test_syntax.py new file mode 100644 index 0000000..b158516 --- /dev/null +++ b/tests/test_syntax.py @@ -0,0 +1,32 @@ +import pytest +from typing import Optional, Union + +from lark import Lark + +from larktools.ebnf_grammar import grammar +from larktools.evaluation import eval_multi_line_block + + +class SyntaxParser: + def __init__(self): + self.parser = Lark(grammar, parser="lalr", start="multi_line_block") + self.parse = self.parser.parse + + def parse_and_eval(self, expression: str, env: Optional[Union[None, dict]] = None) -> Union[int, float]: + tree = self.parse(expression) + res = eval_multi_line_block(tree, {} if env is None else env) + return res + + +def _parse_and_assert(expression: str, expected: Union[int, float]) -> None: + parser = SyntaxParser() + res = parser.parse_and_eval(expression) + assert expected == res + +def test_multi_line(): + _parse_and_assert("5\n8", 8) + _parse_and_assert("\n\n\n8", 8) + _parse_and_assert("8\n\n\n", 8) + _parse_and_assert("5+5\n3+4\n1+2", 3) + _parse_and_assert("\n\n5\n\n3\n8", 8) +