Browse Source

Intermediate changes

robot-piglet 11 months ago
parent
commit
dd2d6a2c7b

+ 2 - 1
contrib/python/pyparsing/py3/.dist-info/METADATA

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pyparsing
-Version: 3.1.1
+Version: 3.1.2
 Summary: pyparsing module - Classes and methods to define and execute parsing grammars
 Author-email: Paul McGuire <ptmcg.gm+pyparsing@gmail.com>
 Requires-Python: >=3.6.8
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy

+ 2 - 2
contrib/python/pyparsing/py3/pyparsing/__init__.py

@@ -120,8 +120,8 @@ class version_info(NamedTuple):
         return f"{__name__}.{type(self).__name__}({', '.join('{}={!r}'.format(*nv) for nv in zip(self._fields, self))})"
 
 
-__version_info__ = version_info(3, 1, 1, "final", 1)
-__version_time__ = "29 Jul 2023 22:27 UTC"
+__version_info__ = version_info(3, 1, 2, "final", 1)
+__version_time__ = "06 Mar 2024 07:08 UTC"
 __version__ = __version_info__.__version__
 __versionTime__ = __version_time__
 __author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"

+ 5 - 16
contrib/python/pyparsing/py3/pyparsing/actions.py

@@ -111,7 +111,6 @@ def with_attribute(*args, **attr_dict):
             <div type="graph">1,3 2,3 1,1</div>
             <div>this has no type</div>
             </div>
