diff options
author | ptmcg <ptmcg@austin.rr.com> | 2020-03-06 23:48:44 -0600 |
---|---|---|
committer | ptmcg <ptmcg@austin.rr.com> | 2020-03-06 23:48:44 -0600 |
commit | 7daecd9e3d0b39b15c755b00d717857cca68753f (patch) | |
tree | 94274c68dadd9e6723d4be2c5085e5188f015f73 | |
parent | 847af590154743bae61a32c3dc1a6c2a19009f42 (diff) | |
download | pyparsing-git-7daecd9e3d0b39b15c755b00d717857cca68753f.tar.gz |
Selected backports from 3.0.0 work
-rw-r--r-- | CHANGES | 9 | ||||
-rw-r--r-- | examples/delta_time.py | 19 | ||||
-rw-r--r-- | pyparsing.py | 30 | ||||
-rw-r--r-- | unitTests.py | 52 |
4 files changed, 90 insertions, 20 deletions
@@ -2,6 +2,15 @@ Change Log ========== +Version 2.4.7 - March, 2020 +--------------------------- +- Backport of selected fixes from 3.0.0 work: + . Each bug with Regex expressions + . And expressions not properly constructing with generator + . Traceback abbreviation + . Bug in delta_time example + + Version 2.4.6 - December, 2019 ------------------------------ - Fixed typos in White mapping of whitespace characters, to use diff --git a/examples/delta_time.py b/examples/delta_time.py index e079094..525f5df 100644 --- a/examples/delta_time.py +++ b/examples/delta_time.py @@ -52,6 +52,9 @@ today, tomorrow, yesterday, noon, midnight, now = map(CK, "today tomorrow yester def plural(s): return CK(s) | CK(s + 's').addParseAction(pp.replaceWith(s)) week, day, hour, minute, second = map(plural, "week day hour minute second".split()) +time_units = hour | minute | second +any_time_units = week | day | time_units + am = CL("am") pm = CL("pm") COLON = pp.Suppress(':') @@ -69,7 +72,7 @@ on_ = CK("on") couple = (pp.Optional(CK("a")) + CK("couple") + pp.Optional(CK("of"))).setParseAction(pp.replaceWith(2)) a_qty = (CK("a") | CK("an")).setParseAction(pp.replaceWith(1)) the_qty = CK("the").setParseAction(pp.replaceWith(1)) -qty = pp.ungroup(integer | couple | a_qty | the_qty) +qty = pp.ungroup(integer | couple | a_qty | the_qty).setName("qty") time_ref_present = pp.Empty().addParseAction(pp.replaceWith(True))('time_ref_present') def fill_24hr_time_fields(t): @@ -86,8 +89,10 @@ def fill_default_time_fields(t): weekday_name_list = list(calendar.day_name) weekday_name = pp.oneOf(weekday_name_list) -_24hour_time = pp.Word(pp.nums, exact=4).addParseAction(lambda t: [int(t[0][:2]),int(t[0][2:])], - fill_24hr_time_fields) +_24hour_time = (~(integer + any_time_units) + + pp.Word(pp.nums, exact=4).addParseAction(lambda t: [int(t[0][:2]),int(t[0][2:])], + fill_24hr_time_fields) + ) _24hour_time.setName("0000 time") ampm = am | pm timespec = (integer("HH") @@ -302,6 +307,10 @@ if __name__ == "__main__": 2pm next Sunday next Sunday at 2pm last Sunday at 2pm + 10 seconds ago + 100 seconds ago + 1000 seconds ago + 10000 seconds ago """ time_of_day = timedelta(hours=current_time.hour, @@ -309,6 +318,10 @@ if __name__ == "__main__": seconds=current_time.second) expected = { 'now' : timedelta(0), + "10 seconds ago": timedelta(seconds=-10), + "100 seconds ago": timedelta(seconds=-100), + "1000 seconds ago": timedelta(seconds=-1000), + "10000 seconds ago": timedelta(seconds=-10000), '10 minutes ago': timedelta(minutes=-10), '10 minutes from now': timedelta(minutes=10), 'in 10 minutes': timedelta(minutes=10), diff --git a/pyparsing.py b/pyparsing.py index 4d2f98e..135b6a0 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.6" -__versionTime__ = "24 Dec 2019 04:27 UTC" +__version__ = "2.4.7" +__versionTime__ = "04 Mar 2020 02:48 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -1391,6 +1391,12 @@ class ParserElement(object): """ ParserElement._literalStringClass = cls + @classmethod + def _trim_traceback(cls, tb): + while tb.tb_next: + tb = tb.tb_next + return tb + def __init__(self, savelist=False): self.parseAction = list() self.failAction = None @@ -1943,7 +1949,8 @@ class ParserElement(object): if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc else: return tokens @@ -2017,7 +2024,8 @@ class ParserElement(object): if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc def transformString(self, instring): @@ -2063,7 +2071,8 @@ class ParserElement(object): if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc def searchString(self, instring, maxMatches=_MAX_INT): @@ -2093,7 +2102,8 @@ class ParserElement(object): if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False): @@ -2565,7 +2575,8 @@ class ParserElement(object): if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc def __eq__(self, other): @@ -3312,7 +3323,7 @@ class Regex(Token): self.name = _ustr(self) self.errmsg = "Expected " + self.name self.mayIndexError = False - self.mayReturnEmpty = True + self.mayReturnEmpty = self.re_match("") is not None self.asGroupList = asGroupList self.asMatch = asMatch if self.asGroupList: @@ -3993,6 +4004,7 @@ class And(ParseExpression): self.leaveWhitespace() def __init__(self, exprs, savelist=True): + exprs = list(exprs) if exprs and Ellipsis in exprs: tmp = [] for i, expr in enumerate(exprs): @@ -4358,7 +4370,7 @@ class Each(ParseExpression): if self.initExprGroups: self.opt1map = dict((id(e.expr), e) for e in self.exprs if isinstance(e, Optional)) opt1 = [e.expr for e in self.exprs if isinstance(e, Optional)] - opt2 = [e for e in self.exprs if e.mayReturnEmpty and not isinstance(e, Optional)] + opt2 = [e for e in self.exprs if e.mayReturnEmpty and not isinstance(e, (Optional, Regex))] self.optionals = opt1 + opt2 self.multioptionals = [e.expr for e in self.exprs if isinstance(e, ZeroOrMore)] self.multirequired = [e.expr for e in self.exprs if isinstance(e, OneOrMore)] diff --git a/unitTests.py b/unitTests.py index 3508394..cd9406e 100644 --- a/unitTests.py +++ b/unitTests.py @@ -2845,14 +2845,28 @@ class OptionalEachTest(ParseTestCase): def runTest1(self): from pyparsing import Optional, Keyword - the_input = "Major Tal Weiss" - parser1 = (Optional('Tal') + Optional('Weiss')) & Keyword('Major') - parser2 = Optional(Optional('Tal') + Optional('Weiss')) & Keyword('Major') - p1res = parser1.parseString( the_input) - p2res = parser2.parseString( the_input) - self.assertEqual(p1res.asList(), p2res.asList(), - "Each failed to match with nested Optionals, " - + str(p1res.asList()) + " should match " + str(p2res.asList())) + for the_input in [ + "Tal Weiss Major", + "Tal Major", + "Weiss Major", + "Major", + "Major Tal", + "Major Weiss", + "Major Tal Weiss", + ]: + print_(the_input) + parser1 = (Optional("Tal") + Optional("Weiss")) & Keyword("Major") + parser2 = Optional(Optional("Tal") + Optional("Weiss")) & Keyword("Major") + p1res = parser1.parseString(the_input) + p2res = parser2.parseString(the_input) + self.assertEqual( + p1res.asList(), + p2res.asList(), + "Each failed to match with nested Optionals, " + + str(p1res.asList()) + + " should match " + + str(p2res.asList()), + ) def runTest2(self): from pyparsing import Word, alphanums, OneOrMore, Group, Regex, Optional @@ -2906,12 +2920,34 @@ class OptionalEachTest(ParseTestCase): 42 """) + def testParseExpressionsWithRegex(self): + from itertools import product + match_empty_regex = pp.Regex(r"[a-z]*") + match_nonempty_regex = pp.Regex(r"[a-z]+") + + parser_classes = pp.ParseExpression.__subclasses__() + test_string = "abc def" + expected = ["abc"] + for expr, cls in product((match_nonempty_regex, match_empty_regex), parser_classes): + print_(expr, cls) + parser = cls([expr]) + parsed_result = parser.parseString(test_string) + print_(parsed_result.dump()) + self.assertParseResultsEquals(parsed_result, expected) + + for expr, cls in product((match_nonempty_regex, match_empty_regex), (pp.MatchFirst, pp.Or)): + parser = cls([expr, expr]) + print_(parser) + parsed_result = parser.parseString(test_string) + print_(parsed_result.dump()) + self.assertParseResultsEquals(parsed_result, expected) def runTest(self): self.runTest1() self.runTest2() self.runTest3() self.runTest4() + self.testParseExpressionsWithRegex() class SumParseResultsTest(ParseTestCase): def runTest(self): |