diff options
author | ptmcg <ptmcg@austin.rr.com> | 2020-06-09 23:28:30 -0500 |
---|---|---|
committer | ptmcg <ptmcg@austin.rr.com> | 2020-06-09 23:28:30 -0500 |
commit | 464ac7100d40ff17b3f90e3e779de03d863a95b1 (patch) | |
tree | d13b9322e9484282c570bfb22fe57184833a0853 | |
parent | 6267bb50b3b462e0515e204a83ccdfc3c65870d7 (diff) | |
download | pyparsing-git-464ac7100d40ff17b3f90e3e779de03d863a95b1.tar.gz |
Add new warnings about common errors using Forward: warn_on_parse_using_empty_Forward warns when failing to attach an expression; warn_on_assignment_to_Forward warns when using '=' instead of '<<='
-rw-r--r-- | CHANGES | 11 | ||||
-rw-r--r-- | pyparsing/__init__.py | 2 | ||||
-rw-r--r-- | pyparsing/core.py | 34 | ||||
-rw-r--r-- | tests/test_unit.py | 77 |
4 files changed, 105 insertions, 19 deletions
@@ -103,6 +103,17 @@ Version 3.0.0a2 - June, 2020 subitems. Fixes bug where adding a results name would hide lower-level structures in the ParseResults. +- Added new __diag__ warnings: + + "warn_on_parse_using_empty_Forward" - warns that a Forward + has been included in a grammar, but no expression was + attached to it using '<<=' or '<<' + + "warn_on_assignment_to_Forward" - warns that a Forward has + been created, but was probably later overwritten by + erroneously using '=' instead of '<<=' (this is a common + mistake when using Forwards) + - Fixed bug in ParseResults repr() which showed all matching entries for a results name, even if listAllMatches was set to False when creating the ParseResults originally. Reported diff --git a/pyparsing/__init__.py b/pyparsing/__init__.py index 7ea212c..269aa41 100644 --- a/pyparsing/__init__.py +++ b/pyparsing/__init__.py @@ -95,7 +95,7 @@ classes inherit from. Use the docstrings for examples of how to: """ __version__ = "3.0.0a2" -__versionTime__ = "13 May 2020 19:13 UTC" +__versionTime__ = "10 June 2020 04:26 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" from .util import * diff --git a/pyparsing/core.py b/pyparsing/core.py index 8f2979e..969748b 100644 --- a/pyparsing/core.py +++ b/pyparsing/core.py @@ -85,9 +85,13 @@ class __diag__(__config_flags): - warn_ungrouped_named_tokens_in_collection - flag to enable warnings when a results name is defined on a containing expression with ungrouped subexpressions that also have results names - - warn_name_set_on_empty_Forward - flag to enable warnings whan a Forward is defined + - warn_name_set_on_empty_Forward - flag to enable warnings when a Forward is defined with a results name, but has no contents defined - - warn_on_multiple_string_args_to_oneof - flag to enable warnings whan oneOf is + - warn_on_parse_using_empty_Forward - flag to enable warnings when a Forward is + defined in a grammar but has never had an expression attached to it + - warn_on_assignment_to_Forward - flag to enable warnings when a Forward is defined + but is overwritten by assigning using '=' instead of '<<=' or '<<' + - warn_on_multiple_string_args_to_oneof - flag to enable warnings when oneOf is incorrectly called with multiple str arguments - enable_debug_on_named_expressions - flag to auto-enable debug on all subsequent calls to ParserElement.setName() @@ -98,6 +102,8 @@ class __diag__(__config_flags): warn_multiple_tokens_in_named_alternation = False warn_ungrouped_named_tokens_in_collection = False warn_name_set_on_empty_Forward = False + warn_on_parse_using_empty_Forward = False + warn_on_assignment_to_Forward = False warn_on_multiple_string_args_to_oneof = False warn_on_match_first_with_lshift_operator = False enable_debug_on_named_expressions = False @@ -4286,10 +4292,13 @@ class Forward(ParseElementEnhance): """ def __init__(self, other=None): + self.caller_frame = traceback.extract_stack(limit=2)[0] super().__init__(other, savelist=False) self.lshift_line = None def __lshift__(self, other): + if hasattr(self, "caller_frame"): + del self.caller_frame if isinstance(other, str_type): other = self._literalStringClass(other) self.expr = other @@ -4315,13 +4324,32 @@ class Forward(ParseElementEnhance): and caller_line == self.lshift_line ): warnings.warn( - "using '<<' operator with '|' is probably error, use '<<='", + "using '<<' operator with '|' is probably an error, use '<<='", SyntaxWarning, stacklevel=3, ) ret = super().__or__(other) return ret + def __del__(self): + # see if we are getting dropped because of '=' reassignment of var instead of '<<=' or '<<' + if self.expr is None and __diag__.warn_on_assignment_to_Forward: + warnings.warn_explicit( + "Forward defined here but no expression attached later using '<<=' or '<<'", + SyntaxWarning, + filename=self.caller_frame.filename, + lineno=self.caller_frame.lineno, + ) + + def parseImpl(self, instring, loc, doActions=True): + if self.expr is None and __diag__.warn_on_parse_using_empty_Forward: + warnings.warn( + "Forward expression was never assigned a value, will not parse any input", + UserWarning, + stacklevel=3, + ) + return super().parseImpl(instring, loc, doActions) + def leaveWhitespace(self, recursive=True): self.skipWhitespace = False return self diff --git a/tests/test_unit.py b/tests/test_unit.py index b16da18..097290a 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -1912,7 +1912,7 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): testInput = "myc(114)r(11)dd" stream = Forward() - stream << Optional(Word(alphas)) + Optional("(" + Word(nums) + ")" + stream) + stream <<= Optional(Word(alphas)) + Optional("(" + Word(nums) + ")" + stream) expected = ["".join(stream.parseString(testInput))] print(expected) @@ -3855,12 +3855,11 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): function_name = identifier.copy() # ~ function_name = ~AA + Word("Z") #identifier.copy() expr = pp.Forward().setName("expr") - expr << ( - pp.Group( - function_name + LPAR + pp.Optional(pp.delimitedList(expr)) + RPAR - ).setName("functionCall") - | identifier.setName("ident") # .setDebug()#.setBreak() - ) + expr <<= pp.Group( + function_name + LPAR + pp.Optional(pp.delimitedList(expr)) + RPAR + ).setName("functionCall") | identifier.setName( + "ident" + ) # .setDebug()#.setBreak() stmt = DO + pp.Group(pp.delimitedList(identifier + ".*" | expr)) result = stmt.parseString("DO Z") @@ -5053,13 +5052,13 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): values = pp.Group(pp.delimitedList(value, ",")) # ~ values = delimitedList(value, ",").setParseAction(lambda toks: [toks.asList()]) - value_list << lbracket + values + rbracket + value_list <<= lbracket + values + rbracket identifier = pp.Word(pp.alphanums + "_.") assignment = pp.Group(identifier + equals + pp.Optional(value)) assignments = pp.Dict(pp.delimitedList(assignment, ";")) - value_dict << lbrace + assignments + rbrace + value_dict <<= lbrace + assignments + rbrace response = assignments @@ -6254,7 +6253,7 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): ) rvalue << (funcCall | identifier | pp.Word(pp.nums)) assignment = pp.Group(identifier + "=" + rvalue) - stmt << (funcDef | assignment | identifier) + stmt <<= funcDef | assignment | identifier module_body = pp.OneOrMore(stmt) @@ -6352,7 +6351,7 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): + pp.Word(pp.alphas) + pp.Suppress(")") ) - stmt << pattern + stmt <<= pattern def key_parse_action(toks): print("Parsing '%s'..." % toks[0]) @@ -6365,7 +6364,7 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): suites = pp.indentedBlock(content, indent_stack) extra = pp.Literal("extra") + pp.Suppress(":") - suites - contents << (content | extra) + contents <<= content | extra parser = pp.OneOrMore(contents) @@ -6798,6 +6797,45 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): ): base("x") + def testWarnParsingEmptyForward(self): + """ + - warn_on_parse_using_empty_Forward - flag to enable warnings whan a Forward + has no contents defined (default=False) + """ + + with ppt.reset_pyparsing_context(): + pp.__diag__.enable("warn_on_parse_using_empty_Forward") + + base = pp.Forward() + + with self.assertWarns( + UserWarning, + msg="failed to warn when naming an empty Forward expression", + ): + try: + print(base.parseString("x")) + except ParseException as pe: + pass + + def testWarnIncorrectAssignmentToForward(self): + """ + - warn_on_parse_using_empty_Forward - flag to enable warnings whan a Forward + has no contents defined (default=False) + """ + + with ppt.reset_pyparsing_context(): + pp.__diag__.enable("warn_on_assignment_to_Forward") + + def a_method(): + base = pp.Forward() + base = pp.Word(pp.alphas)[...] | "(" + base + ")" + + with self.assertWarns( + SyntaxWarning, + msg="failed to warn when using '=' to assign expression to a Forward", + ): + a_method() + def testWarnOnMultipleStringArgsToOneOf(self): """ - warn_on_multiple_string_args_to_oneof - flag to enable warnings whan oneOf is @@ -7235,15 +7273,15 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): fwd = pp.Forward() g1 = pp.OneOrMore((pp.Literal("A") + "B" + "C") | fwd) g2 = ("C" + g1)[...] - fwd << pp.Group(g2) + fwd <<= pp.Group(g2) testValidation(fwd, "fwd", isValid=True) fwd2 = pp.Forward() - fwd2 << pp.Group("A" | fwd2) + fwd2 <<= pp.Group("A" | fwd2) testValidation(fwd2, "fwd2", isValid=False) fwd3 = pp.Forward() - fwd3 << pp.Optional("A") + fwd3 + fwd3 <<= pp.Optional("A") + fwd3 testValidation(fwd3, "fwd3", isValid=False) def testGetNameBehavior(self): @@ -7455,6 +7493,15 @@ class Test2_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase): print("unsafe << and |, should warn") fwd << pp.Word("a") | pp.Word("b") + with self.assertWarns( + SyntaxWarning, + msg="failed to warn of using << and | operators (within lambda)", + ): + fwd = pp.Forward() + print("unsafe << and |, should warn") + fwd_fn = lambda expr1, expr2: fwd << expr1 | expr2 + fwd_fn(pp.Word("a"), pp.Word("b")) + fwd = pp.Forward() print("safe <<= and |, should not warn") fwd <<= pp.Word("a") | pp.Word("b") |