summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorptmcg <ptmcg@austin.rr.com>2020-06-09 23:28:30 -0500
committerptmcg <ptmcg@austin.rr.com>2020-06-09 23:28:30 -0500
commit464ac7100d40ff17b3f90e3e779de03d863a95b1 (patch)
treed13b9322e9484282c570bfb22fe57184833a0853
parent6267bb50b3b462e0515e204a83ccdfc3c65870d7 (diff)
downloadpyparsing-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--CHANGES11
-rw-r--r--pyparsing/__init__.py2
-rw-r--r--pyparsing/core.py34
-rw-r--r--tests/test_unit.py77
4 files changed, 105 insertions, 19 deletions
diff --git a/CHANGES b/CHANGES
index 70a7cf4..5af1e0c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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")