-
         '''
         div,div_end = make_html_tags("div")
 
@@ -199,19 +198,9 @@ def with_class(classname, namespace=""):
 
 # pre-PEP8 compatibility symbols
 # fmt: off
-@replaced_by_pep8(replace_with)
-def replaceWith(): ...
-
-@replaced_by_pep8(remove_quotes)
-def removeQuotes(): ...
-
-@replaced_by_pep8(with_attribute)
-def withAttribute(): ...
-
-@replaced_by_pep8(with_class)
-def withClass(): ...
-
-@replaced_by_pep8(match_only_at_col)
-def matchOnlyAtCol(): ...
-
+replaceWith = replaced_by_pep8("replaceWith", replace_with)
+removeQuotes = replaced_by_pep8("removeQuotes", remove_quotes)
+withAttribute = replaced_by_pep8("withAttribute", with_attribute)
+withClass = replaced_by_pep8("withClass", with_class)
+matchOnlyAtCol = replaced_by_pep8("matchOnlyAtCol", match_only_at_col)
 # fmt: on

+ 8 - 1
contrib/python/pyparsing/py3/pyparsing/common.py

@@ -206,7 +206,7 @@ class pyparsing_common:
     scientific notation and returns a float"""
 
     # streamlining this expression makes the docs nicer-looking
-    number = (sci_real | real | signed_integer).setName("number").streamline()
+    number = (sci_real | real | signed_integer).set_name("number").streamline()
     """any numeric expression, returns the corresponding Python type"""
 
     fnumber = (
@@ -216,6 +216,13 @@ class pyparsing_common:
     )
     """any int or real number, returned as float"""
 
+    ieee_float = (
+        Regex(r"(?i)[+-]?((\d+\.?\d*(e[+-]?\d+)?)|nan|inf(inity)?)")
+        .set_name("ieee_float")
+        .set_parse_action(convert_to_float)
+    )
+    """any floating-point literal (int, real number, infinity, or NaN), returned as float"""
+
     identifier = Word(identchars, identbodychars).set_name("identifier")
     """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')"""
 

File diff suppressed because it is too large
+ 264 - 317
contrib/python/pyparsing/py3/pyparsing/core.py


+ 1 - 1
contrib/python/pyparsing/py3/pyparsing/diagram/__init__.py

@@ -473,7 +473,7 @@ def _to_diagram_element(
     :param show_groups: bool flag indicating whether to show groups using bounding box
     """
     exprs = element.recurse()
-    name = name_hint or element.customName or element.__class__.__name__
+    name = name_hint or element.customName or type(element).__name__
 
     # Python's id() is used to provide a unique identifier for elements
     el_id = id(element)

+ 41 - 40
contrib/python/pyparsing/py3/pyparsing/exceptions.py

@@ -14,11 +14,13 @@ from .util import (
 from .unicode import pyparsing_unicode as ppu
 
 
-class ExceptionWordUnicode(ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic):
+class _ExceptionWordUnicodeSet(
+    ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic
+):
     pass
 
 
-_extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums)
+_extract_alphanums = _collapse_string_to_ranges(_ExceptionWordUnicodeSet.alphanums)
 _exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.")
 
 
@@ -86,41 +88,39 @@ class ParseBaseException(Exception):
             ret.append(" " * (exc.column - 1) + "^")
         ret.append(f"{type(exc).__name__}: {exc}")
 
-        if depth > 0:
-            callers = inspect.getinnerframes(exc.__traceback__, context=depth)
-            seen = set()
-            for i, ff in enumerate(callers[-depth:]):
-                frm = ff[0]
-
-                f_self = frm.f_locals.get("self", None)
-                if isinstance(f_self, ParserElement):
-                    if not frm.f_code.co_name.startswith(
-                        ("parseImpl", "_parseNoCache")
-                    ):
-                        continue
-                    if id(f_self) in seen:
-                        continue
-                    seen.add(id(f_self))
-
-                    self_type = type(f_self)
-                    ret.append(
-                        f"{self_type.__module__}.{self_type.__name__} - {f_self}"
-                    )
-
-                elif f_self is not None:
-                    self_type = type(f_self)
-                    ret.append(f"{self_type.__module__}.{self_type.__name__}")
+        if depth <= 0:
+            return "\n".join(ret)
 
-                else:
-                    code = frm.f_code
-                    if code.co_name in ("wrapper", "<module>"):
-                        continue
+        callers = inspect.getinnerframes(exc.__traceback__, context=depth)
+        seen = set()
+        for ff in callers[-depth:]:
+            frm = ff[0]
+
+            f_self = frm.f_locals.get("self", None)
+            if isinstance(f_self, ParserElement):
+                if not frm.f_code.co_name.startswith(("parseImpl", "_parseNoCache")):
+                    continue
+                if id(f_self) in seen:
+                    continue
+                seen.add(id(f_self))
+
+                self_type = type(f_self)
+                ret.append(f"{self_type.__module__}.{self_type.__name__} - {f_self}")
+
+            elif f_self is not None:
+                self_type = type(f_self)
+                ret.append(f"{self_type.__module__}.{self_type.__name__}")
+
+            else:
+                code = frm.f_code
+                if code.co_name in ("wrapper", "<module>"):
+                    continue
 
-                    ret.append(code.co_name)
+                ret.append(code.co_name)
 
-                depth -= 1
-                if not depth:
-                    break
+            depth -= 1
+            if not depth:
+                break
 
         return "\n".join(ret)
 
@@ -220,8 +220,10 @@ class ParseBaseException(Exception):
 
         Example::
 
+            # an expression to parse 3 integers
             expr = pp.Word(pp.nums) * 3
             try:
+                # a failing parse - the third integer is prefixed with "A"
                 expr.parse_string("123 456 A789")
             except pp.ParseException as pe:
                 print(pe.explain(depth=0))
@@ -244,8 +246,7 @@ class ParseBaseException(Exception):
         return self.explain_exception(self, depth)
 
     # fmt: off
-    @replaced_by_pep8(mark_input_line)
-    def markInputline(self): ...
+    markInputline = replaced_by_pep8("markInputline", mark_input_line)
     # fmt: on
 
 
@@ -255,16 +256,16 @@ class ParseException(ParseBaseException):
 
     Example::
 
+        integer = Word(nums).set_name("integer")
         try:
-            Word(nums).set_name("integer").parse_string("ABC")
+            integer.parse_string("ABC")
         except ParseException as pe:
             print(pe)
-            print("column: {}".format(pe.column))
+            print(f"column: {pe.column}")
 
     prints::
 
-       Expected integer (at char 0), (line:1, col:1)
-        column: 1
+       Expected integer (at char 0), (line:1, col:1) column: 1
 
     """
 

+ 32 - 54
contrib/python/pyparsing/py3/pyparsing/helpers.py

@@ -74,7 +74,7 @@ def counted_array(
         intExpr = intExpr.copy()
     intExpr.set_name("arrayLen")
     intExpr.add_parse_action(count_field_parse_action, call_during_try=True)
-    return (intExpr + array_expr).set_name("(len) " + str(expr) + "...")
+    return (intExpr + array_expr).set_name(f"(len) {expr}...")
 
 
 def match_previous_literal(expr: ParserElement) -> ParserElement:
@@ -95,15 +95,17 @@ def match_previous_literal(expr: ParserElement) -> ParserElement:
     rep = Forward()
 
     def copy_token_to_repeater(s, l, t):
-        if t:
-            if len(t) == 1:
-                rep << t[0]
-            else:
-                # flatten t tokens
-                tflat = _flatten(t.as_list())
-                rep << And(Literal(tt) for tt in tflat)
-        else:
+        if not t:
             rep << Empty()
+            return
+
+        if len(t) == 1:
+            rep << t[0]
+            return
+
+        # flatten t tokens
+        tflat = _flatten(t.as_list())
+        rep << And(Literal(tt) for tt in tflat)
 
     expr.add_parse_action(copy_token_to_repeater, callDuringTry=True)
     rep.set_name("(prev) " + str(expr))
@@ -230,7 +232,7 @@ def one_of(
                 if isequal(other, cur):
                     del symbols[i + j + 1]
                     break
-                elif masks(cur, other):
+                if masks(cur, other):
                     del symbols[i + j + 1]
                     symbols.insert(i, other)
                     break
@@ -534,7 +536,9 @@ def nested_expr(
         )
     else:
         ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer))
-    ret.set_name("nested %s%s expression" % (opener, closer))
+    ret.set_name(f"nested {opener}{closer} expression")
+    # don't override error message from content expressions
+    ret.errmsg = None
     return ret
 
 
@@ -580,7 +584,7 @@ def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">"))
         )
     closeTag = Combine(Literal("</") + tagStr + ">", adjacent=False)
 
-    openTag.set_name("<%s>" % resname)
+    openTag.set_name(f"<{resname}>")
     # add start<tagname> results name in parse action now that ungrouped names are not reported at two levels
     openTag.add_parse_action(
         lambda t: t.__setitem__(
@@ -589,7 +593,7 @@ def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">"))
     )
     closeTag = closeTag(
         "end" + "".join(resname.replace(":", " ").title().split())
-    ).set_name("</%s>" % resname)
+    ).set_name(f"</{resname}>")
     openTag.tag = resname
     closeTag.tag = resname
     openTag.tag_body = SkipTo(closeTag())
@@ -777,7 +781,7 @@ def infix_notation(
         rpar = Suppress(rpar)
 
     # if lpar and rpar are not suppressed, wrap in group
-    if not (isinstance(rpar, Suppress) and isinstance(rpar, Suppress)):
+    if not (isinstance(lpar, Suppress) and isinstance(rpar, Suppress)):
         lastExpr = base_expr | Group(lpar + ret + rpar)
     else:
         lastExpr = base_expr | (lpar + ret + rpar)
@@ -787,7 +791,7 @@ def infix_notation(
     pa: typing.Optional[ParseAction]
     opExpr1: ParserElement
     opExpr2: ParserElement
-    for i, operDef in enumerate(op_list):
+    for operDef in op_list:
         opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4]  # type: ignore[assignment]
         if isinstance(opExpr, str_type):
             opExpr = ParserElement._literalStringClass(opExpr)
@@ -1058,43 +1062,17 @@ dblSlashComment = dbl_slash_comment
 cppStyleComment = cpp_style_comment
 javaStyleComment = java_style_comment
 pythonStyleComment = python_style_comment
-
-@replaced_by_pep8(DelimitedList)
-def delimitedList(): ...
-
-@replaced_by_pep8(DelimitedList)
-def delimited_list(): ...
-
-@replaced_by_pep8(counted_array)
-def countedArray(): ...
-
-@replaced_by_pep8(match_previous_literal)
-def matchPreviousLiteral(): ...
-
-@replaced_by_pep8(match_previous_expr)
-def matchPreviousExpr(): ...
-
-@replaced_by_pep8(one_of)
-def oneOf(): ...
-
-@replaced_by_pep8(dict_of)
-def dictOf(): ...
-
-@replaced_by_pep8(original_text_for)
-def originalTextFor(): ...
-
-@replaced_by_pep8(nested_expr)
-def nestedExpr(): ...
-
-@replaced_by_pep8(make_html_tags)
-def makeHTMLTags(): ...
-
-@replaced_by_pep8(make_xml_tags)
-def makeXMLTags(): ...
-
-@replaced_by_pep8(replace_html_entity)
-def replaceHTMLEntity(): ...
-
-@replaced_by_pep8(infix_notation)
-def infixNotation(): ...
+delimitedList = replaced_by_pep8("delimitedList", DelimitedList)
+delimited_list = replaced_by_pep8("delimited_list", DelimitedList)
+countedArray = replaced_by_pep8("countedArray", counted_array)
+matchPreviousLiteral = replaced_by_pep8("matchPreviousLiteral", match_previous_literal)
+matchPreviousExpr = replaced_by_pep8("matchPreviousExpr", match_previous_expr)
+oneOf = replaced_by_pep8("oneOf", one_of)
+dictOf = replaced_by_pep8("dictOf", dict_of)
+originalTextFor = replaced_by_pep8("originalTextFor", original_text_for)
+nestedExpr = replaced_by_pep8("nestedExpr", nested_expr)
+makeHTMLTags = replaced_by_pep8("makeHTMLTags", make_html_tags)
+makeXMLTags = replaced_by_pep8("makeXMLTags", make_xml_tags)
+replaceHTMLEntity = replaced_by_pep8("replaceHTMLEntity", replace_html_entity)
+infixNotation = replaced_by_pep8("infixNotation", infix_notation)
 # fmt: on

+ 106 - 105
contrib/python/pyparsing/py3/pyparsing/results.py

@@ -173,42 +173,48 @@ class ParseResults:
     ):
         self._tokdict: Dict[str, _ParseResultsWithOffset]
         self._modal = modal
-        if name is not None and name != "":
-            if isinstance(name, int):
-                name = str(name)
-            if not modal:
-                self._all_names = {name}
-            self._name = name
-            if toklist not in self._null_values:
-                if isinstance(toklist, (str_type, type)):
-                    toklist = [toklist]
-                if asList:
-                    if isinstance(toklist, ParseResults):
-                        self[name] = _ParseResultsWithOffset(
-                            ParseResults(toklist._toklist), 0
-                        )
-                    else:
-                        self[name] = _ParseResultsWithOffset(
-                            ParseResults(toklist[0]), 0
-                        )
-                    self[name]._name = name
-                else:
-                    try:
-                        self[name] = toklist[0]
-                    except (KeyError, TypeError, IndexError):
-                        if toklist is not self:
-                            self[name] = toklist
-                        else:
-                            self._name = name
+
+        if name is None or name == "":
+            return
+
+        if isinstance(name, int):
+            name = str(name)
+
+        if not modal:
+            self._all_names = {name}
+
+        self._name = name
+
+        if toklist in self._null_values:
+            return
+
+        if isinstance(toklist, (str_type, type)):
+            toklist = [toklist]
+
+        if asList:
+            if isinstance(toklist, ParseResults):
+                self[name] = _ParseResultsWithOffset(ParseResults(toklist._toklist), 0)
+            else:
+                self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0)
+            self[name]._name = name
+            return
+
+        try:
+            self[name] = toklist[0]
+        except (KeyError, TypeError, IndexError):
+            if toklist is not self:
+                self[name] = toklist
+            else:
+                self._name = name
 
     def __getitem__(self, i):
         if isinstance(i, (int, slice)):
             return self._toklist[i]
-        else:
-            if i not in self._all_names:
-                return self._tokdict[i][-1][0]
-            else:
-                return ParseResults([v[0] for v in self._tokdict[i]])
+
+        if i not in self._all_names:
+            return self._tokdict[i][-1][0]
+
+        return ParseResults([v[0] for v in self._tokdict[i]])
 
     def __setitem__(self, k, v, isinstance=isinstance):
         if isinstance(v, _ParseResultsWithOffset):
@@ -226,27 +232,28 @@ class ParseResults:
             sub._parent = self
 
     def __delitem__(self, i):
-        if isinstance(i, (int, slice)):
-            mylen = len(self._toklist)
-            del self._toklist[i]
-
-            # convert int to slice
-            if isinstance(i, int):
-                if i < 0:
-                    i += mylen
-                i = slice(i, i + 1)
-            # get removed indices
-            removed = list(range(*i.indices(mylen)))
-            removed.reverse()
-            # fixup indices in token dictionary
-            for name, occurrences in self._tokdict.items():
-                for j in removed:
-                    for k, (value, position) in enumerate(occurrences):
-                        occurrences[k] = _ParseResultsWithOffset(
-                            value, position - (position > j)
-                        )
-        else:
+        if not isinstance(i, (int, slice)):
             del self._tokdict[i]
+            return
+
+        mylen = len(self._toklist)
+        del self._toklist[i]
+
+        # convert int to slice
+        if isinstance(i, int):
+            if i < 0:
+                i += mylen
+            i = slice(i, i + 1)
+        # get removed indices
+        removed = list(range(*i.indices(mylen)))
+        removed.reverse()
+        # fixup indices in token dictionary
+        for occurrences in self._tokdict.values():
+            for j in removed:
+                for k, (value, position) in enumerate(occurrences):
+                    occurrences[k] = _ParseResultsWithOffset(
+                        value, position - (position > j)
+                    )
 
     def __contains__(self, k) -> bool:
         return k in self._tokdict
@@ -376,7 +383,7 @@ class ParseResults:
         """
         self._toklist.insert(index, ins_string)
         # fixup indices in token dictionary
-        for name, occurrences in self._tokdict.items():
+        for occurrences in self._tokdict.values():
             for k, (value, position) in enumerate(occurrences):
                 occurrences[k] = _ParseResultsWithOffset(
                     value, position + (position > index)
@@ -652,58 +659,52 @@ class ParseResults:
         NL = "\n"
         out.append(indent + str(self.as_list()) if include_list else "")
 
-        if full:
-            if self.haskeys():
-                items = sorted((str(k), v) for k, v in self.items())
-                for k, v in items:
-                    if out:
-                        out.append(NL)
-                    out.append(f"{indent}{('  ' * _depth)}- {k}: ")
-                    if isinstance(v, ParseResults):
-                        if v:
-                            out.append(
-                                v.dump(
-                                    indent=indent,
-                                    full=full,
-                                    include_list=include_list,
-                                    _depth=_depth + 1,
-                                )
-                            )
-                        else:
-                            out.append(str(v))
-                    else:
-                        out.append(repr(v))
-            if any(isinstance(vv, ParseResults) for vv in self):
-                v = self
-                for i, vv in enumerate(v):
-                    if isinstance(vv, ParseResults):
-                        out.append(
-                            "\n{}{}[{}]:\n{}{}{}".format(
-                                indent,
-                                ("  " * (_depth)),
-                                i,
-                                indent,
-                                ("  " * (_depth + 1)),
-                                vv.dump(
-                                    indent=indent,
-                                    full=full,
-                                    include_list=include_list,
-                                    _depth=_depth + 1,
-                                ),
-                            )
-                        )
-                    else:
-                        out.append(
-                            "\n%s%s[%d]:\n%s%s%s"
-                            % (
-                                indent,
-                                ("  " * (_depth)),
-                                i,
-                                indent,
-                                ("  " * (_depth + 1)),
-                                str(vv),
-                            )
-                        )
+        if not full:
+            return "".join(out)
+
+        if self.haskeys():
+            items = sorted((str(k), v) for k, v in self.items())
+            for k, v in items:
+                if out:
+                    out.append(NL)
+                out.append(f"{indent}{('  ' * _depth)}- {k}: ")
+                if not isinstance(v, ParseResults):
+                    out.append(repr(v))
+                    continue
+
+                if not v:
+                    out.append(str(v))
+                    continue
+
+                out.append(
+                    v.dump(
+                        indent=indent,
+                        full=full,
+                        include_list=include_list,
+                        _depth=_depth + 1,
+                    )
+                )
+        if not any(isinstance(vv, ParseResults) for vv in self):
+            return "".join(out)
+
+        v = self
+        incr = "  "
+        nl = "\n"
+        for i, vv in enumerate(v):
+            if isinstance(vv, ParseResults):
+                vv_dump = vv.dump(
+                    indent=indent,
+                    full=full,
+                    include_list=include_list,
+                    _depth=_depth + 1,
+                )
+                out.append(
+                    f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv_dump}"
+                )
+            else:
+                out.append(
+                    f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv}"
+                )
 
         return "".join(out)
 

+ 66 - 52
contrib/python/pyparsing/py3/pyparsing/testing.py

@@ -1,8 +1,10 @@
 # testing.py
 
 from contextlib import contextmanager
+import re
 import typing
 
+
 from .core import (
     ParserElement,
     ParseException,
@@ -49,23 +51,23 @@ class pyparsing_test:
             self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS
             self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS
 
-            self._save_context[
-                "literal_string_class"
-            ] = ParserElement._literalStringClass
+            self._save_context["literal_string_class"] = (
+                ParserElement._literalStringClass
+            )
 
             self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace
 
             self._save_context["packrat_enabled"] = ParserElement._packratEnabled
             if ParserElement._packratEnabled:
-                self._save_context[
-                    "packrat_cache_size"
-                ] = ParserElement.packrat_cache.size
+                self._save_context["packrat_cache_size"] = (
+                    ParserElement.packrat_cache.size
+                )
             else:
                 self._save_context["packrat_cache_size"] = None
             self._save_context["packrat_parse"] = ParserElement._parse
-            self._save_context[
-                "recursion_enabled"
-            ] = ParserElement._left_recursion_enabled
+            self._save_context["recursion_enabled"] = (
+                ParserElement._left_recursion_enabled
+            )
 
             self._save_context["__diag__"] = {
                 name: getattr(__diag__, name) for name in __diag__._all_names
@@ -180,49 +182,52 @@ class pyparsing_test:
             """
             run_test_success, run_test_results = run_tests_report
 
-            if expected_parse_results is not None:
-                merged = [
-                    (*rpt, expected)
-                    for rpt, expected in zip(run_test_results, expected_parse_results)
-                ]
-                for test_string, result, expected in merged:
-                    # expected should be a tuple containing a list and/or a dict or an exception,
-                    # and optional failure message string
-                    # an empty tuple will skip any result validation
-                    fail_msg = next(
-                        (exp for exp in expected if isinstance(exp, str)), None
+            if expected_parse_results is None:
+                self.assertTrue(
+                    run_test_success, msg=msg if msg is not None else "failed runTests"
+                )
+                return
+
+            merged = [
+                (*rpt, expected)
+                for rpt, expected in zip(run_test_results, expected_parse_results)
+            ]
+            for test_string, result, expected in merged:
+                # expected should be a tuple containing a list and/or a dict or an exception,
+                # and optional failure message string
+                # an empty tuple will skip any result validation
+                fail_msg = next((exp for exp in expected if isinstance(exp, str)), None)
+                expected_exception = next(
+                    (
+                        exp
+                        for exp in expected
+                        if isinstance(exp, type) and issubclass(exp, Exception)
+                    ),
+                    None,
+                )
+                if expected_exception is not None:
+                    with self.assertRaises(
+                        expected_exception=expected_exception, msg=fail_msg or msg
+                    ):
+                        if isinstance(result, Exception):
+                            raise result
+                else:
+                    expected_list = next(
+                        (exp for exp in expected if isinstance(exp, list)), None
                     )
-                    expected_exception = next(
-                        (
-                            exp
-                            for exp in expected
-                            if isinstance(exp, type) and issubclass(exp, Exception)
-                        ),
-                        None,
+                    expected_dict = next(
+                        (exp for exp in expected if isinstance(exp, dict)), None
                     )
-                    if expected_exception is not None:
-                        with self.assertRaises(
-                            expected_exception=expected_exception, msg=fail_msg or msg
-                        ):
-                            if isinstance(result, Exception):
-                                raise result
-                    else:
-                        expected_list = next(
-                            (exp for exp in expected if isinstance(exp, list)), None
+                    if (expected_list, expected_dict) != (None, None):
+                        self.assertParseResultsEquals(
+                            result,
+                            expected_list=expected_list,
+                            expected_dict=expected_dict,
+                            msg=fail_msg or msg,
                         )
-                        expected_dict = next(
-                            (exp for exp in expected if isinstance(exp, dict)), None
-                        )
-                        if (expected_list, expected_dict) != (None, None):
-                            self.assertParseResultsEquals(
-                                result,
-                                expected_list=expected_list,
-                                expected_dict=expected_dict,
-                                msg=fail_msg or msg,
-                            )
-                        else:
-                            # warning here maybe?
-                            print(f"no validation for {test_string!r}")
+                    else:
+                        # warning here maybe?
+                        print(f"no validation for {test_string!r}")
 
             # do this last, in case some specific test results can be reported instead
             self.assertTrue(
@@ -230,9 +235,18 @@ class pyparsing_test:
             )
 
         @contextmanager
-        def assertRaisesParseException(self, exc_type=ParseException, msg=None):
-            with self.assertRaises(exc_type, msg=msg):
-                yield
+        def assertRaisesParseException(
+            self, exc_type=ParseException, expected_msg=None, msg=None
+        ):
+            if expected_msg is not None:
+                if isinstance(expected_msg, str):
+                    expected_msg = re.escape(expected_msg)
+                with self.assertRaisesRegex(exc_type, expected_msg, msg=msg) as ctx:
+                    yield ctx
+
+            else:
+                with self.assertRaises(exc_type, msg=msg) as ctx:
+                    yield ctx
 
     @staticmethod
     def with_line_numbers(

Some files were not shown because too many files changed in this diff