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`.
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
: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
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
- 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
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.]
- 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
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
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 "",
),
"",
)[__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>"
"ParseResults",
"ParseSyntaxException",
"ParserElement",
+ "PositionToken",
"QuotedString",
"RecursiveGrammarException",
"Regex",
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.
"""
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
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
"""
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
"""
return loc, []
-class StringEnd(_PositionToken):
+class StringEnd(PositionToken):
"""
Matches if current position is at the end of the parse string
"""
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
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
for e in self.exprs:
try:
return e._parse(
- instring, loc, doActions,
+ instring,
+ loc,
+ doActions,
)
except ParseFatalException as pfe:
pfe.__traceback__ = None
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")
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
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):
Dict,
Callable,
Set,
+ Iterable,
)
from jinja2 import Template
from io import StringIO
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:
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,
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=[])
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"
)
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()"""