diff --git a/bnf/apyds_bnf/__init__.py b/bnf/apyds_bnf/__init__.py index a6ba0778..5c35f594 100644 --- a/bnf/apyds_bnf/__init__.py +++ b/bnf/apyds_bnf/__init__.py @@ -1,6 +1,7 @@ __all__ = ["parse", "unparse"] from antlr4 import InputStream, CommonTokenStream +from antlr4.error.ErrorListener import ErrorListener from .DspLexer import DspLexer from .DspParser import DspParser from .DspVisitor import DspVisitor @@ -9,6 +10,11 @@ from .DsVisitor import DsVisitor +class ThrowingErrorListener(ErrorListener): + def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): + raise Exception(f"line {line}:{column} {msg}") + + class ParseVisitor(DspVisitor): def visitRule_pool(self, ctx): return "\n\n".join(self.visit(r) for r in ctx.rule_()) @@ -71,8 +77,12 @@ def visitBinary(self, ctx): def parse(input: str) -> str: chars = InputStream(input) lexer = DspLexer(chars) + lexer.removeErrorListeners() + lexer.addErrorListener(ThrowingErrorListener()) tokens = CommonTokenStream(lexer) parser = DspParser(tokens) + parser.removeErrorListeners() + parser.addErrorListener(ThrowingErrorListener()) tree = parser.rule_pool() visitor = ParseVisitor() return visitor.visit(tree) @@ -81,8 +91,12 @@ def parse(input: str) -> str: def unparse(input: str) -> str: chars = InputStream(input) lexer = DsLexer(chars) + lexer.removeErrorListeners() + lexer.addErrorListener(ThrowingErrorListener()) tokens = CommonTokenStream(lexer) parser = DsParser(tokens) + parser.removeErrorListeners() + parser.addErrorListener(ThrowingErrorListener()) tree = parser.rule_pool() visitor = UnparseVisitor() return visitor.visit(tree) diff --git a/bnf/tests/test_parse_unparse.py b/bnf/tests/test_parse_unparse.py index 0b3e039b..eef8d491 100644 --- a/bnf/tests/test_parse_unparse.py +++ b/bnf/tests/test_parse_unparse.py @@ -1,3 +1,4 @@ +import pytest from apyds_bnf import parse, unparse @@ -143,3 +144,38 @@ def test_roundtrip_unparse_parse() -> None: dsp_intermediate = unparse(ds_original) ds_result = parse(dsp_intermediate) assert ds_result == ds_original + + +def test_parse_error_missing_closing_parenthesis() -> None: + """Test that parse throws error on missing closing parenthesis""" + dsp_input = "(a + b -> c" + with pytest.raises(Exception, match=r"line 1:7.*no viable alternative"): + parse(dsp_input) + + +def test_parse_error_bad_syntax() -> None: + """Test that parse throws error on bad syntax""" + dsp_input = "a b c -> -> d" + with pytest.raises(Exception, match=r"line 1:2.*mismatched input"): + parse(dsp_input) + + +def test_parse_error_malformed_parentheses() -> None: + """Test that parse throws error on malformed parentheses""" + dsp_input = "()()()" + with pytest.raises(Exception, match=r"line 1:1.*no viable alternative"): + parse(dsp_input) + + +def test_unparse_error_incomplete_binary() -> None: + """Test that unparse throws error on incomplete binary expression""" + ds_input = "(binary" + with pytest.raises(Exception, match=r"line 1:7.*mismatched input"): + unparse(ds_input) + + +def test_unparse_error_malformed_function() -> None: + """Test that unparse throws error on malformed function""" + ds_input = "(function" + with pytest.raises(Exception, match=r"line 1:9.*mismatched input"): + unparse(ds_input)