def test_prefer_shifts_no_sr_conflicts(): """ Test that grammar with S/R conflict will be resolved to SHIFT actions if prefer_shift option is used. """ # This grammar has S/R conflict as B+ may consume multiple single "a" A # because "b" is optional. Thus, parser can't decide if it should shift "a" # or reduce by 'B: "b"? A+' and later by 'S: B+'; Most of the time we want # gready behavior so in case of doubt parser will choose shift if # prefer_shift is set to `True`. This means that the parser will first # consume all "a" using A+ and that reduce B at the end. grammar = r""" S: B+; B: "b"? A+; terminals A: "a"; """ g = Grammar.from_string(grammar) # There is a shift reduce conflict so we can't use LR parser. table = create_table(g) assert len(table.sr_conflicts) == 1 # But we can eliminate conflict by prefer_shifts strategy. table = create_table(g, prefer_shifts=True) assert len(table.sr_conflicts) == 0 # With prefer_shifts we get a greedy behavior input_str = 'b a a a b a a' output = [['b', ['a', 'a', 'a']], ['b', ['a', 'a']]] parser = Parser(g, prefer_shifts=True) result = parser.parse(input_str) assert result == output # GLR parser can parse without prefer_shifts strategy. This grammar is # ambiguous and yields 8 solutions for the given input. parser = GLRParser(g) results = [parser.call_actions(tree) for tree in parser.parse(input_str)] expected = [[['b', ['a']], [None, ['a']], [None, ['a']], ['b', ['a']], [None, ['a']]], [['b', ['a', 'a']], [None, ['a']], ['b', ['a']], [None, ['a']]], [['b', ['a']], [None, ['a', 'a']], ['b', ['a']], [None, ['a']]], [['b', ['a', 'a', 'a']], ['b', ['a']], [None, ['a']]], [['b', ['a']], [None, ['a']], [None, ['a']], ['b', ['a', 'a']]], [['b', ['a', 'a']], [None, ['a']], ['b', ['a', 'a']]], [['b', ['a']], [None, ['a', 'a']], ['b', ['a', 'a']]], [['b', ['a', 'a', 'a']], ['b', ['a', 'a']]]] assert results == expected # But if `prefer_shift` is used we get only one solution parser = GLRParser(g, prefer_shifts=True) result = parser.parse(input_str) assert len(result) == 1 assert parser.call_actions(result[0]) == output
def test_parse_context_call_actions(): """ Test that valid context attributes are available when calling actions using `call_actions`. """ global called called = [False, False] actions["E"][0] = act_sum(is_tree=True) parser = Parser(g, build_tree=True, actions=actions) tree = parser.parse(" 1 + 2 ") parser.call_actions(tree) assert all(called)
def test_action_override(): """ Explicitely provided action in `actions` param overrides default or grammar provided. """ grammar = """ S: Foo Bar; @pass_nochange Bar: "1" a; terminals @pass_nochange Foo: 'foo'; a: "a"; """ g = Grammar.from_string(grammar) p = Parser(g) input_str = "foo 1 a" result = p.parse(input_str) assert result == ["foo", ["1", "a"]] actions = {"Foo": lambda _, __: "eggs", "Bar": lambda _, __: "bar reduce"} p = Parser(g, actions=actions) result = p.parse(input_str) assert result == ["eggs", "bar reduce"] # Test with actions call postponing p = Parser(g, build_tree=True, actions=actions) tree = p.parse(input_str) result = p.call_actions(tree) assert result == ["eggs", "bar reduce"]
def test_parse_context_call_actions(): """ Test that valid context attributes are available when calling actions using `call_actions`. """ global called called = [False, False, False] parser = Parser(g, build_tree=True, actions=actions, debug=True) tree = parser.parse(" 1 + 2 ") context = Context() context.call_actions = True parser.call_actions(tree, context=context) assert all(called) assert node_exists[0]
def test_actions_manual(): """ Actions may be called as a separate step. """ grammar = get_grammar() p = Parser(grammar, build_tree=True, actions=get_actions()) result = p.parse("""34.7+78*34 +89+ 12.223*4""") assert type(result) is NodeNonTerm assert p.call_actions(result) == \ 34.7 + 78 * 34 + 89 + 12.223 * 4