From ff26efcec65724844461f54a0eabd04487f6291f Mon Sep 17 00:00:00 2001 From: ptmcg Date: Tue, 4 Oct 2016 00:23:22 +0000 Subject: Fixed behavior of LineStart; added AutoReset to other unit tests that modify global state in pyparsing; also, disable output buffering in unit tests when specific test classes are given to build the test suite git-svn-id: svn://svn.code.sf.net/p/pyparsing/code/trunk@446 9bf210a0-9d2d-494c-87cf-cfb32e7dff7b --- src/CHANGES | 5 +++ src/pyparsing.py | 18 +++++----- src/unitTests.py | 105 +++++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/CHANGES b/src/CHANGES index 7a9d3f2..bf964aa 100644 --- a/src/CHANGES +++ b/src/CHANGES @@ -7,6 +7,11 @@ Version 2.1.10 - - Fixed bug in reporting named parse results for ZeroOrMore expressions, thanks Ethan Nash for reporting this! +- Fixed behavior of LineStart to be much more predictable. + LineStart can now be used to detect if the next parse position + is col 1, factoring in potential leading whitespace (which would + cause LineStart to fail). + - Added support for multiline test strings in runTests. - Fixed bug in ParseResults.dump when keys were not strings. diff --git a/src/pyparsing.py b/src/pyparsing.py index 516e804..a12e1b6 100644 --- a/src/pyparsing.py +++ b/src/pyparsing.py @@ -61,7 +61,7 @@ The pyparsing module handles some of the problems that are typically vexing when """ __version__ = "2.1.10" -__versionTime__ = "02 Oct 2016 23:15 UTC" +__versionTime__ = "04 Oct 2016 00:19 UTC" __author__ = "Paul McGuire " import string @@ -3085,27 +3085,25 @@ class GoToColumn(_PositionToken): ret = instring[ loc: newloc ] return newloc, ret + class LineStart(_PositionToken): """ Matches if current position is at the beginning of a line within the parse string """ def __init__( self ): super(LineStart,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n", "")) self.errmsg = "Expected start of line" def preParse( self, instring, loc ): - preloc = super(LineStart,self).preParse(instring,loc) - if instring[preloc] == "\n": - loc += 1 + # don't do any pre-parsing for this class return loc def parseImpl( self, instring, loc, doActions=True ): - if not( loc==0 or - (loc == self.preParse( instring, 0 )) or - (instring[loc-1] == "\n") ): #col(loc, instring) != 1: - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] + loc, _ = Optional(White(' \t').leaveWhitespace() + Empty()).parseImpl(instring, loc) + if loc == 0 or col(loc, instring) == 1: + return loc, [] + raise ParseException(instring, loc, self.errmsg, self) class LineEnd(_PositionToken): """ diff --git a/src/unitTests.py b/src/unitTests.py index 04aa5d8..d97c27a 100644 --- a/src/unitTests.py +++ b/src/unitTests.py @@ -76,6 +76,8 @@ class AutoReset(object): for attr, value in zip(self.save_attrs, self.save_values): setattr(self.ob, attr, value) +BUFFER_OUTPUT = True + class ParseTestCase(TestCase): def __init__(self): super(ParseTestCase, self).__init__(methodName='_runTest') @@ -87,8 +89,9 @@ class ParseTestCase(TestCase): try: with AutoReset(sys, 'stdout', 'stderr'): try: - sys.stdout = buffered_stdout - sys.stderr = buffered_stdout + if BUFFER_OUTPUT: + sys.stdout = buffered_stdout + sys.stderr = buffered_stdout print_(">>>> Starting test",str(self)) self.runTest() @@ -97,8 +100,9 @@ class ParseTestCase(TestCase): print_() except Exception as exc: - print_() - print_(buffered_stdout.getvalue()) + if BUFFER_OUTPUT: + print_() + print_(buffered_stdout.getvalue()) raise @@ -1674,6 +1678,63 @@ class CountedArrayTest3(ParseTestCase): assert r.asList() == [[5,7],[0,1,2,3,4,5],[],[5,4,3]], \ "Failed matching countedArray, got " + str(r.asList()) +class LineStartTest(ParseTestCase): + def runTest(self): + import pyparsing as pp + + pass_tests = [ + """\ + AAA + BBB + """, + """\ + AAA... + BBB + """, + ] + fail_tests = [ + """\ + AAA... + ...BBB + """, + """\ + AAA BBB + """, + ] + + # cleanup test strings + pass_tests = ['\n'.join(s.lstrip() for s in t.splitlines()).replace('.', ' ') for t in pass_tests] + fail_tests = ['\n'.join(s.lstrip() for s in t.splitlines()).replace('.', ' ') for t in fail_tests] + + test_patt = pp.Word('A') - pp.LineStart() + pp.Word('B') + print(test_patt.streamline()) + success = test_patt.runTests(pass_tests)[0] + assert success, "failed LineStart passing tests (1)" + + success = test_patt.runTests(fail_tests, failureTests=True)[0] + assert success, "failed LineStart failure mode tests (1)" + + with AutoReset(pp.ParserElement, "DEFAULT_WHITE_CHARS"): + print(r'no \n in default whitespace chars') + pp.ParserElement.setDefaultWhitespaceChars(' ') + + test_patt = pp.Word('A') - pp.LineStart() + pp.Word('B') + print(test_patt.streamline()) + # should fail the pass tests too, since \n is no longer valid whitespace and we aren't parsing for it + success = test_patt.runTests(pass_tests, failureTests=True)[0] + assert success, "failed LineStart passing tests (2)" + + success = test_patt.runTests(fail_tests, failureTests=True)[0] + assert success, "failed LineStart failure mode tests (2)" + + test_patt = pp.Word('A') - pp.LineEnd().suppress() + pp.LineStart() + pp.Word('B') + pp.LineEnd().suppress() + print(test_patt.streamline()) + success = test_patt.runTests(pass_tests)[0] + assert success, "failed LineStart passing tests (3)" + + success = test_patt.runTests(fail_tests, failureTests=True)[0] + assert success, "failed LineStart failure mode tests (3)" + class LineAndStringEndTest(ParseTestCase): def runTest(self): from pyparsing import OneOrMore,lineEnd,alphanums,Word,stringEnd,delimitedList,SkipTo @@ -3197,16 +3258,14 @@ class DefaultKeywordCharsTest(ParseTestCase): else: pass - save_kwd_chars = pp.Keyword.DEFAULT_KEYWORD_CHARS - pp.Keyword.setDefaultKeywordChars(pp.alphas) - try: - pp.Keyword("start").parseString("start1000") - except pp.ParseException: - assert False, "failed to match keyword using updated keyword chars" - else: - pass - - pp.Keyword.setDefaultKeywordChars(save_kwd_chars) + with AutoReset(pp.Keyword, "DEFAULT_KEYWORD_CHARS"): + pp.Keyword.setDefaultKeywordChars(pp.alphas) + try: + pp.Keyword("start").parseString("start1000") + except pp.ParseException: + assert False, "failed to match keyword using updated keyword chars" + else: + pass try: pp.CaselessKeyword("START").parseString("start1000") @@ -3222,15 +3281,14 @@ class DefaultKeywordCharsTest(ParseTestCase): else: pass - pp.Keyword.setDefaultKeywordChars(pp.alphas) - try: - pp.CaselessKeyword("START").parseString("start1000") - except pp.ParseException: - assert False, "failed to match keyword using updated keyword chars" - else: - pass - - pp.Keyword.setDefaultKeywordChars(save_kwd_chars) + with AutoReset(pp.Keyword, "DEFAULT_KEYWORD_CHARS"): + pp.Keyword.setDefaultKeywordChars(pp.alphas) + try: + pp.CaselessKeyword("START").parseString("start1000") + except pp.ParseException: + assert False, "failed to match keyword using updated keyword chars" + else: + pass class MiscellaneousParserTests(ParseTestCase): @@ -3493,6 +3551,7 @@ if console: if not testclasses: testRunner.run( makeTestSuite() ) else: + BUFFER_OUTPUT = False if lp is None: testRunner.run( makeTestSuiteTemp(testclasses) ) else: -- cgit v1.2.1