summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul McGuire <ptmcg@austin.rr.com>2019-08-04 23:11:07 -0500
committerPaul McGuire <ptmcg@austin.rr.com>2019-08-04 23:11:07 -0500
commit897e536d2131c6d07cbc355edb0ed744213c6997 (patch)
tree4454dd53ac5b874779605b1b2f82a144913215e7
parentb0f76d82a134d2ea2324b384ac9eba6c50a516d3 (diff)
downloadpyparsing-git-897e536d2131c6d07cbc355edb0ed744213c6997.tar.gz
Improved handling of '-' ErrorStop's when used within Each
-rw-r--r--CHANGES14
-rw-r--r--pyparsing.py82
-rw-r--r--unitTests.py25
3 files changed, 104 insertions, 17 deletions
diff --git a/CHANGES b/CHANGES
index e1a9462..673bbbc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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):