From e9d3d92b2c1ae540479ab5397adf1b95d16871d8 Mon Sep 17 00:00:00 2001 From: ptmcg Date: Sat, 31 Oct 2015 17:58:32 +0000 Subject: Fixed bug in Each with multiple Optionals; added parseAll and output cleanup to runTests; improved exception messages for MatchFirst and Or exceptions. git-svn-id: svn://svn.code.sf.net/p/pyparsing/code/trunk@296 9bf210a0-9d2d-494c-87cf-cfb32e7dff7b --- src/CHANGES | 18 ++++++++++++++++++ src/pyparsing.py | 35 ++++++++++++++++++++++------------- src/unitTests.py | 18 +++++++++++++++++- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/CHANGES b/src/CHANGES index 7c51766..7332ce7 100644 --- a/src/CHANGES +++ b/src/CHANGES @@ -2,6 +2,24 @@ Change Log ========== +Version 2.0.6 - +--------------------------- +- Fixed a bug in Each when multiple Optional elements are present. + Thanks for reporting this, whereswalden on SO. + +- Added optional parseAll argument to runTests, whether tests should + require the entire input string to be parsed or not (similar to + parseAll argument to parseString). Plus a little neaten-up of the + output on Python 2 (no stray ()'s). + +- Modified exception messages from MatchFirst and Or expressions. These + were formerly misleading as they would only give the first or longest + exception mismatch error message. Now the error message includes all + the alternatives that were possible matches. Originally proposed by + a pyparsing user, but I've lost the email thread - finally figured out + a fairly clean way to do this. + + Version 2.0.5 - --------------------------- - (&$(@#&$(@!!!! Some "print" statements snuck into pyparsing v2.0.4, diff --git a/src/pyparsing.py b/src/pyparsing.py index ef429db..d239524 100644 --- a/src/pyparsing.py +++ b/src/pyparsing.py @@ -57,8 +57,8 @@ The pyparsing module handles some of the problems that are typically vexing when - embedded comments """ -__version__ = "2.0.5" -__versionTime__ = "29 Oct 2015 08:08" +__version__ = "2.0.6" +__versionTime__ = "31 Oct 2015 12:41" __author__ = "Paul McGuire " import string @@ -1548,25 +1548,30 @@ class ParserElement(object): def __rne__(self,other): return not (self == other) - def runTests(self, tests): + def runTests(self, tests, parseAll=False): """Execute the parse expression on a series of test strings, showing each test, the parsed results or where the parse failed. Quick and easy way to run a parse expression against a list of sample strings. + + Parameters: + - tests - a list of separate test strings, or a multiline string of test strings + - parseAll - (default=False) - flag to pass to C{L{parseString}} when running tests """ if isinstance(tests, basestring): tests = map(str.strip, tests.splitlines()) for t in tests: - print (t) + out = [t] try: - print (self.parseString(t).dump()) + out.append(self.parseString(t, parseAll=parseAll).dump()) except ParseException as pe: if '\n' in t: - print (line(pe.loc, t)) - print (' '*(col(pe.loc,t)-1) + '^') + out.append(line(pe.loc, t)) + out.append(' '*(col(pe.loc,t)-1) + '^') else: - print (' '*pe.loc + '^') - print (pe) - print() + out.append(' '*pe.loc + '^') + out.append(str(pe)) + out.append('') + print('\n'.join(out)) class Token(ParserElement): @@ -2348,6 +2353,8 @@ class ParseExpression(ParserElement): self.mayReturnEmpty |= other.mayReturnEmpty self.mayIndexError |= other.mayIndexError + self.errmsg = "Expected " + str(self) + return self def setResultsName( self, name, listAllMatches=False ): @@ -2466,6 +2473,7 @@ class Or(ParseExpression): if maxMatchLoc < 0: if maxException is not None: + maxException.msg = self.errmsg raise maxException else: raise ParseException(instring, loc, "no defined alternatives to match", self) @@ -2523,6 +2531,7 @@ class MatchFirst(ParseExpression): # only got here if no expression matched, raise exception for match that made it the furthest else: if maxException is not None: + maxException.msg = self.errmsg raise maxException else: raise ParseException(instring, loc, "no defined alternatives to match", self) @@ -2561,7 +2570,7 @@ class Each(ParseExpression): def parseImpl( self, instring, loc, doActions=True ): if self.initExprGroups: opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and e not in opt1 ] + opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)] 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) ] @@ -3766,9 +3775,9 @@ if __name__ == "__main__": ident = Word( alphas, alphanums + "_$" ) columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - columnNameList = Group( delimitedList( columnName ) )#.setName("columns") + columnNameList = Group( delimitedList( columnName ) ).setName("columns") tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - tableNameList = Group( delimitedList( tableName ) )#.setName("tables") + tableNameList = Group( delimitedList( tableName ) ).setName("tables") simpleSQL = ( selectToken + \ ( '*' | columnNameList ).setResultsName( "columns" ) + \ fromToken + \ diff --git a/src/unitTests.py b/src/unitTests.py index a675ffe..422ceb3 100644 --- a/src/unitTests.py +++ b/src/unitTests.py @@ -2120,7 +2120,7 @@ class WordBoundaryExpressionsTest(ParseTestCase): print_() class OptionalEachTest(ParseTestCase): - def runTest(self): + def runTest1(self): from pyparsing import Optional, Keyword the_input = "Major Tal Weiss" @@ -2131,6 +2131,22 @@ class OptionalEachTest(ParseTestCase): assert 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, Suppress, OneOrMore, Group, Regex, Optional + + word = Word(alphanums + '_').setName("word") + with_stmt = 'with' + OneOrMore(Group(word('key') + '=' + word('value')))('overrides') + using_stmt = 'using' + Regex('id-[0-9a-f]{8}')('id') + modifiers = Optional(with_stmt('with_stmt')) & Optional(using_stmt('using_stmt')) + + assert modifiers == "with foo=bar bing=baz using id-deadbeef" + assert not modifiers == "with foo=bar bing=baz using id-deadbeef using id-feedfeed" + + def runTest(self): + self.runTest1() + self.runTest2() + class SumParseResultsTest(ParseTestCase): def runTest(self): -- cgit v1.2.1