diff --git a/pyparsing/core.py b/pyparsing/core.py index 7ed0af9..564b969 100644 --- a/pyparsing/core.py +++ b/pyparsing/core.py @@ -4703,6 +4703,13 @@ class Or(ParseExpression): if self.exprs: self._may_return_empty = any(e.mayReturnEmpty for e in self.exprs) self.skipWhitespace = all(e.skipWhitespace for e in self.exprs) + # only pre-skip whitespace here (so a parse action attached + # directly to this Or gets the whitespace-skipped loc) if every + # alternative would itself have pre-skipped it; otherwise leave + # this to each alternative, same condition already used in + # parseImpl below to decide whether to pre-skip before trying + # the alternatives + self.callPreparse = all(e.callPreparse for e in self.exprs) else: self._may_return_empty = True @@ -4863,6 +4870,12 @@ class MatchFirst(ParseExpression): if self.exprs: self._may_return_empty = any(e.mayReturnEmpty for e in self.exprs) self.skipWhitespace = all(e.skipWhitespace for e in self.exprs) + # only pre-skip whitespace here (so a parse action attached + # directly to this MatchFirst gets the whitespace-skipped loc) + # if every alternative would itself have pre-skipped it; when an + # alternative depends on seeing unskipped whitespace (e.g. a + # leading White()), leave preparse to that alternative instead + self.callPreparse = all(e.callPreparse for e in self.exprs) else: self._may_return_empty = True diff --git a/tests/test_unit.py b/tests/test_unit.py index 4bd5e70..b58bf08 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -8910,6 +8910,32 @@ class Test02_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): "failed testing parse actions being run inside a NotAny", ) + def testParseActionLocOnOrAndMatchFirst(self): + # see Issue #557 + # a parse action attached directly to an Or ('^') or MatchFirst ('|') + # expression should receive the whitespace-skipped loc of the + # matched tokens, just as it would if the same alternatives were + # combined with And ('+') + src = " abc" + expected_loc = src.index("abc") + word = pp.Word(pp.alphas) + + for label, expr in ( + ("Or", word ^ pp.Literal("xyz")), + ("MatchFirst", word | pp.Literal("xyz")), + ): + with self.subTest(label=label): + seen_locs = [] + expr = expr.copy().set_parse_action( + lambda s, loc, toks: seen_locs.append(loc) + ) + expr.parse_string(src) + self.assertEqual( + [expected_loc], + seen_locs, + f"{label} parse action received wrong loc (whitespace not skipped)", + ) + def testParseResultsNameBelowUngroupedName(self): rule_num = pp.Regex("[0-9]+")("LIT_NUM*") list_num = pp.Group(