diff options
author | Paul McGuire <ptmcg@austin.rr.com> | 2019-06-29 00:19:43 -0500 |
---|---|---|
committer | Paul McGuire <ptmcg@austin.rr.com> | 2019-06-29 00:19:43 -0500 |
commit | 450fd02154cfb0ba0cdbec09fc34e8e66a092f11 (patch) | |
tree | dacc3d8e1d014fb2af890eed967a8907adfe7f35 | |
parent | 459f1d5d9f6f200f94bb70e96493fa1bf82dbba8 (diff) | |
download | pyparsing-git-450fd02154cfb0ba0cdbec09fc34e8e66a092f11.tar.gz |
Fix issue #87, regression in indentedBlock
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | pyparsing.py | 19 | ||||
-rw-r--r-- | unitTests.py | 123 |
3 files changed, 136 insertions, 11 deletions
@@ -4,6 +4,11 @@ Change Log Version 2.4.1 - ---------------------- +- Fixed issue #87, a regression in indented block. + Reported by Renz Bagaporo, who submitted a very nice repro + example, which makes the bug-fixing process a lot easier, + thanks! + - Modified runTests to call postParse function before dumping out the parsed results - allows for postParse to add further results, such as indications of additional validation success/failure. diff --git a/pyparsing.py b/pyparsing.py index 0e7b5e1..c041fc6 100644 --- a/pyparsing.py +++ b/pyparsing.py @@ -96,7 +96,7 @@ classes inherit from. Use the docstrings for examples of how to: """ __version__ = "2.4.1" -__versionTime__ = "06 Jun 2019 03:33 UTC" +__versionTime__ = "29 Jun 2019 05:14 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -5921,21 +5921,24 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True): def checkUnindent(s,l,t): if l >= len(s): return curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + if not(indentStack and curCol in indentStack): raise ParseException(s,l,"not an unindent") - indentStack.pop() + if curCol < indentStack[-1]: + indentStack.pop() NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') PEER = Empty().setParseAction(checkPeerIndent).setName('') UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + smExpr = Group(Optional(NL) + + INDENT + + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL)) + + UNDENT) else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + smExpr = Group(Optional(NL) + + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL)) + + UNDENT) smExpr.setFailAction(lambda a, b, c, d: reset_stack()) blockStatementExpr.ignore(_bslash + LineEnd()) return smExpr.setName('indented block') diff --git a/unitTests.py b/unitTests.py index 5db9ffe..e14016a 100644 --- a/unitTests.py +++ b/unitTests.py @@ -3817,12 +3817,78 @@ class UnicodeTests(ParseTestCase): self.assertEqual(result.asDict(), {u'şehir': u'İzmir', u'ülke': u'Türkiye', u'nüfus': 4279677}, "Failed to parse Turkish key-value pairs") + +class IndentedBlockExampleTest(ParseTestCase): + # Make sure example in indentedBlock docstring actually works! + def runTest(self): + from textwrap import dedent + from pyparsing import (Word, alphas, alphanums, indentedBlock, Optional, delimitedList, Group, Forward, + nums, OneOrMore) + data = dedent(''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''') + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group("(" + Optional(delimitedList(identifier)) + ")") + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group(funcDecl + func_body) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << (funcDef | assignment | identifier) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + self.assertEqual(parseTree.asList(), + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]], + "Failed indentedBlock example" + ) + + class IndentedBlockTest(ParseTestCase): # parse pseudo-yaml indented text def runTest(self): - if pp.ParserElement.packrat_cache: - print_("cannot test indentedBlock with packrat enabled") - return import textwrap EQ = pp.Suppress('=') @@ -3854,6 +3920,57 @@ class IndentedBlockTest(ParseTestCase): self.assertEqual(result.c.c2.c21, 999, "invalid indented block result") +class IndentedBlockTest2(ParseTestCase): + # exercise indentedBlock with example posted in issue #87 + def runTest(self): + from textwrap import dedent + from pyparsing import Word, alphas, alphanums, Suppress, Forward, indentedBlock, Literal, OneOrMore + + indent_stack = [1] + + key = Word(alphas, alphanums) + Suppress(":") + stmt = Forward() + + suite = indentedBlock(stmt, indent_stack) + body = key + suite + + pattern = (Word(alphas) + Suppress("(") + Word(alphas) + Suppress(")")) + stmt << pattern + + def key_parse_action(toks): + print_("Parsing '%s'..." % toks[0]) + + key.setParseAction(key_parse_action) + header = Suppress("[") + Literal("test") + Suppress("]") + content = (header + OneOrMore(indentedBlock(body, indent_stack, False))) + + contents = Forward() + suites = indentedBlock(content, indent_stack) + + extra = Literal("extra") + Suppress(":") + suites + contents << (content | extra) + + parser = OneOrMore(contents) + + sample = dedent("""\ + extra: + [test] + one0: + two (three) + four0: + five (seven) + extra: + [test] + one1: + two (three) + four1: + five (seven) + """) + + success, _ = parser.runTests([sample]) + self.assertTrue(success, "Failed indentedBlock test for issue #87") + + class IndentedBlockScanTest(ParseTestCase): def get_parser(self): """ |