| @@ -0,0 +1,247 @@ | |||||
| from __future__ import absolute_import | |||||
| import unittest | |||||
| from lark import Lark | |||||
| from lark.lexer import Token | |||||
| from lark.tree import Tree | |||||
| from lark.visitors import Visitor, Transformer, Discard | |||||
| from lark.parsers.earley_forest import TreeForestTransformer, handles_ambiguity | |||||
| class TestTreeForestTransformer(unittest.TestCase): | |||||
| grammar = """ | |||||
| start: ab bc cd | |||||
| !ab: "A" "B"? | |||||
| !bc: "B"? "C"? | |||||
| !cd: "C"? "D" | |||||
| """ | |||||
| parser = Lark(grammar, parser='earley', ambiguity='forest') | |||||
| forest = parser.parse("ABCD") | |||||
| def test_identity_resolve_ambiguity(self): | |||||
| l = Lark(self.grammar, parser='earley', ambiguity='resolve') | |||||
| tree1 = l.parse("ABCD") | |||||
| tree2 = TreeForestTransformer(resolve_ambiguity=True).transform(self.forest) | |||||
| self.assertEqual(tree1, tree2) | |||||
| def test_identity_explicit_ambiguity(self): | |||||
| l = Lark(self.grammar, parser='earley', ambiguity='explicit') | |||||
| tree1 = l.parse("ABCD") | |||||
| tree2 = TreeForestTransformer(resolve_ambiguity=False).transform(self.forest) | |||||
| self.assertEqual(tree1, tree2) | |||||
| def test_tree_class(self): | |||||
| class CustomTree(Tree): | |||||
| pass | |||||
| class TreeChecker(Visitor): | |||||
| def __default__(self, tree): | |||||
| assert isinstance(tree, CustomTree) | |||||
| tree = TreeForestTransformer(resolve_ambiguity=False, tree_class=CustomTree).transform(self.forest) | |||||
| TreeChecker().visit(tree) | |||||
| def test_token_calls(self): | |||||
| visited_A = False | |||||
| visited_B = False | |||||
| visited_C = False | |||||
| visited_D = False | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| def A(self, node): | |||||
| assert node.type == 'A' | |||||
| nonlocal visited_A | |||||
| visited_A = True | |||||
| def B(self, node): | |||||
| assert node.type == 'B' | |||||
| nonlocal visited_B | |||||
| visited_B = True | |||||
| def C(self, node): | |||||
| assert node.type == 'C' | |||||
| nonlocal visited_C | |||||
| visited_C = True | |||||
| def D(self, node): | |||||
| assert node.type == 'D' | |||||
| nonlocal visited_D | |||||
| visited_D = True | |||||
| tree = CustomTransformer(resolve_ambiguity=False).transform(self.forest) | |||||
| self.assertTrue(visited_A) | |||||
| self.assertTrue(visited_B) | |||||
| self.assertTrue(visited_C) | |||||
| self.assertTrue(visited_D) | |||||
| def test_default_token(self): | |||||
| token_count = 0 | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| def __default_token__(self, node): | |||||
| nonlocal token_count | |||||
| token_count += 1 | |||||
| assert isinstance(node, Token) | |||||
| tree = CustomTransformer(resolve_ambiguity=True).transform(self.forest) | |||||
| self.assertEqual(token_count, 4) | |||||
| def test_rule_calls(self): | |||||
| visited_start = False | |||||
| visited_ab = False | |||||
| visited_bc = False | |||||
| visited_cd = False | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| def start(self, data): | |||||
| nonlocal visited_start | |||||
| visited_start = True | |||||
| def ab(self, data): | |||||
| nonlocal visited_ab | |||||
| visited_ab = True | |||||
| def bc(self, data): | |||||
| nonlocal visited_bc | |||||
| visited_bc = True | |||||
| def cd(self, data): | |||||
| nonlocal visited_cd | |||||
| visited_cd = True | |||||
| tree = CustomTransformer(resolve_ambiguity=False).transform(self.forest) | |||||
| self.assertTrue(visited_start) | |||||
| self.assertTrue(visited_ab) | |||||
| self.assertTrue(visited_bc) | |||||
| self.assertTrue(visited_cd) | |||||
| def test_default_rule(self): | |||||
| rule_count = 0 | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| def __default__(self, name, data): | |||||
| nonlocal rule_count | |||||
| rule_count += 1 | |||||
| tree = CustomTransformer(resolve_ambiguity=True).transform(self.forest) | |||||
| self.assertEqual(rule_count, 4) | |||||
| def test_default_ambig(self): | |||||
| ambig_count = 0 | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| def __default_ambig__(self, name, data): | |||||
| nonlocal ambig_count | |||||
| if len(data) > 1: | |||||
| ambig_count += 1 | |||||
| tree = CustomTransformer(resolve_ambiguity=False).transform(self.forest) | |||||
| self.assertEqual(ambig_count, 1) | |||||
| def test_handles_ambiguity(self): | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| @handles_ambiguity | |||||
| def start(self, data): | |||||
| assert isinstance(data, list) | |||||
| assert len(data) == 4 | |||||
| for tree in data: | |||||
| assert tree.data == 'start' | |||||
| return 'handled' | |||||
| @handles_ambiguity | |||||
| def ab(self, data): | |||||
| assert isinstance(data, list) | |||||
| assert len(data) == 1 | |||||
| assert data[0].data == 'ab' | |||||
| tree = CustomTransformer(resolve_ambiguity=False).transform(self.forest) | |||||
| self.assertEqual(tree, 'handled') | |||||
| def test_discard(self): | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| def bc(self, data): | |||||
| raise Discard | |||||
| def D(self, node): | |||||
| raise Discard | |||||
| class TreeChecker(Transformer): | |||||
| def bc(self, children): | |||||
| assert False | |||||
| def D(self, token): | |||||
| assert False | |||||
| tree = CustomTransformer(resolve_ambiguity=False).transform(self.forest) | |||||
| TreeChecker(visit_tokens=True).transform(tree) | |||||
| def test_aliases(self): | |||||
| visited_ambiguous = False | |||||
| visited_full = False | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| @handles_ambiguity | |||||
| def start(self, data): | |||||
| for tree in data: | |||||
| assert tree.data == 'ambiguous' or tree.data == 'full' | |||||
| def ambiguous(self, data): | |||||
| nonlocal visited_ambiguous | |||||
| visited_ambiguous = True | |||||
| assert len(data) == 3 | |||||
| assert data[0].data == 'ab' | |||||
| assert data[1].data == 'bc' | |||||
| assert data[2].data == 'cd' | |||||
| return self.tree_class('ambiguous', data) | |||||
| def full(self, data): | |||||
| nonlocal visited_full | |||||
| visited_full = True | |||||
| assert len(data) == 1 | |||||
| assert data[0].data == 'abcd' | |||||
| return self.tree_class('full', data) | |||||
| grammar = """ | |||||
| start: ab bc cd -> ambiguous | |||||
| | abcd -> full | |||||
| !ab: "A" "B"? | |||||
| !bc: "B"? "C"? | |||||
| !cd: "C"? "D" | |||||
| !abcd: "ABCD" | |||||
| """ | |||||
| l = Lark(grammar, parser='earley', ambiguity='forest') | |||||
| forest = l.parse('ABCD') | |||||
| tree = CustomTransformer(resolve_ambiguity=False).transform(forest) | |||||
| self.assertTrue(visited_ambiguous) | |||||
| self.assertTrue(visited_full) | |||||
| def test_transformation(self): | |||||
| class CustomTransformer(TreeForestTransformer): | |||||
| def __default__(self, name, data): | |||||
| result = [] | |||||
| for item in data: | |||||
| if isinstance(item, list): | |||||
| result += item | |||||
| else: | |||||
| result.append(item) | |||||
| return result | |||||
| def __default_token__(self, node): | |||||
| return node.lower() | |||||
| def __default_ambig__(self, name, data): | |||||
| return data[0] | |||||
| result = CustomTransformer(resolve_ambiguity=False).transform(self.forest) | |||||
| expected = ['a', 'b', 'c', 'd'] | |||||
| self.assertEqual(result, expected) | |||||
| if __name__ == '__main__': | |||||
| unittest.main() | |||||