Browse Source

Improved load_grammar's error messages, and added tests

tags/gm/2021-09-23T00Z/github.com--lark-parser-lark/0.10.0
Erez Sh 4 years ago
parent
commit
fe89296193
4 changed files with 49 additions and 13 deletions
  1. +16
    -12
      lark/load_grammar.py
  2. +1
    -1
      lark/parsers/grammar_analysis.py
  3. +1
    -0
      tests/__main__.py
  4. +31
    -0
      tests/test_grammar.py

+ 16
- 12
lark/load_grammar.py View File

@@ -789,6 +789,20 @@ def _find_used_symbols(tree):
for t in x.scan_values(lambda t: t.type in ('RULE', 'TERMINAL'))}

class GrammarLoader:
ERRORS = {
'Unclosed parenthesis': ['a: (\n'],
'Umatched closing parenthesis': ['a: )\n', 'a: [)\n', 'a: (]\n'],
'Expecting rule or terminal definition (missing colon)': ['a\n', 'A\n', 'a->\n', 'A->\n', 'a A\n'],
'Illegal name for rules or terminals': ['Aa:\n'],
'Alias expects lowercase name': ['a: -> "a"\n'],
'Unexpected colon': ['a::\n', 'a: b:\n', 'a: B:\n', 'a: "a":\n'],
'Misplaced operator': ['a: b??', 'a: b(?)', 'a:+\n', 'a:?\n', 'a:*\n', 'a:|*\n'],
'Expecting option ("|") or a new rule or terminal definition': ['a:a\n()\n'],
'Terminal names cannot contain dots': ['A.B\n'],
'%import expects a name': ['%import "a"\n'],
'%ignore expects a value': ['%ignore %import\n'],
}

def __init__(self, re_module):
terminals = [TerminalDef(name, PatternRE(value)) for name, value in TERMINALS.items()]

@@ -814,19 +828,9 @@ class GrammarLoader:
(e.line, e.column, grammar_name, context))
except UnexpectedToken as e:
context = e.get_context(grammar_text)
error = e.match_examples(self.parser.parse, {
'Unclosed parenthesis': ['a: (\n'],
'Umatched closing parenthesis': ['a: )\n', 'a: [)\n', 'a: (]\n'],
'Expecting rule or terminal definition (missing colon)': ['a\n', 'a->\n', 'A->\n', 'a A\n'],
'Alias expects lowercase name': ['a: -> "a"\n'],
'Unexpected colon': ['a::\n', 'a: b:\n', 'a: B:\n', 'a: "a":\n'],
'Misplaced operator': ['a: b??', 'a: b(?)', 'a:+\n', 'a:?\n', 'a:*\n', 'a:|*\n'],
'Expecting option ("|") or a new rule or terminal definition': ['a:a\n()\n'],
'%import expects a name': ['%import "a"\n'],
'%ignore expects a value': ['%ignore %import\n'],
})
error = e.match_examples(self.parser.parse, self.ERRORS, use_accepts=True)
if error:
raise GrammarError("%s at line %s column %s\n\n%s" % (error, e.line, e.column, context))
raise GrammarError("%s, at line %s column %s\n\n%s" % (error, e.line, e.column, context))
elif 'STRING' in e.expected:
raise GrammarError("Expecting a value at line %s column %s\n\n%s" % (e.line, e.column, context))
raise


+ 1
- 1
lark/parsers/grammar_analysis.py View File

@@ -138,7 +138,7 @@ class GrammarAnalyzer(object):
for r in rules:
for sym in r.expansion:
if not (sym.is_term or sym in self.rules_by_origin):
raise GrammarError("Using an undefined rule: %s" % sym) # TODO test validation
raise GrammarError("Using an undefined rule: %s" % sym)

self.start_states = {start: self.expand_rule(root_rule.origin)
for start, root_rule in root_rules.items()}


+ 1
- 0
tests/__main__.py View File

@@ -7,6 +7,7 @@ from lark import logger
from .test_trees import TestTrees
from .test_tools import TestStandalone
from .test_cache import TestCache
from .test_grammar import TestGrammar
from .test_reconstructor import TestReconstructor

try:


+ 31
- 0
tests/test_grammar.py View File

@@ -0,0 +1,31 @@
from __future__ import absolute_import

import sys
from unittest import TestCase, main

from lark import Lark
from lark.load_grammar import GrammarLoader, GrammarError


class TestGrammar(TestCase):
def setUp(self):
pass

def test_errors(self):
for msg, examples in GrammarLoader.ERRORS.items():
for example in examples:
try:
p = Lark(example)
except GrammarError as e:
assert msg in str(e)
else:
assert False, "example did not raise an error"




if __name__ == '__main__':
main()




Loading…
Cancel
Save