| @@ -17,9 +17,7 @@ from ..exceptions import ParseError, UnexpectedToken | |||||
| from .grammar_analysis import GrammarAnalyzer | from .grammar_analysis import GrammarAnalyzer | ||||
| from ..grammar import NonTerminal | from ..grammar import NonTerminal | ||||
| from .earley_common import Item, TransitiveItem | from .earley_common import Item, TransitiveItem | ||||
| from .earley_forest import ForestToTreeVisitor, ForestSumVisitor, SymbolNode | |||||
| from collections import deque, defaultdict | |||||
| from .earley_forest import ForestToTreeVisitor, ForestSumVisitor, SymbolNode, Forest | |||||
| class Parser: | class Parser: | ||||
| def __init__(self, parser_conf, term_matcher, resolve_ambiguity=True, forest_sum_visitor = ForestSumVisitor): | def __init__(self, parser_conf, term_matcher, resolve_ambiguity=True, forest_sum_visitor = ForestSumVisitor): | ||||
| @@ -295,7 +293,7 @@ class Parser: | |||||
| ## If we're not resolving ambiguity, we just return the root of the SPPF tree to the caller. | ## If we're not resolving ambiguity, we just return the root of the SPPF tree to the caller. | ||||
| # This means the caller can work directly with the SPPF tree. | # This means the caller can work directly with the SPPF tree. | ||||
| if not self.resolve_ambiguity: | if not self.resolve_ambiguity: | ||||
| return ForestToAmbiguousTreeVisitor(solutions[0], self.callbacks).go() | |||||
| return Forest(solutions[0], self.callbacks) | |||||
| # ... otherwise, disambiguate and convert the SPPF to an AST, removing any ambiguities | # ... otherwise, disambiguate and convert the SPPF to an AST, removing any ambiguities | ||||
| # according to the rules. | # according to the rules. | ||||
| @@ -462,3 +462,20 @@ class ForestToPyDotVisitor(ForestVisitor): | |||||
| child_graph_node_id = str(id(child)) | child_graph_node_id = str(id(child)) | ||||
| child_graph_node = self.graph.get_node(child_graph_node_id)[0] | child_graph_node = self.graph.get_node(child_graph_node_id)[0] | ||||
| self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node)) | self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node)) | ||||
| class Forest(Tree): | |||||
| def __init__(self, root, callbacks): | |||||
| self.root = root | |||||
| self.callbacks = callbacks | |||||
| self.data = '_ambig' | |||||
| self._children = None | |||||
| @property | |||||
| def children(self): | |||||
| if self._children is None: | |||||
| t = ForestToAmbiguousTreeVisitor(self.callbacks).go(self.root) | |||||
| self._children = t.children | |||||
| return self._children | |||||
| def to_pydot(self, filename): | |||||
| ForestToPyDotVisitor().go(self.root, filename) | |||||
| @@ -24,7 +24,7 @@ from .grammar_analysis import GrammarAnalyzer | |||||
| from ..grammar import NonTerminal, Terminal | from ..grammar import NonTerminal, Terminal | ||||
| from .earley import ApplyCallbacks | from .earley import ApplyCallbacks | ||||
| from .earley_common import Item, TransitiveItem | from .earley_common import Item, TransitiveItem | ||||
| from .earley_forest import ForestToTreeVisitor, ForestSumVisitor, SymbolNode, ForestToAmbiguousTreeVisitor | |||||
| from .earley_forest import ForestToTreeVisitor, ForestSumVisitor, SymbolNode, Forest | |||||
| class Parser: | class Parser: | ||||
| @@ -359,7 +359,7 @@ class Parser: | |||||
| ## If we're not resolving ambiguity, we just return the root of the SPPF tree to the caller. | ## If we're not resolving ambiguity, we just return the root of the SPPF tree to the caller. | ||||
| # This means the caller can work directly with the SPPF tree. | # This means the caller can work directly with the SPPF tree. | ||||
| if not self.resolve_ambiguity: | if not self.resolve_ambiguity: | ||||
| return ForestToAmbiguousTreeVisitor(self.callbacks).go(solutions[0]) | |||||
| return Forest(solutions[0], self.callbacks) | |||||
| # ... otherwise, disambiguate and convert the SPPF to an AST, removing any ambiguities | # ... otherwise, disambiguate and convert the SPPF to an AST, removing any ambiguities | ||||
| # according to the rules. | # according to the rules. | ||||
| @@ -20,10 +20,9 @@ from .test_parser import ( | |||||
| TestEarleyStandard, | TestEarleyStandard, | ||||
| TestCykStandard, | TestCykStandard, | ||||
| TestLalrContextual, | TestLalrContextual, | ||||
| # TestEarleyScanless, | |||||
| TestEarleyDynamic, | TestEarleyDynamic, | ||||
| # TestFullEarleyScanless, | |||||
| # TestFullEarleyStandard, | |||||
| TestFullEarleyDynamic, | TestFullEarleyDynamic, | ||||
| TestFullEarleyDynamic_complete, | TestFullEarleyDynamic_complete, | ||||
| @@ -228,6 +228,7 @@ def _make_full_earley_test(LEXER): | |||||
| empty_tree = Tree('empty', [Tree('empty2', [])]) | empty_tree = Tree('empty', [Tree('empty2', [])]) | ||||
| self.assertSequenceEqual(res.children, ['a', empty_tree, empty_tree, 'b']) | self.assertSequenceEqual(res.children, ['a', empty_tree, empty_tree, 'b']) | ||||
| @unittest.skipIf(LEXER=='standard', "Requires dynamic lexer") | |||||
| def test_earley_explicit_ambiguity(self): | def test_earley_explicit_ambiguity(self): | ||||
| # This was a sneaky bug! | # This was a sneaky bug! | ||||
| @@ -244,6 +245,7 @@ def _make_full_earley_test(LEXER): | |||||
| self.assertEqual( ambig_tree.data, '_ambig') | self.assertEqual( ambig_tree.data, '_ambig') | ||||
| self.assertEqual( len(ambig_tree.children), 2) | self.assertEqual( len(ambig_tree.children), 2) | ||||
| @unittest.skipIf(LEXER=='standard', "Requires dynamic lexer") | |||||
| def test_ambiguity1(self): | def test_ambiguity1(self): | ||||
| grammar = """ | grammar = """ | ||||
| start: cd+ "e" | start: cd+ "e" | ||||
| @@ -261,7 +263,7 @@ def _make_full_earley_test(LEXER): | |||||
| assert ambig_tree.data == '_ambig', ambig_tree | assert ambig_tree.data == '_ambig', ambig_tree | ||||
| assert len(ambig_tree.children) == 2 | assert len(ambig_tree.children) == 2 | ||||
| @unittest.skipIf(LEXER==None, "Scanless doesn't support regular expressions") | |||||
| @unittest.skipIf(LEXER=='standard', "Requires dynamic lexer") | |||||
| def test_ambiguity2(self): | def test_ambiguity2(self): | ||||
| grammar = """ | grammar = """ | ||||
| ANY: /[a-zA-Z0-9 ]+/ | ANY: /[a-zA-Z0-9 ]+/ | ||||
| @@ -324,7 +326,7 @@ def _make_full_earley_test(LEXER): | |||||
| self.assertEqual(set(tree.children), set(expected.children)) | self.assertEqual(set(tree.children), set(expected.children)) | ||||
| @unittest.skipIf(LEXER=='dynamic', "Only relevant for the dynamic_complete parser") | |||||
| @unittest.skipIf(LEXER!='dynamic_complete', "Only relevant for the dynamic_complete parser") | |||||
| def test_explicit_ambiguity2(self): | def test_explicit_ambiguity2(self): | ||||
| grammar = r""" | grammar = r""" | ||||
| start: NAME+ | start: NAME+ | ||||