Skip to content

Commit 5713fba

Browse files
committed
Fixed bugs in Each with ZeroOrMore and OneOrMore (first matched element enclosed in extra nesting level; results names not maintained; did not handle mix with required expressions)
1 parent 1add439 commit 5713fba

File tree

3 files changed

+56
-8
lines changed

3 files changed

+56
-8
lines changed

CHANGES

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ Version 3.0.0b1
3535
debug actions, see the following bullet regarding an optional API change
3636
for those methods.
3737

38+
- Fixed bugs in Each when passed OneOrMore or ZeroOrMore expressions:
39+
. first expression match could be enclosed in an extra nesting level
40+
. out-of-order expressions now handled correctly if mixed with required
41+
expressions
42+
. results names are maintained correctly for these expressions
43+
3844
- Fixed traceback trimming, and added ParserElement.verbose_traceback
3945
save/restore to reset_pyparsing_context().
4046

pyparsing/core.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ def setResultsName(self, name, listAllMatches=False):
414414
return self._setResultsName(name, listAllMatches)
415415

416416
def _setResultsName(self, name, listAllMatches=False):
417+
if name is None:
418+
return self
417419
newself = self.copy()
418420
if name.endswith("*"):
419421
name = name[:-1]
@@ -3573,14 +3575,18 @@ def parseImpl(self, instring, loc, doActions=True):
35733575
opt2 = [
35743576
e
35753577
for e in self.exprs
3576-
if e.mayReturnEmpty and not isinstance(e, (Optional, Regex))
3578+
if e.mayReturnEmpty and not isinstance(e, (Optional, Regex, ZeroOrMore))
35773579
]
35783580
self.optionals = opt1 + opt2
35793581
self.multioptionals = [
3580-
e.expr for e in self.exprs if isinstance(e, ZeroOrMore)
3582+
e.expr.setResultsName(e.resultsName, listAllMatches=True)
3583+
for e in self.exprs
3584+
if isinstance(e, _MultipleMatch)
35813585
]
35823586
self.multirequired = [
3583-
e.expr for e in self.exprs if isinstance(e, OneOrMore)
3587+
e.expr.setResultsName(e.resultsName, listAllMatches=True)
3588+
for e in self.exprs
3589+
if isinstance(e, OneOrMore)
35843590
]
35853591
self.required = [
35863592
e
@@ -3589,16 +3595,18 @@ def parseImpl(self, instring, loc, doActions=True):
35893595
]
35903596
self.required += self.multirequired
35913597
self.initExprGroups = False
3598+
35923599
tmpLoc = loc
35933600
tmpReqd = self.required[:]
35943601
tmpOpt = self.optionals[:]
3602+
multis = self.multioptionals[:]
35953603
matchOrder = []
35963604

35973605
keepMatching = True
35983606
failed = []
35993607
fatals = []
36003608
while keepMatching:
3601-
tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
3609+
tmpExprs = tmpReqd + tmpOpt + multis
36023610
failed.clear()
36033611
fatals.clear()
36043612
for e in tmpExprs:
@@ -3642,13 +3650,12 @@ def parseImpl(self, instring, loc, doActions=True):
36423650
e for e in self.exprs if isinstance(e, Optional) and e.expr in tmpOpt
36433651
]
36443652

3645-
resultlist = []
3653+
total_results = ParseResults([])
36463654
for e in matchOrder:
36473655
loc, results = e._parse(instring, loc, doActions)
3648-
resultlist.append(results)
3656+
total_results += results
36493657

3650-
finalResults = sum(resultlist, ParseResults([]))
3651-
return loc, finalResults
3658+
return loc, total_results
36523659

36533660
def _generateDefaultName(self):
36543661
return "{" + " & ".join(str(e) for e in self.exprs) + "}"

tests/test_unit.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4636,6 +4636,41 @@ def testEachWithParseFatalException(self):
46364636
"incorrect exception raised for test string {!r}".format(test_str),
46374637
)
46384638

4639+
def testEachWithMultipleMatch(self):
4640+
size = "size" + pp.oneOf("S M L XL")
4641+
color = pp.Group(
4642+
"color" + pp.oneOf("red orange yellow green blue purple white black brown")
4643+
)
4644+
size.setName("size_spec")
4645+
color.setName("color_spec")
4646+
4647+
spec0 = size("size") & color[...]("colors")
4648+
spec1 = size("size") & color[1, ...]("colors")
4649+
4650+
for spec in (spec0, spec1):
4651+
for test, expected_dict in [
4652+
(
4653+
"size M color red color yellow",
4654+
{
4655+
"colors": [["color", "red"], ["color", "yellow"]],
4656+
"size": ["size", "M"],
4657+
},
4658+
),
4659+
(
4660+
"color green size M color red color yellow",
4661+
{
4662+
"colors": [
4663+
["color", "green"],
4664+
["color", "red"],
4665+
["color", "yellow"],
4666+
],
4667+
"size": ["size", "M"],
4668+
},
4669+
),
4670+
]:
4671+
result = spec.parseString(test, parseAll=True)
4672+
self.assertParseResultsEquals(result, expected_dict=expected_dict)
4673+
46394674
def testSumParseResults(self):
46404675

46414676
samplestr1 = "garbage;DOB 10-10-2010;more garbage\nID PARI12345678;more garbage"

0 commit comments

Comments
 (0)