| @@ -74,3 +74,54 @@ Prints out: | |||||
| ``` | ``` | ||||
| *Note: We don't have to return a token, because comments are ignored* | *Note: We don't have to return a token, because comments are ignored* | ||||
| ## CollapseAmbiguities | |||||
| Parsing ambiguous texts with earley and `ambiguity='explicit'` produces a single tree with `_ambig` nodes to mark where the ambiguity occured. | |||||
| However, it's sometimes more convenient instead to work with a list of all possible unambiguous trees. | |||||
| Lark provides a utility transformer for that purpose: | |||||
| ```python | |||||
| from lark import Lark, Tree, Transformer | |||||
| from lark.visitors import CollapseAmbiguities | |||||
| grammar = """ | |||||
| !start: x y | |||||
| !x: "a" "b" | |||||
| | "ab" | |||||
| | "abc" | |||||
| !y: "c" "d" | |||||
| | "cd" | |||||
| | "d" | |||||
| """ | |||||
| parser = Lark(grammar, ambiguity='explicit') | |||||
| t = parser.parse('abcd') | |||||
| for x in CollapseAmbiguities().transform(t): | |||||
| print(x.pretty()) | |||||
| ``` | |||||
| This prints out: | |||||
| start | |||||
| x | |||||
| a | |||||
| b | |||||
| y | |||||
| c | |||||
| d | |||||
| start | |||||
| x ab | |||||
| y cd | |||||
| start | |||||
| x abc | |||||
| y d | |||||
| While convenient, this should be used carefully, as highly ambiguous trees will soon create an exponential explosion of such unambiguous derivations. | |||||
| @@ -1,4 +1,5 @@ | |||||
| import sys | import sys | ||||
| from functools import reduce | |||||
| from ast import literal_eval | from ast import literal_eval | ||||
| from collections import deque | from collections import deque | ||||
| @@ -265,3 +266,24 @@ def eval_escaping(s): | |||||
| raise ValueError(s, e) | raise ValueError(s, e) | ||||
| return s | return s | ||||
| def combine_alternatives(lists): | |||||
| """ | |||||
| Accepts a list of alternatives, and enumerates all their possible concatinations. | |||||
| Examples: | |||||
| >>> combine_alternatives([range(2), [4,5]]) | |||||
| [[0, 4], [0, 5], [1, 4], [1, 5]] | |||||
| >>> combine_alternatives(["abc", "xy", '$']) | |||||
| [['a', 'x', '$'], ['a', 'y', '$'], ['b', 'x', '$'], ['b', 'y', '$'], ['c', 'x', '$'], ['c', 'y', '$']] | |||||
| >>> combine_alternatives([]) | |||||
| [[]] | |||||
| """ | |||||
| if not lists: | |||||
| return [[]] | |||||
| assert all(l for l in lists), lists | |||||
| init = [[x] for x in lists[0]] | |||||
| return reduce(lambda a,b: [i+[j] for i in a for j in b], lists[1:], init) | |||||
| @@ -1,6 +1,6 @@ | |||||
| from functools import wraps | from functools import wraps | ||||
| from .utils import smart_decorator | |||||
| from .utils import smart_decorator, combine_alternatives | |||||
| from .tree import Tree | from .tree import Tree | ||||
| from .exceptions import VisitError, GrammarError | from .exceptions import VisitError, GrammarError | ||||
| from .lexer import Token | from .lexer import Token | ||||
| @@ -345,3 +345,23 @@ def v_args(inline=False, meta=False, tree=False, wrapper=None): | |||||
| ###} | ###} | ||||
| #--- Visitor Utilities --- | |||||
| class CollapseAmbiguities(Transformer): | |||||
| """ | |||||
| Transforms a tree that contains any number of _ambig nodes into a list of trees, | |||||
| each one containing an unambiguous tree. | |||||
| The length of the resulting list is the product of the length of all _ambig nodes. | |||||
| Warning: This may quickly explode for highly ambiguous trees. | |||||
| """ | |||||
| def _ambig(self, options): | |||||
| return sum(options, []) | |||||
| def __default__(self, data, children_lists, meta): | |||||
| return [Tree(data, children, meta) for children in combine_alternatives(children_lists)] | |||||
| def __default_token__(self, t): | |||||
| return [t] | |||||