summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES18
-rw-r--r--pyparsing.py29
-rw-r--r--unitTests.py29
3 files changed, 69 insertions, 7 deletions
diff --git a/CHANGES b/CHANGES
index 675a284..ef7f8ec 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,24 @@
Change Log
==========
+Version 2.4.0 - March, 2019
+---------------------------
+- Well, it looks like the API change that was introduced in 2.3.1 was more
+ drastic than expected, so for a friendlier forward upgrade path, this
+ release:
+ . Adds a pyparsing.__compat__ object for specifying compatibility with
+ future breaking changes.
+ . Conditionalizes the API-breaking behavior, based on the value
+ pyparsing.__compat__.collect_all_And_tokens. By default, this value
+ will be set to True, reflecting the new bugfixed behavior.
+ . User code that is dependent on the pre-bugfix behavior can restore
+ it by setting this value to False.
+
+ In 2.5 and later versions, the conditional code will be removed and
+ setting the flag to True or False in these later versions will have no
+ effect.
+
+
Version 2.3.1 - January, 2019
-----------------------------
- POSSIBLE API CHANGE: this release fixes a bug when results names were
diff --git a/pyparsing.py b/pyparsing.py
index 4ba5b11..578a07c 100644
--- a/pyparsing.py
+++ b/pyparsing.py
@@ -93,8 +93,8 @@ classes inherit from. Use the docstrings for examples of how to:
namespace class
"""
-__version__ = "2.3.2"
-__versionTime__ = "18 Jan 2019 22:15 UTC"
+__version__ = "2.4.0"
+__versionTime__ = "30 Mar 2019 05:34 UTC"
__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
import string
@@ -143,6 +143,20 @@ try:
except ImportError:
class SimpleNamespace: pass
+# version compatibility configuration
+__compat__ = SimpleNamespace()
+__compat__.__doc__ = """
+ A cross-version compatibility configuration for pyparsing features that will be
+ released in a future version. By setting values in this configuration to True,
+ those features can be enabled in prior versions for compatibility development
+ and testing.
+
+ - collect_all_And_tokens - flag to enable fix for Issue #63 that fixes erroneous grouping
+ of results names when an And expression is nested within an Or or MatchFirst; set to
+ True to enable bugfix to be released in pyparsing 2.4
+"""
+__compat__.collect_all_And_tokens = True
+
#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
@@ -350,7 +364,7 @@ class ParseException(ParseBaseException):
callers = inspect.getinnerframes(exc.__traceback__, context=depth)
seen = set()
for i, ff in enumerate(callers[-depth:]):
- frm = ff.frame
+ frm = ff[0]
f_self = frm.f_locals.get('self', None)
if isinstance(f_self, ParserElement):
@@ -3774,7 +3788,8 @@ class Or(ParseExpression):
def streamline(self):
super(Or, self).streamline()
- self.saveAsList = any(e.saveAsList for e in self.exprs)
+ if __compat__.collect_all_And_tokens:
+ self.saveAsList = any(e.saveAsList for e in self.exprs)
return self
def parseImpl( self, instring, loc, doActions=True ):
@@ -3856,13 +3871,13 @@ class MatchFirst(ParseExpression):
super(MatchFirst,self).__init__(exprs, savelist)
if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
- self.saveAsList = any(e.saveAsList for e in self.exprs)
else:
self.mayReturnEmpty = True
def streamline(self):
super(MatchFirst, self).streamline()
- self.saveAsList = any(e.saveAsList for e in self.exprs)
+ if __compat__.collect_all_And_tokens:
+ self.saveAsList = any(e.saveAsList for e in self.exprs)
return self
def parseImpl( self, instring, loc, doActions=True ):
@@ -4728,7 +4743,7 @@ class Group(TokenConverter):
"""
def __init__( self, expr ):
super(Group,self).__init__( expr )
- self.saveAsList = expr.saveAsList
+ self.saveAsList = True
def postParse( self, instring, loc, tokenlist ):
return [ tokenlist ]
diff --git a/unitTests.py b/unitTests.py
index 4786255..caf538d 100644
--- a/unitTests.py
+++ b/unitTests.py
@@ -3868,6 +3868,35 @@ class ParseResultsWithNameOr(ParseTestCase):
self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split())
self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split())
+ expr = (expr_a | expr_b)('rexp')
+ expr.runTests("""\
+ not the bird
+ the bird
+ """)
+ self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split())
+ self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split())
+
+ # test compatibility mode, restoring pre-2.3.1 behavior
+ with AutoReset(pp.__compat__, "collect_all_And_tokens"):
+ pp.__compat__.collect_all_And_tokens = False
+ expr_a = pp.Literal('not') + pp.Literal('the') + pp.Literal('bird')
+ expr_b = pp.Literal('the') + pp.Literal('bird')
+ expr = (expr_a ^ expr_b)('rexp')
+ expr.runTests("""\
+ not the bird
+ the bird
+ """)
+ self.assertEqual(expr.parseString('not the bird')['rexp'], 'not')
+ self.assertEqual(expr.parseString('the bird')['rexp'], 'the')
+
+ expr = (expr_a | expr_b)('rexp')
+ expr.runTests("""\
+ not the bird
+ the bird
+ """)
+ self.assertEqual(expr.parseString('not the bird')['rexp'], 'not')
+ self.assertEqual(expr.parseString('the bird')['rexp'], 'the')
+
class EmptyDictDoesNotRaiseException(ParseTestCase):
def runTest(self):
if not PY_3: