Skip to content

Commit db503c9

Browse files
committed
fix!: non-consuming alternatives in ordered choices
If a branch in ordered choice has a potentially non-consuming successful alternative (Optional, ZeroOrMore, Not, And), it will succeed if alternative succeeds and thus will not try further choices.
1 parent 0a57265 commit db503c9

3 files changed

Lines changed: 32 additions & 18 deletions

File tree

arpeggio/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,9 @@ def _parse(self, parser):
416416
for e in self.nodes:
417417
try:
418418
result = e.parse(parser)
419-
if result is not None:
420-
match = True
421-
result = [result]
422-
break
419+
match = True
420+
result = [result]
421+
break
423422
except NoMatch:
424423
parser.position = c_pos # Backtracking
425424
finally:

arpeggio/tests/regressions/issue_20/test_issue_20.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@
99
#######################################################################
1010

1111
from __future__ import unicode_literals
12+
import pytest
1213

1314
# Grammar
14-
from arpeggio import ParserPython, Optional, EOF
15+
from arpeggio import ParserPython, Optional, NoMatch, EOF
1516

1617
def g(): return [Optional('first'), Optional('second'), Optional('third')], EOF
1718

1819

1920
def test_optional_in_choice():
2021
parser = ParserPython(g)
22+
# This input fails as the ordered choice will succeed on the first optional
23+
# without consuming the input.
2124
input_str = "second"
22-
parse_tree = parser.parse(input_str)
23-
assert parse_tree is not None
25+
with pytest.raises(NoMatch) as e:
26+
parser.parse(input_str)
27+
28+
assert "Expected 'first' or EOF" in str(e.value)

arpeggio/tests/test_error_reporting.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,13 @@ def grammar():
2727
assert "Expected 'a' or 'b'" in str(e.value)
2828
assert (e.value.line, e.value.col) == (1, 1)
2929

30+
# This grammar always succeeds due to the optional match
3031
def grammar():
3132
return ['b', Optional('a')]
3233

3334
parser = ParserPython(grammar)
34-
35-
with pytest.raises(NoMatch) as e:
36-
parser.parse('c')
37-
38-
assert "Expected 'b'" in str(e.value)
39-
assert (e.value.line, e.value.col) == (1, 1)
35+
parser.parse('b')
36+
parser.parse('c')
4037

4138

4239
def test_optional_with_better_match():
@@ -45,7 +42,7 @@ def test_optional_with_better_match():
4542
has precedence over non-optional.
4643
"""
4744

48-
def grammar(): return [first, Optional(second)]
45+
def grammar(): return [first, (Optional(second), 'six')]
4946
def first(): return 'one', 'two', 'three', '4'
5047
def second(): return 'one', 'two', 'three', 'four', 'five'
5148

@@ -131,9 +128,10 @@ def grammar():
131128
return ['one', Not('two')], _(r'\w+')
132129

133130
parser = ParserPython(grammar)
131+
parser.parse('three ident')
134132

135133
with pytest.raises(NoMatch) as e:
136-
parser.parse(' three ident')
134+
parser.parse(' two ident')
137135
assert "Expected 'one' at " in str(e.value)
138136

139137

@@ -165,6 +163,18 @@ def grammar():
165163
parser.parse(' three ident')
166164
assert "Expected 'one' or 'two' at" in str(e.value)
167165

168-
with pytest.raises(NoMatch) as e:
169-
parser.parse(' four ident')
170-
assert "Expected 'one' or 'two' at" in str(e.value)
166+
parser.parse(' four ident')
167+
168+
169+
def test_not_succeed_in_ordered_choice():
170+
"""
171+
Test that Not can succeed in ordered choice leading to ordered choice
172+
to succeed.
173+
See: https://github.com/textX/Arpeggio/issues/96
174+
"""
175+
176+
def grammar():
177+
return [Not("a"), "a"], Optional("b")
178+
179+
parser=ParserPython(grammar)
180+
parser.parse('b')

0 commit comments

Comments
 (0)