Imported Upstream version 3.0.4 upstream/3.0.4
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 18 Jul 2022 05:42:36 +0000 (14:42 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 18 Jul 2022 05:42:36 +0000 (14:42 +0900)
CHANGES
PKG-INFO
docs/whats_new_in_3_0_0.rst
pyparsing.egg-info/PKG-INFO
pyparsing/__init__.py
pyparsing/core.py
pyparsing/diagram/__init__.py
pyparsing/testing.py
tests/test_unit.py

diff --git a/CHANGES b/CHANGES
index 6127d25ec3352d257ed28900cc2bbff4b1deb6e4..1c1396e85550a2e0c7a83c3ef98e782263b7d486 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,35 @@
 Change Log
 ==========
 
+Version 3.0.4 -
+---------------
+- Fixed bug in which `Dict` classes did not correctly return tokens as nested
+  `ParseResults`, reported by and fix identified by Bu Sun Kim, many thanks!!!
+
+- Documented API-changing side-effect of converting `ParseResults` to use `__slots__`
+  to pre-define instance attributes. This means that code written like this (which
+  was allowed in pyparsing 2.4.7):
+
+    result = Word(alphas).parseString("abc")
+    result.xyz = 100
+
+  now raises this Python exception:
+
+    AttributeError: 'ParseResults' object has no attribute 'xyz'
+
+  To add new attribute values to ParseResults object in 3.0.0 and later, you must
+  assign them using indexed notation:
+
+    result["xyz"] = 100
+
+  You will still be able to access this new value as an attribute or as an
+  indexed item.
+
+- Fixed bug in railroad diagramming where the vertical limit would count all
+  expressions in a group, not just those that would create visible railroad
+  elements.
+
+
 Version 3.0.3 -
 ---------------
 - Fixed regex typo in `one_of` fix for `as_keyword=True`.
index 63c9c8a993d1e96cf693a9bd3798745886cf7807..46c89b1f84a71fbf3d397d9fcf510e759720218b 100644 (file)
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pyparsing
-Version: 3.0.3
+Version: 3.0.4
 Summary: Python parsing module
 Home-page: https://github.com/pyparsing/pyparsing/
 Author: Paul McGuire
index 3bf408db4d1bd741b1573aa3dbc130d569bd0b26..e5e40e40a6baf85ece2a18d91d4de9ba70118bf4 100644 (file)
@@ -8,7 +8,7 @@ What's New in Pyparsing 3.0.0
 
 :abstract: This document summarizes the changes made
     in the 3.0.0 release of pyparsing.
-    (Updated to reflect changes up to 3.0.2)
+    (Updated to reflect changes up to 3.0.4)
 
 .. sectnum::    :depth: 4
 
@@ -498,6 +498,25 @@ Other new features
 API Changes
 ===========
 
+- [Note added in pyparsing 3.0.4]
+  The `ParseResults` class now uses `__slots__` to pre-define instance attributes. This
+  means that code written like this (which was allowed in pyparsing 2.4.7)::
+
+    result = Word(alphas).parseString("abc")
+    result.xyz = 100
+
+  now raises this Python exception::
+
+    AttributeError: 'ParseResults' object has no attribute 'xyz'
+
+  To add new attribute values to ParseResults object in 3.0.0 and later, you must
+  assign them using indexed notation::
+
+    result["xyz"] = 100
+
+  You will still be able to access this new value as an attribute or as an
+  indexed item.
+
 - ``enable_diag()`` and ``disable_diag()`` methods to
   enable specific diagnostic values (instead of setting them
   to ``True`` or ``False``). ``enable_all_warnings()`` has
@@ -532,7 +551,7 @@ API Changes
 - Debug actions now take an added keyword argument ``cache_hit``.
   Now that debug actions are called for expressions matched in the
   packrat parsing cache, debug actions are now called with this extra
-  flag, set to True. For custom debug actions, it is necessary to add
+  flag, set to ``True``. For custom debug actions, it is necessary to add
   support for this new argument.
 
 - ``ZeroOrMore`` expressions that have results names will now
@@ -689,8 +708,9 @@ Other discontinued features
 Fixed Bugs
 ==========
 
-- [Reverted in 3.0.2]Fixed issue when ``LineStart``() expressions would match input text that was not
+- [Reverted in 3.0.2]Fixed issue when ``LineStart()`` expressions would match input text that was not
   necessarily at the beginning of a line.
+
   [The previous behavior was the correct behavior, since it represents the ``LineStart`` as its own
   matching expression. ``ParserElements`` that must start in column 1 can be wrapped in the new
   ``AtLineStart`` class.]
@@ -715,8 +735,7 @@ Fixed Bugs
 
 - Fixed bugs in ``Each`` when passed ``OneOrMore`` or ``ZeroOrMore`` expressions:
   . first expression match could be enclosed in an extra nesting level
-  . out-of-order expressions now handled correctly if mixed with required
-    expressions
+  . out-of-order expressions now handled correctly if mixed with required expressions
   . results names are maintained correctly for these expression
 
 - Fixed ``FutureWarning`` that sometimes is raised when ``'['`` passed as a
index 63c9c8a993d1e96cf693a9bd3798745886cf7807..46c89b1f84a71fbf3d397d9fcf510e759720218b 100644 (file)
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pyparsing
-Version: 3.0.3
+Version: 3.0.4
 Summary: Python parsing module
 Home-page: https://github.com/pyparsing/pyparsing/
 Author: Paul McGuire
index ab03e85dc7b33eed15cc5e48cdf404ec13ddf85d..c8d40b564ee4dec37565083092aedebd09ddfa2d 100644 (file)
@@ -96,7 +96,7 @@ classes inherit from. Use the docstrings for examples of how to:
 from collections import namedtuple
 
 version_info = namedtuple("version_info", "major minor micro release_level serial")
-__version_info__ = version_info(3, 0, 3, "final", 0)
+__version_info__ = version_info(3, 0, 4, "final", 0)
 __version__ = "{}.{}.{}".format(*__version_info__[:3]) + (
     "{}{}{}".format(
         "r" if __version_info__.release_level[0] == "c" else "",
@@ -105,7 +105,10 @@ __version__ = "{}.{}.{}".format(*__version_info__[:3]) + (
     ),
     "",
 )[__version_info__.release_level == "final"]
-__version_time__ = "27 October 2021 17:58 UTC"
+__version_time__ = "29 October 2021 05:11 UTC"
+version_info.__str__ = lambda *args: "pyparsing {} - {}".format(
+    __version__, __version_time__
+)
 __versionTime__ = __version_time__
 __author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
 
@@ -181,6 +184,7 @@ __all__ = [
     "ParseResults",
     "ParseSyntaxException",
     "ParserElement",
+    "PositionToken",
     "QuotedString",
     "RecursiveGrammarException",
     "Regex",
index ab178f32574d6b081e77085f4c5bb8c26a1e0d40..fe7dbb4a0b178eaa8a413424ecbb565dd152396c 100644 (file)
@@ -3326,14 +3326,14 @@ class White(Token):
         return loc, instring[start:loc]
 
 
-class _PositionToken(Token):
+class PositionToken(Token):
     def __init__(self):
         super().__init__()
         self.mayReturnEmpty = True
         self.mayIndexError = False
 
 
-class GoToColumn(_PositionToken):
+class GoToColumn(PositionToken):
     """Token to advance to a specific column of input text; useful for
     tabular report scraping.
     """
@@ -3364,7 +3364,7 @@ class GoToColumn(_PositionToken):
         return newloc, ret
 
 
-class LineStart(_PositionToken):
+class LineStart(PositionToken):
     r"""Matches if current position is at the beginning of a line within
     the parse string
 
@@ -3411,7 +3411,7 @@ class LineStart(_PositionToken):
         raise ParseException(instring, loc, self.errmsg, self)
 
 
-class LineEnd(_PositionToken):
+class LineEnd(PositionToken):
     """Matches if current position is at the end of a line within the
     parse string
     """
@@ -3434,7 +3434,7 @@ class LineEnd(_PositionToken):
             raise ParseException(instring, loc, self.errmsg, self)
 
 
-class StringStart(_PositionToken):
+class StringStart(PositionToken):
     """Matches if current position is at the beginning of the parse
     string
     """
@@ -3451,7 +3451,7 @@ class StringStart(_PositionToken):
         return loc, []
 
 
-class StringEnd(_PositionToken):
+class StringEnd(PositionToken):
     """
     Matches if current position is at the end of the parse string
     """
@@ -3471,7 +3471,7 @@ class StringEnd(_PositionToken):
             raise ParseException(instring, loc, self.errmsg, self)
 
 
-class WordStart(_PositionToken):
+class WordStart(PositionToken):
     """Matches if the current position is at the beginning of a
     :class:`Word`, and is not preceded by any character in a given
     set of ``word_chars`` (default= ``printables``). To emulate the
@@ -3497,7 +3497,7 @@ class WordStart(_PositionToken):
         return loc, []
 
 
-class WordEnd(_PositionToken):
+class WordEnd(PositionToken):
     """Matches if the current position is at the end of a :class:`Word`,
     and is not followed by any character in a given set of ``word_chars``
     (default= ``printables``). To emulate the ``\b`` behavior of
@@ -4014,7 +4014,9 @@ class MatchFirst(ParseExpression):
         for e in self.exprs:
             try:
                 return e._parse(
-                    instring, loc, doActions,
+                    instring,
+                    loc,
+                    doActions,
                 )
             except ParseFatalException as pfe:
                 pfe.__traceback__ = None
@@ -4342,7 +4344,9 @@ class IndentedBlock(ParseElementEnhance):
             self.errmsg = "expected indent at column greater than {}".format(ref_col)
             self.add_condition(lambda s, l, t: col(l, s) > ref_col)
 
-    def __init__(self, expr: ParserElement, *, recursive: bool = False, grouped: bool = True):
+    def __init__(
+        self, expr: ParserElement, *, recursive: bool = False, grouped: bool = True
+    ):
         super().__init__(expr, savelist=True)
         # if recursive:
         #     raise NotImplementedError("IndentedBlock with recursive is not implemented")
@@ -4523,7 +4527,7 @@ class PrecededBy(ParseElementEnhance):
         elif isinstance(expr, (Word, CharsNotIn)) and expr.maxLen != _MAX_INT:
             retreat = expr.maxLen
             self.exact = True
-        elif isinstance(expr, _PositionToken):
+        elif isinstance(expr, PositionToken):
             retreat = 0
             self.exact = True
         self.retreat = retreat
@@ -5409,7 +5413,10 @@ class Dict(TokenConverter):
                 else:
                     tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0], i)
 
-        return tokenlist if not self._asPythonDict else tokenlist.as_dict()
+        if self._asPythonDict:
+            return [tokenlist.as_dict()] if self.resultsName else tokenlist.as_dict()
+        else:
+            return [tokenlist] if self.resultsName else tokenlist
 
 
 class Suppress(TokenConverter):
index 2b6d11234613c7ff383de5f88eb2a9a1cd4d4d3c..4f7c41e448e0efc4c78539bd271fba0d2ca23ddc 100644 (file)
@@ -10,6 +10,7 @@ from typing import (
     Dict,
     Callable,
     Set,
+    Iterable,
 )
 from jinja2 import Template
 from io import StringIO
@@ -185,14 +186,16 @@ def to_railroad(
     return sorted(resolved, key=lambda diag: diag.index)
 
 
-def _should_vertical(specification: int, count: int) -> bool:
+def _should_vertical(
+    specification: int, exprs: Iterable[pyparsing.ParserElement]
+) -> bool:
     """
     Returns true if we should return a vertical list of elements
     """
     if specification is None:
         return False
     else:
-        return count >= specification
+        return len(_visible_exprs(exprs)) >= specification
 
 
 class ElementState:
@@ -386,6 +389,19 @@ def _apply_diagram_item_enhancements(fn):
     return _inner
 
 
+def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]):
+    non_diagramming_exprs = (
+        pyparsing.ParseElementEnhance,
+        pyparsing.PositionToken,
+        pyparsing.And._ErrorStop,
+    )
+    return [
+        e
+        for e in exprs
+        if not (e.customName or e.resultsName or isinstance(e, non_diagramming_exprs))
+    ]
+
+
 @_apply_diagram_item_enhancements
 def _to_diagram_element(
     element: pyparsing.ParserElement,
@@ -473,14 +489,14 @@ def _to_diagram_element(
             ret = EditablePartial.from_call(
                 railroad.OneOrMore, item="", repeat=str(len(exprs))
             )
-        elif _should_vertical(vertical, len(exprs)):
+        elif _should_vertical(vertical, exprs):
             ret = EditablePartial.from_call(railroad.Stack, items=[])
         else:
             ret = EditablePartial.from_call(railroad.Sequence, items=[])
     elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)):
         if not exprs:
             return None
-        if _should_vertical(vertical, len(exprs)):
+        if _should_vertical(vertical, exprs):
             ret = EditablePartial.from_call(railroad.Choice, 0, items=[])
         else:
             ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[])
index 75ef39f4b40a87abeec193e6b18eb7591b0a4182..991972f3fb2986d2d03951be87b07eeb6bce77bc 100644 (file)
@@ -303,17 +303,19 @@ class pyparsing_test:
             header0 = (
                 lead
                 + "".join(
-                    "{}{}".format(' ' * 99, (i + 1) % 100) for i in range(max(max_line_len // 100, 1))
+                    "{}{}".format(" " * 99, (i + 1) % 100)
+                    for i in range(max(max_line_len // 100, 1))
                 )
                 + "\n"
             )
         else:
             header0 = ""
         header1 = (
-            header0 +
-            lead
+            header0
+            lead
             + "".join(
-                "         {}".format((i + 1) % 10) for i in range(-(-max_line_len // 10))
+                "         {}".format((i + 1) % 10)
+                for i in range(-(-max_line_len // 10))
             )
             + "\n"
         )
index f90e42acb969d1fea277c3af646b9103fbf3582e..b60e90b74760ef073966fcd64665caa5b632e2d7 100644 (file)
@@ -2914,6 +2914,101 @@ class Test02_WithoutPackrat(ppt.TestParseResultsAsserts, TestCase):
             result1, expected, msg="issue with ParseResults.extend(ParseResults)"
         )
 
+    def testParseResultsWithNestedNames(self):
+        from pyparsing import (
+            Dict,
+            Literal,
+            Group,
+            Optional,
+            Regex,
+            QuotedString,
+            oneOf,
+            Or,
+            CaselessKeyword,
+            ZeroOrMore,
+        )
+
+        RELATION_SYMBOLS = "= > < >= <= <> =="
+
+        def _set_info(string, location, tokens):
+            for t in tokens:
+                try:
+                    t["_info_"] = (string, location)
+                except TypeError:
+                    pass
+            tokens["_info_"] = (string, location)
+
+        def keywords(name):
+            words = "any all within encloses adj".split()
+            return Or(map(CaselessKeyword, words))
+
+        charString1 = Group(Regex(r'[^()=<>"/\s]+'))("identifier")
+        charString1.addParseAction(_set_info)
+        charString2 = Group(QuotedString('"', "\\"))("quoted")
+        charString2.addParseAction(_set_info)
+
+        term = Group(charString1 | charString2)
+        modifier_key = charString1
+
+        # relations
+        comparitor_symbol = oneOf(RELATION_SYMBOLS)
+        named_comparitors = keywords("comparitors")
+        comparitor = Group(comparitor_symbol | named_comparitors)("comparitor")
+        comparitor.addParseAction(_set_info)
+
+        def modifier_list1(key):
+            modifier = Dict(
+                Literal("/")
+                + Group(modifier_key(key))("name")
+                + Optional(comparitor_symbol("symbol") + term("value"))
+            )("modifier")
+            modifier.addParseAction(_set_info)
+            return ZeroOrMore(modifier)("modifier_list")
+
+        def modifier_list2(key):
+            modifier = Dict(
+                Literal("/")
+                + Group(modifier_key(key))("name")
+                + Optional(comparitor_symbol("symbol") + term("value")),
+                asdict=True,
+            )("modifier")
+            modifier.addParseAction(_set_info)
+            return ZeroOrMore(modifier)("modifier_list")
+
+        def modifier_list3(key):
+            modifier = Group(  # this line is different from the others, must group to get results names
+                Dict(
+                    Literal("/")
+                    + Group(modifier_key(key))("name")
+                    + Optional(comparitor_symbol("symbol") + term("value"))
+                )
+            )
+            modifier.addParseAction(_set_info)
+            return ZeroOrMore(modifier)("modifier_list")
+
+        def modifier_list4(key):
+            modifier = Dict(
+                Literal("/")
+                + Group(modifier_key(key))("name")
+                + Optional(comparitor_symbol("symbol") + term("value")),
+                asdict=True,
+            )
+            modifier.addParseAction(_set_info)
+            return ZeroOrMore(modifier)("modifier_list")
+
+        for modifier_list_fn in (
+            modifier_list1,
+            modifier_list2,
+            modifier_list3,
+            modifier_list4,
+        ):
+            modifier_parser = modifier_list_fn("default")
+
+            result = modifier_parser.parseString("/respectaccents/ignoreaccents")
+            for r in result:
+                print(r)
+                print(r.get("_info_"))
+
     def testParseResultsFromDict(self):
         """test helper classmethod ParseResults.from_dict()"""