diff options
author | Paul McGuire <ptmcg@austin.rr.com> | 2019-08-04 23:11:07 -0500 |
---|---|---|
committer | Paul McGuire <ptmcg@austin.rr.com> | 2019-08-04 23:11:07 -0500 |
commit | 897e536d2131c6d07cbc355edb0ed744213c6997 (patch) | |
tree | 4454dd53ac5b874779605b1b2f82a144913215e7 | |
parent | b0f76d82a134d2ea2324b384ac9eba6c50a516d3 (diff) | |
download | pyparsing-git-897e536d2131c6d07cbc355edb0ed744213c6997.tar.gz |
Improved handling of '-' ErrorStop's when used within Each
-rw-r--r-- | CHANGES | 14 | ||||
-rw-r--r-- | pyparsing.py | 82 | ||||
-rw-r--r-- | unitTests.py | 25 |
3 files changed, 104 insertions, 17 deletions
@@ -2,6 +2,20 @@ Change Log ========== +Version 2.5.0a1 +--------------- +- Removed Py2.x support and other deprecated features + (not yet implemented) + +- Fixed handling of ParseSyntaxExceptions raised as part of Each + expressions, when sub-expressions contain '-' backtrack + suppression. As part of resolution to a question posted by John + Greene on StackOverflow. + +- BigQueryViewParser.py added to examples directory, PR submitted + by Michael Smedberg, nice work! + + Version 2.4.2 - July, 2019 -------------------------- - Updated the shorthand notation that has been added for repetition diff --git a/pyparsing.py b/pyparsing.py index 6715edf..0871fa8 100644 --- a/pyparsing.py +++ b/pyparsing.py @@ -95,8 +95,8 @@ classes inherit from. Use the docstrings for examples of how to: namespace class """ -__version__ = "2.4.2" -__versionTime__ = "29 Jul 2019 02:58 UTC" +__version__ = "2.5.0a1" +__versionTime__ = "05 Aug 2019 00:46 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -111,7 +111,7 @@ import pprint import traceback import types from datetime import datetime -from operator import itemgetter +from operator import itemgetter, attrgetter import itertools from functools import wraps @@ -1717,10 +1717,12 @@ class ParserElement(object): return loc, retTokens - def tryParse(self, instring, loc): + def tryParse(self, instring, loc, raise_fatal=False): try: return self._parse(instring, loc, doActions=False)[0] except ParseFatalException: + if raise_fatal: + raise raise ParseException(instring, loc, self.errmsg, self) def canParseNext(self, instring, loc): @@ -4104,14 +4106,22 @@ class Or(ParseExpression): maxExcLoc = -1 maxException = None matches = [] + fatals = [] for e in self.exprs: try: - loc2 = e.tryParse(instring, loc) + loc2 = e.tryParse(instring, loc, raise_fatal=True) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parserElement = e + fatals.append(pfe) + maxException = None + maxExcLoc = -1 except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc + if not fatals: + err.__traceback__ = None + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc except IndexError: if len(instring) > maxExcLoc: maxException = ParseException(instring, len(instring), e.errmsg, self) @@ -4154,6 +4164,14 @@ class Or(ParseExpression): if longest != (-1, None): return longest + if fatals: + if len(fatals) > 1: + fatals.sort(key=lambda e: -e.loc) + if fatals[0].loc == fatals[1].loc: + fatals.sort(key=lambda e: (-e.loc, -len(str(e.parserElement)))) + max_fatal = fatals[0] + raise max_fatal + if maxException is not None: maxException.msg = self.errmsg raise maxException @@ -4226,12 +4244,18 @@ class MatchFirst(ParseExpression): def parseImpl(self, instring, loc, doActions=True): maxExcLoc = -1 maxException = None + fatals = [] for e in self.exprs: try: ret = e._parse(instring, loc, doActions) return ret + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parserElement = e + fatals.append(pfe) + maxException = None except ParseException as err: - if err.loc > maxExcLoc: + if not fatals and err.loc > maxExcLoc: maxException = err maxExcLoc = err.loc except IndexError: @@ -4240,12 +4264,19 @@ class MatchFirst(ParseExpression): maxExcLoc = len(instring) # only got here if no expression matched, raise exception for match that made it the furthest + if fatals: + if len(fatals) > 1: + fatals.sort(key=lambda e: -e.loc) + if fatals[0].loc == fatals[1].loc: + fatals.sort(key=lambda e: (-e.loc, -len(str(e.parserElement)))) + max_fatal = fatals[0] + raise max_fatal + + if maxException is not None: + maxException.msg = self.errmsg + raise maxException else: - if maxException is not None: - maxException.msg = self.errmsg - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) + raise ParseException(instring, loc, "no defined alternatives to match", self) def __ior__(self, other): if isinstance(other, basestring): @@ -4365,12 +4396,20 @@ class Each(ParseExpression): matchOrder = [] keepMatching = True + failed = [] + fatals = [] while keepMatching: tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired - failed = [] + failed.clear() + fatals.clear() for e in tmpExprs: try: - tmpLoc = e.tryParse(instring, tmpLoc) + tmpLoc = e.tryParse(instring, tmpLoc, raise_fatal=True) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parserElement = e + fatals.append(pfe) + failed.append(e) except ParseException: failed.append(e) else: @@ -4382,6 +4421,15 @@ class Each(ParseExpression): if len(failed) == len(tmpExprs): keepMatching = False + # look for any ParseFatalExceptions + if fatals: + if len(fatals) > 1: + fatals.sort(key=lambda e: -e.loc) + if fatals[0].loc == fatals[1].loc: + fatals.sort(key=lambda e: (-e.loc, -len(str(e.parserElement)))) + max_fatal = fatals[0] + raise max_fatal + if tmpReqd: missing = ", ".join(_ustr(e) for e in tmpReqd) raise ParseException(instring, loc, "Missing one or more required elements (%s)" % missing) diff --git a/unitTests.py b/unitTests.py index 330afe1..999f2c7 100644 --- a/unitTests.py +++ b/unitTests.py @@ -2909,6 +2909,31 @@ class OptionalEachTest(ParseTestCase): self.runTest3() self.runTest4() +class EachWithParseFatalExceptionTest(ParseTestCase): + def runTest(self): + import pyparsing as pp + ppc = pp.pyparsing_common + + option_expr = pp.Keyword('options') - '(' + ppc.integer + ')' + step_expr1 = pp.Keyword('step') - '(' + ppc.integer + ")" + step_expr2 = pp.Keyword('step') - '(' + ppc.integer + "Z" + ")" + step_expr = step_expr1 ^ step_expr2 + + parser = option_expr & step_expr[...] + tests = [ + ("options(100) step(A)", "Expected integer, found 'A' (at char 18), (line:1, col:19)"), + ("step(A) options(100)", "Expected integer, found 'A' (at char 5), (line:1, col:6)"), + ("options(100) step(100A)", """Expected "Z", found 'A' (at char 21), (line:1, col:22)"""), + ("options(100) step(22) step(100ZA)", + """Expected ")", found 'A' (at char 31), (line:1, col:32)"""), + ] + test_lookup = dict(tests) + + success, output = parser.runTests((t[0] for t in tests), failureTests=True) + for test_str, result in output: + self.assertEqual(test_lookup[test_str], str(result), + "incorrect exception raised for test string {0!r}".format(test_str)) + class SumParseResultsTest(ParseTestCase): def runTest(self): |