summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul McGuire <ptmcg@austin.rr.com>2019-06-29 00:19:43 -0500
committerPaul McGuire <ptmcg@austin.rr.com>2019-06-29 00:19:43 -0500
commit450fd02154cfb0ba0cdbec09fc34e8e66a092f11 (patch)
treedacc3d8e1d014fb2af890eed967a8907adfe7f35
parent459f1d5d9f6f200f94bb70e96493fa1bf82dbba8 (diff)
downloadpyparsing-git-450fd02154cfb0ba0cdbec09fc34e8e66a092f11.tar.gz
Fix issue #87, regression in indentedBlock
-rw-r--r--CHANGES5
-rw-r--r--pyparsing.py19
-rw-r--r--unitTests.py123
3 files changed, 136 insertions, 11 deletions
diff --git a/CHANGES b/CHANGES
index 4ab2c7c..af8a04f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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):
"""