summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorptmcg <ptmcg@9bf210a0-9d2d-494c-87cf-cfb32e7dff7b>2016-05-13 18:51:50 +0000
committerptmcg <ptmcg@9bf210a0-9d2d-494c-87cf-cfb32e7dff7b>2016-05-13 18:51:50 +0000
commitb0c3af8e8d9e3b3eac8455c1bd9bb1a32851bc6c (patch)
tree2a0e9a8ac3509bc1da1343bf7bad2d05c412e8f9
parentf7336a81d67792afe97320398e9eb948b3fc0817 (diff)
downloadpyparsing-b0c3af8e8d9e3b3eac8455c1bd9bb1a32851bc6c.tar.gz
Added 'fatal' option to addCondition; enhancements to runTests, and added unit test for runTests
git-svn-id: svn://svn.code.sf.net/p/pyparsing/code/trunk@348 9bf210a0-9d2d-494c-87cf-cfb32e7dff7b
-rw-r--r--src/CHANGES10
-rw-r--r--src/pyparsing.py55
-rw-r--r--src/unitTests.py58
3 files changed, 103 insertions, 20 deletions
diff --git a/src/CHANGES b/src/CHANGES
index 1a50a73..d2aa8ad 100644
--- a/src/CHANGES
+++ b/src/CHANGES
@@ -5,15 +5,16 @@ Change Log
Version 2.1.4 -
------------------------------
- Split out the '==' behavior in ParserElement, now implemented
- as the matches() method. Using '==' for string test purposes will
- be removed in a future release.
+ as the ParserElement.matches() method. Using '==' for string test
+ purposes will be removed in a future release.
- Expanded capabilities of runTests(). Will now accept embedded
comments (default is Python style, leading '#' character, but
customizable). Comments will be emitted along with the tests and
test output. Useful during test development, to create a test string
consisting only of test case description comments separated by
- blank lines, and then fill in the test cases.
+ blank lines, and then fill in the test cases. Will also highlight
+ ParseFatalExceptions with "(FATAL)".
- Added a 'pyparsing_common' class containing common/helpful little
expressions such as integer, float, identifier, etc. I used this
@@ -23,6 +24,9 @@ Version 2.1.4 -
- Minor enhancement to traceParseAction decorator, to retain the
parse action's name for the trace output.
+- Added optional 'fatal' keyword arg to addCondition, to indicate that
+ a condition failure should halt parsing immediately.
+
Version 2.1.3 - May, 2016
------------------------------
diff --git a/src/pyparsing.py b/src/pyparsing.py
index 0dc0647..8047e79 100644
--- a/src/pyparsing.py
+++ b/src/pyparsing.py
@@ -58,7 +58,7 @@ The pyparsing module handles some of the problems that are typically vexing when
"""
__version__ = "2.1.4"
-__versionTime__ = "13 May 2016 09:04 UTC"
+__versionTime__ = "13 May 2016 18:25 UTC"
__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
import string
@@ -953,6 +953,9 @@ class ParserElement(object):
If the functions in fns modify the tokens, they can return them as the return
value from fn, and the modified list of tokens will replace the original.
Otherwise, fn does not need to return any value.
+
+ Optional keyword arguments::
+ - callDuringTry = (default=False) indicate if parse action should be run during lookaheads and alternate testing
Note: the default parsing behavior is to expand tabs in the input string
before starting the parsing process. See L{I{parseString}<parseString>} for more information
@@ -972,14 +975,19 @@ class ParserElement(object):
def addCondition(self, *fns, **kwargs):
"""Add a boolean predicate function to expression's list of parse actions. See
- L{I{setParseAction}<setParseAction>}. Optional keyword argument C{message} can
- be used to define a custom message to be used in the raised exception."""
- msg = kwargs.get("message") or "failed user-defined condition"
+ L{I{setParseAction}<setParseAction>} for function call signatures. Unlike C{setParseAction},
+ functions passed to C{addCondition} need to return boolean success/fail of the condition.
+
+ Optional keyword arguments::
+ - message = define a custom message to be used in the raised exception
+ - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
+ """
+ msg = kwargs.get("message", "failed user-defined condition")
+ exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
for fn in fns:
def pa(s,l,t):
if not bool(_trim_arity(fn)(s,l,t)):
- raise ParseException(s,l,msg)
- return t
+ raise exc_type(s,l,msg)
self.parseAction.append(pa)
self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
return self
@@ -1632,7 +1640,7 @@ class ParserElement(object):
except ParseBaseException:
return False
- def runTests(self, tests, parseAll=False, comment='#'):
+ def runTests(self, tests, parseAll=False, comment='#', printResults=True):
"""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.
@@ -1640,33 +1648,48 @@ class ParserElement(object):
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
- - comment - (default='#') - expression for indicating embedded comments in the test string;
- pass None to disable comment filtering
+ - comment - (default='#') - expression for indicating embedded comments in the test
+ string; pass None to disable comment filtering
+ - printResults - (default=True) prints test output to stdout; if False, returns a
+ (success, results) tuple, where success indicates that all tests succeeded, and the
+ results contain a list of lines of each test's output as it would have been
+ printed to stdout
"""
if isinstance(tests, basestring):
tests = list(map(str.strip, tests.splitlines()))
if isinstance(comment, basestring):
comment = Literal(comment)
+ allResults = []
comments = []
+ success = True
for t in tests:
if comment is not None and comment.matches(t, False) or comments and not t:
comments.append(t)
continue
if not t:
continue
- out = comments + [t]
+ out = ['\n'.join(comments), t]
comments = []
try:
out.append(self.parseString(t, parseAll=parseAll).dump())
- except ParseException as pe:
+ except ParseBaseException as pe:
+ fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
if '\n' in t:
out.append(line(pe.loc, t))
- out.append(' '*(col(pe.loc,t)-1) + '^')
+ out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
else:
- out.append(' '*pe.loc + '^')
- out.append(str(pe))
- out.append('')
- print('\n'.join(out))
+ out.append(' '*pe.loc + '^' + fatal)
+ out.append("FAIL: " + str(pe))
+ success = False
+
+ if printResults:
+ out.append('')
+ print('\n'.join(out))
+ else:
+ allResults.append(out)
+
+ if not printResults:
+ return success, allResults
class Token(ParserElement):
diff --git a/src/unitTests.py b/src/unitTests.py
index 9e3bcd2..b545af4 100644
--- a/src/unitTests.py
+++ b/src/unitTests.py
@@ -2362,7 +2362,7 @@ class PopTest(ParseTestCase):
class AddConditionTest(ParseTestCase):
def runTest(self):
- from pyparsing import Word, alphas, nums
+ from pyparsing import Word, alphas, nums, Suppress, ParseFatalException
numParser = Word(nums)
numParser.addParseAction(lambda s,l,t: int(t[0]))
@@ -2373,6 +2373,26 @@ class AddConditionTest(ParseTestCase):
print_(result.asList())
assert result.asList() == [[7],[9]], "failed to properly process conditions"
+ numParser = Word(nums)
+ numParser.addParseAction(lambda s,l,t: int(t[0]))
+ rangeParser = (numParser("from_") + Suppress('-') + numParser("to"))
+
+ result = rangeParser.searchString("1-4 2-4 4-3 5 6 7 8 9 10")
+ print_(result.asList())
+ assert result.asList() == [[1, 4], [2, 4], [4, 3]], "failed to properly process conditions"
+
+ rangeParser.addCondition(lambda t: t.to > t.from_, message="from must be <= to", fatal=False)
+ result = rangeParser.searchString("1-4 2-4 4-3 5 6 7 8 9 10")
+ print_(result.asList())
+ assert result.asList() == [[1, 4], [2, 4]], "failed to properly process conditions"
+
+ rangeParser = (numParser("from_") + Suppress('-') + numParser("to"))
+ rangeParser.addCondition(lambda t: t.to > t.from_, message="from must be <= to", fatal=True)
+ try:
+ result = rangeParser.searchString("1-4 2-4 4-3 5 6 7 8 9 10")
+ assert False, "failed to interrupt parsing on fatal condition failure"
+ except ParseFatalException:
+ print_("detected fatal condition")
class PatientOrTest(ParseTestCase):
def runTest(self):
@@ -2570,6 +2590,42 @@ class TraceParseActionDecoratorTest(ParseTestCase):
integer.addParseAction(traceParseAction(Z()))
integer.parseString("132")
+class RunTestsTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Word, nums, delimitedList
+
+ integer = Word(nums).setParseAction(lambda t : int(t[0]))
+ intrange = integer("start") + '-' + integer("end")
+ intrange.addCondition(lambda t: t.end > t.start, message="invalid range, start must be <= end", fatal=True)
+ intrange.addParseAction(lambda t: list(range(t.start, t.end+1)))
+
+ indices = delimitedList(intrange | integer)
+ indices.addParseAction(lambda t: sorted(set(t)))
+
+ tests = """\
+ # normal data
+ 1-3,2-4,6,8-10,16
+
+ # invalid range
+ 1-2, 3-1, 4-6, 7, 12
+
+ # lone integer
+ 11"""
+ results = indices.runTests(tests, printResults=False)[1]
+ #~ import pprint
+ #~ pprint.pprint(results)
+
+ expectedResults = [
+ ['# normal data', '1-3,2-4,6,8-10,16', '[1, 2, 3, 4, 6, 8, 9, 10, 16]'],
+ ['# invalid range',
+ '1-2, 3-1, 4-6, 7, 12',
+ ' ^(FATAL)',
+ 'FAIL: invalid range, start must be <= end (at char 5), (line:1, col:6)'],
+ ['# lone integer', '11', '[11]']]
+
+ for res,expected in zip(results, expectedResults):
+ assert res == expected, "failed test: " + expected[0][2:]
+
class MiscellaneousParserTests(ParseTestCase):
def runTest(self):
import pyparsing