diff options
-rw-r--r-- | CHANGES | 18 | ||||
-rw-r--r-- | pyparsing.py | 29 | ||||
-rw-r--r-- | unitTests.py | 29 |
3 files changed, 69 insertions, 7 deletions
@@ -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: |