Skip to content

Commit 4dace94

Browse files
committed
Fixed bug in nested_expr introduced in 3.2.2, fixes #600
1 parent 3b3ca8d commit 4dace94

File tree

4 files changed

+50
-12
lines changed

4 files changed

+50
-12
lines changed

CHANGES

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,20 @@ Required Python versions by pyparsing version
3535
+--------------------------------------------------+-------------------+
3636
| pyparsing version | Required Python |
3737
+==================================================+===================+
38-
| 3.2.0 - 3.2.2 | 3.9 or later |
38+
| 3.2.0 - later | 3.9 or later |
3939
| 3.0.8 - 3.1.4 | 3.6.8 or later |
4040
| 3.0.0 - 3.0.7 (these versions are discouraged) | 3.6 or later |
4141
| 2.4.7 | 2.7 or later |
4242
| 1.5.7 | 2.6 - 2.7 |
4343
+--------------------------------------------------+-------------------+
4444

45-
Version 3.2.3 - under development
46-
---------------------------------
45+
46+
Version 3.2.3 - March, 2025
47+
---------------------------
48+
- Fixed bug released in 3.2.2 in which `nested_expr` could overwrite parse actions
49+
for defined content, and could truncate list of items within a nested list.
50+
Fixes Issue #600, reported by hoxbro and luisglft, with helpful diag logs and
51+
repro code.
4752

4853

4954
Version 3.2.2 - March, 2025

pyparsing/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def __repr__(self):
121121

122122

123123
__version_info__ = version_info(3, 2, 3, "final", 1)
124-
__version_time__ = "24 Mar 2025 22:55 UTC"
124+
__version_time__ = "25 Mar 2025 01:38 UTC"
125125
__version__ = __version_info__.__version__
126126
__versionTime__ = __version_time__
127127
__author__ = "Paul McGuire <[email protected]>"

pyparsing/helpers.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ def locatedExpr(expr: ParserElement) -> ParserElement:
418418
)
419419

420420

421+
# define special default value to permit None as a significant value for
422+
# ignore_expr
421423
_NO_IGNORE_EXPR_GIVEN = NoMatch()
422424

423425

@@ -498,11 +500,13 @@ def nested_expr(
498500
"""
499501
if ignoreExpr != ignore_expr:
500502
ignoreExpr = ignore_expr if ignoreExpr is _NO_IGNORE_EXPR_GIVEN else ignoreExpr
503+
501504
if ignoreExpr is _NO_IGNORE_EXPR_GIVEN:
502505
ignoreExpr = quoted_string()
503506

504507
if opener == closer:
505508
raise ValueError("opening and closing strings cannot be the same")
509+
506510
if content is None:
507511
if isinstance(opener, str_type) and isinstance(closer, str_type):
508512
opener = typing.cast(str, opener)
@@ -519,8 +523,11 @@ def nested_expr(
519523
)
520524
)
521525
else:
522-
content = empty.copy() + CharsNotIn(
523-
opener + closer + ParserElement.DEFAULT_WHITE_CHARS
526+
content = Combine(
527+
Empty()
528+
+ CharsNotIn(
529+
opener + closer + ParserElement.DEFAULT_WHITE_CHARS
530+
)
524531
)
525532
else:
526533
if ignoreExpr is not None:
@@ -544,10 +551,12 @@ def nested_expr(
544551
raise ValueError(
545552
"opening and closing arguments must be strings if no content expression is given"
546553
)
547-
if ParserElement.DEFAULT_WHITE_CHARS:
548-
content.set_parse_action(
549-
lambda t: t[0].strip(ParserElement.DEFAULT_WHITE_CHARS)
550-
)
554+
555+
# for these internally-created context expressions, simulate whitespace-skipping
556+
if ParserElement.DEFAULT_WHITE_CHARS:
557+
content.set_parse_action(
558+
lambda t: t[0].strip(ParserElement.DEFAULT_WHITE_CHARS)
559+
)
551560

552561
ret = Forward()
553562
if ignoreExpr is not None:
@@ -556,7 +565,9 @@ def nested_expr(
556565
)
557566
else:
558567
ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer))
568+
559569
ret.set_name(f"nested {opener}{closer} expression")
570+
560571
# don't override error message from content expressions
561572
ret.errmsg = None
562573
return ret
@@ -621,7 +632,7 @@ def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">"))
621632

622633

623634
def make_html_tags(
624-
tag_str: Union[str, ParserElement]
635+
tag_str: Union[str, ParserElement],
625636
) -> tuple[ParserElement, ParserElement]:
626637
"""Helper to construct opening and closing tag expressions for HTML,
627638
given a tag name. Matches tags in either upper or lower case,
@@ -648,7 +659,7 @@ def make_html_tags(
648659

649660

650661
def make_xml_tags(
651-
tag_str: Union[str, ParserElement]
662+
tag_str: Union[str, ParserElement],
652663
) -> tuple[ParserElement, ParserElement]:
653664
"""Helper to construct opening and closing tag expressions for XML,
654665
given a tag name. Matches tags only in the given upper/lower case.

tests/test_unit.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5463,6 +5463,28 @@ def testNestedExpressions3(self):
54635463
# make sure things have been put back properly
54645464
self.assertEqual(pp.ParserElement.DEFAULT_WHITE_CHARS, prior_ws_chars)
54655465

5466+
def testNestedExpressions4(self):
5467+
allowed = pp.alphas
5468+
plot_options_short = pp.nestedExpr('[',
5469+
']',
5470+
content=pp.OneOrMore(pp.Word(allowed) ^ pp.quotedString)
5471+
).setResultsName('plot_options')
5472+
5473+
self.assertParseAndCheckList(
5474+
plot_options_short,
5475+
"[slkjdfl sldjf [lsdf'lsdf']]",
5476+
[['slkjdfl', 'sldjf', ['lsdf', "'lsdf'"]]]
5477+
)
5478+
5479+
def testNestedExpressionDoesNotOverwriteParseActions(self):
5480+
content = pp.Word(pp.nums + " ")
5481+
5482+
content.add_parse_action(lambda t: None)
5483+
orig_pa = content.parseAction[0]
5484+
5485+
expr = pp.nested_expr(content=content)
5486+
assert content.parseAction[0] is orig_pa
5487+
54665488
def testWordMinMaxArgs(self):
54675489
parsers = [
54685490
"A" + pp.Word(pp.nums),

0 commit comments

Comments
 (0)