Bump pyparsing from 3.0.7 to 3.0.9 (#1741)

* Bump pyparsing from 3.0.7 to 3.0.9

Bumps [pyparsing](https://github.com/pyparsing/pyparsing) from 3.0.7 to 3.0.9.
- [Release notes](https://github.com/pyparsing/pyparsing/releases)
- [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES)
- [Commits](https://github.com/pyparsing/pyparsing/compare/pyparsing_3.0.7...pyparsing_3.0.9)

---
updated-dependencies:
- dependency-name: pyparsing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update pyparsing==3.0.9

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2022-05-16 20:55:40 -07:00 committed by GitHub
parent 93b6370759
commit d17015de44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 322 additions and 232 deletions

View file

@ -1,6 +1,6 @@
# module pyparsing.py # module pyparsing.py
# #
# Copyright (c) 2003-2021 Paul T. McGuire # Copyright (c) 2003-2022 Paul T. McGuire
# #
# Permission is hereby granted, free of charge, to any person obtaining # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # a copy of this software and associated documentation files (the
@ -105,14 +105,17 @@ class version_info(NamedTuple):
@property @property
def __version__(self): def __version__(self):
return "{}.{}.{}".format(self.major, self.minor, self.micro) + ( return (
"{}{}{}".format( "{}.{}.{}".format(self.major, self.minor, self.micro)
"r" if self.releaselevel[0] == "c" else "", + (
self.releaselevel[0], "{}{}{}".format(
self.serial, "r" if self.releaselevel[0] == "c" else "",
), self.releaselevel[0],
"", self.serial,
)[self.releaselevel == "final"] ),
"",
)[self.releaselevel == "final"]
)
def __str__(self): def __str__(self):
return "{} {} / {}".format(__name__, self.__version__, __version_time__) return "{} {} / {}".format(__name__, self.__version__, __version_time__)
@ -125,8 +128,8 @@ class version_info(NamedTuple):
) )
__version_info__ = version_info(3, 0, 7, "final", 0) __version_info__ = version_info(3, 0, 9, "final", 0)
__version_time__ = "15 Jan 2022 04:10 UTC" __version_time__ = "05 May 2022 07:02 UTC"
__version__ = __version_info__.__version__ __version__ = __version_info__.__version__
__versionTime__ = __version_time__ __versionTime__ = __version_time__
__author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>" __author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"

View file

@ -55,7 +55,7 @@ def replace_with(repl_str):
na = one_of("N/A NA").set_parse_action(replace_with(math.nan)) na = one_of("N/A NA").set_parse_action(replace_with(math.nan))
term = na | num term = na | num
OneOrMore(term).parse_string("324 234 N/A 234") # -> [324, 234, nan, 234] term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
""" """
return lambda s, l, t: [repl_str] return lambda s, l, t: [repl_str]

View file

@ -2,9 +2,8 @@
# core.py # core.py
# #
import os import os
import typing
from typing import ( from typing import (
Optional as OptionalType,
Iterable as IterableType,
NamedTuple, NamedTuple,
Union, Union,
Callable, Callable,
@ -14,7 +13,6 @@ from typing import (
List, List,
TextIO, TextIO,
Set, Set,
Dict as DictType,
Sequence, Sequence,
) )
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
@ -23,7 +21,6 @@ import string
import copy import copy
import warnings import warnings
import re import re
import sre_constants
import sys import sys
from collections.abc import Iterable from collections.abc import Iterable
import traceback import traceback
@ -53,7 +50,7 @@ _MAX_INT = sys.maxsize
str_type: Tuple[type, ...] = (str, bytes) str_type: Tuple[type, ...] = (str, bytes)
# #
# Copyright (c) 2003-2021 Paul T. McGuire # Copyright (c) 2003-2022 Paul T. McGuire
# #
# Permission is hereby granted, free of charge, to any person obtaining # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # a copy of this software and associated documentation files (the
@ -76,6 +73,19 @@ str_type: Tuple[type, ...] = (str, bytes)
# #
if sys.version_info >= (3, 8):
from functools import cached_property
else:
class cached_property:
def __init__(self, func):
self._func = func
def __get__(self, instance, owner=None):
ret = instance.__dict__[self._func.__name__] = self._func(instance)
return ret
class __compat__(__config_flags): class __compat__(__config_flags):
""" """
A cross-version compatibility configuration for pyparsing features that will be A cross-version compatibility configuration for pyparsing features that will be
@ -180,7 +190,7 @@ del __config_flags
def _should_enable_warnings( def _should_enable_warnings(
cmd_line_warn_options: IterableType[str], warn_env_var: OptionalType[str] cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str]
) -> bool: ) -> bool:
enable = bool(warn_env_var) enable = bool(warn_env_var)
for warn_opt in cmd_line_warn_options: for warn_opt in cmd_line_warn_options:
@ -246,10 +256,10 @@ hexnums = nums + "ABCDEFabcdef"
alphanums = alphas + nums alphanums = alphas + nums
printables = "".join([c for c in string.printable if c not in string.whitespace]) printables = "".join([c for c in string.printable if c not in string.whitespace])
_trim_arity_call_line = None _trim_arity_call_line: traceback.StackSummary = None
def _trim_arity(func, maxargs=2): def _trim_arity(func, max_limit=3):
"""decorator to trim function calls to match the arity of the target""" """decorator to trim function calls to match the arity of the target"""
global _trim_arity_call_line global _trim_arity_call_line
@ -267,16 +277,12 @@ def _trim_arity(func, maxargs=2):
# synthesize what would be returned by traceback.extract_stack at the call to # synthesize what would be returned by traceback.extract_stack at the call to
# user's parse action 'func', so that we don't incur call penalty at parse time # user's parse action 'func', so that we don't incur call penalty at parse time
LINE_DIFF = 11 # fmt: off
LINE_DIFF = 7
# IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND
# THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
_trim_arity_call_line = ( _trim_arity_call_line = (_trim_arity_call_line or traceback.extract_stack(limit=2)[-1])
_trim_arity_call_line or traceback.extract_stack(limit=2)[-1] pa_call_line_synth = (_trim_arity_call_line[0], _trim_arity_call_line[1] + LINE_DIFF)
)
pa_call_line_synth = (
_trim_arity_call_line[0],
_trim_arity_call_line[1] + LINE_DIFF,
)
def wrapper(*args): def wrapper(*args):
nonlocal found_arity, limit nonlocal found_arity, limit
@ -297,16 +303,18 @@ def _trim_arity(func, maxargs=2):
del tb del tb
if trim_arity_type_error: if trim_arity_type_error:
if limit <= maxargs: if limit < max_limit:
limit += 1 limit += 1
continue continue
raise raise
# fmt: on
# copy func name to wrapper for sensible debug output # copy func name to wrapper for sensible debug output
# (can't use functools.wraps, since that messes with function signature) # (can't use functools.wraps, since that messes with function signature)
func_name = getattr(func, "__name__", getattr(func, "__class__").__name__) func_name = getattr(func, "__name__", getattr(func, "__class__").__name__)
wrapper.__name__ = func_name wrapper.__name__ = func_name
wrapper.__doc__ = func.__doc__
return wrapper return wrapper
@ -394,7 +402,7 @@ class ParserElement(ABC):
DEFAULT_WHITE_CHARS: str = " \n\t\r" DEFAULT_WHITE_CHARS: str = " \n\t\r"
verbose_stacktrace: bool = False verbose_stacktrace: bool = False
_literalStringClass: OptionalType[type] = None _literalStringClass: typing.Optional[type] = None
@staticmethod @staticmethod
def set_default_whitespace_chars(chars: str) -> None: def set_default_whitespace_chars(chars: str) -> None:
@ -404,11 +412,11 @@ class ParserElement(ABC):
Example:: Example::
# default whitespace chars are space, <TAB> and newline # default whitespace chars are space, <TAB> and newline
OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
# change to just treat newline as significant # change to just treat newline as significant
ParserElement.set_default_whitespace_chars(" \t") ParserElement.set_default_whitespace_chars(" \t")
OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl") # -> ['abc', 'def'] Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def']
""" """
ParserElement.DEFAULT_WHITE_CHARS = chars ParserElement.DEFAULT_WHITE_CHARS = chars
@ -440,13 +448,13 @@ class ParserElement(ABC):
ParserElement._literalStringClass = cls ParserElement._literalStringClass = cls
class DebugActions(NamedTuple): class DebugActions(NamedTuple):
debug_try: OptionalType[DebugStartAction] debug_try: typing.Optional[DebugStartAction]
debug_match: OptionalType[DebugSuccessAction] debug_match: typing.Optional[DebugSuccessAction]
debug_fail: OptionalType[DebugExceptionAction] debug_fail: typing.Optional[DebugExceptionAction]
def __init__(self, savelist: bool = False): def __init__(self, savelist: bool = False):
self.parseAction: List[ParseAction] = list() self.parseAction: List[ParseAction] = list()
self.failAction: OptionalType[ParseFailAction] = None self.failAction: typing.Optional[ParseFailAction] = None
self.customName = None self.customName = None
self._defaultName = None self._defaultName = None
self.resultsName = None self.resultsName = None
@ -467,7 +475,6 @@ class ParserElement(ABC):
self.modalResults = True self.modalResults = True
# custom debug actions # custom debug actions
self.debugActions = self.DebugActions(None, None, None) self.debugActions = self.DebugActions(None, None, None)
self.re = None
# avoid redundant calls to preParse # avoid redundant calls to preParse
self.callPreparse = True self.callPreparse = True
self.callDuringTry = False self.callDuringTry = False
@ -501,7 +508,7 @@ class ParserElement(ABC):
integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K") integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K")
integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
print(OneOrMore(integerK | integerM | integer).parse_string("5K 100 640K 256M")) print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M"))
prints:: prints::
@ -886,7 +893,7 @@ class ParserElement(ABC):
# cache for left-recursion in Forward references # cache for left-recursion in Forward references
recursion_lock = RLock() recursion_lock = RLock()
recursion_memos: DictType[ recursion_memos: typing.Dict[
Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]] Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]]
] = {} ] = {}
@ -976,7 +983,7 @@ class ParserElement(ABC):
@staticmethod @staticmethod
def enable_left_recursion( def enable_left_recursion(
cache_size_limit: OptionalType[int] = None, *, force=False cache_size_limit: typing.Optional[int] = None, *, force=False
) -> None: ) -> None:
""" """
Enables "bounded recursion" parsing, which allows for both direct and indirect Enables "bounded recursion" parsing, which allows for both direct and indirect
@ -1342,7 +1349,7 @@ class ParserElement(ABC):
last = e last = e
yield instring[last:] yield instring[last:]
def __add__(self, other): def __add__(self, other) -> "ParserElement":
""" """
Implementation of ``+`` operator - returns :class:`And`. Adding strings to a :class:`ParserElement` Implementation of ``+`` operator - returns :class:`And`. Adding strings to a :class:`ParserElement`
converts them to :class:`Literal`s by default. converts them to :class:`Literal`s by default.
@ -1382,7 +1389,7 @@ class ParserElement(ABC):
) )
return And([self, other]) return And([self, other])
def __radd__(self, other): def __radd__(self, other) -> "ParserElement":
""" """
Implementation of ``+`` operator when left operand is not a :class:`ParserElement` Implementation of ``+`` operator when left operand is not a :class:`ParserElement`
""" """
@ -1399,7 +1406,7 @@ class ParserElement(ABC):
) )
return other + self return other + self
def __sub__(self, other): def __sub__(self, other) -> "ParserElement":
""" """
Implementation of ``-`` operator, returns :class:`And` with error stop Implementation of ``-`` operator, returns :class:`And` with error stop
""" """
@ -1413,7 +1420,7 @@ class ParserElement(ABC):
) )
return self + And._ErrorStop() + other return self + And._ErrorStop() + other
def __rsub__(self, other): def __rsub__(self, other) -> "ParserElement":
""" """
Implementation of ``-`` operator when left operand is not a :class:`ParserElement` Implementation of ``-`` operator when left operand is not a :class:`ParserElement`
""" """
@ -1427,7 +1434,7 @@ class ParserElement(ABC):
) )
return other - self return other - self
def __mul__(self, other): def __mul__(self, other) -> "ParserElement":
""" """
Implementation of ``*`` operator, allows use of ``expr * 3`` in place of Implementation of ``*`` operator, allows use of ``expr * 3`` in place of
``expr + expr + expr``. Expressions may also be multiplied by a 2-integer ``expr + expr + expr``. Expressions may also be multiplied by a 2-integer
@ -1513,10 +1520,10 @@ class ParserElement(ABC):
ret = And([self] * minElements) ret = And([self] * minElements)
return ret return ret
def __rmul__(self, other): def __rmul__(self, other) -> "ParserElement":
return self.__mul__(other) return self.__mul__(other)
def __or__(self, other): def __or__(self, other) -> "ParserElement":
""" """
Implementation of ``|`` operator - returns :class:`MatchFirst` Implementation of ``|`` operator - returns :class:`MatchFirst`
""" """
@ -1533,7 +1540,7 @@ class ParserElement(ABC):
) )
return MatchFirst([self, other]) return MatchFirst([self, other])
def __ror__(self, other): def __ror__(self, other) -> "ParserElement":
""" """
Implementation of ``|`` operator when left operand is not a :class:`ParserElement` Implementation of ``|`` operator when left operand is not a :class:`ParserElement`
""" """
@ -1547,7 +1554,7 @@ class ParserElement(ABC):
) )
return other | self return other | self
def __xor__(self, other): def __xor__(self, other) -> "ParserElement":
""" """
Implementation of ``^`` operator - returns :class:`Or` Implementation of ``^`` operator - returns :class:`Or`
""" """
@ -1561,7 +1568,7 @@ class ParserElement(ABC):
) )
return Or([self, other]) return Or([self, other])
def __rxor__(self, other): def __rxor__(self, other) -> "ParserElement":
""" """
Implementation of ``^`` operator when left operand is not a :class:`ParserElement` Implementation of ``^`` operator when left operand is not a :class:`ParserElement`
""" """
@ -1575,7 +1582,7 @@ class ParserElement(ABC):
) )
return other ^ self return other ^ self
def __and__(self, other): def __and__(self, other) -> "ParserElement":
""" """
Implementation of ``&`` operator - returns :class:`Each` Implementation of ``&`` operator - returns :class:`Each`
""" """
@ -1589,7 +1596,7 @@ class ParserElement(ABC):
) )
return Each([self, other]) return Each([self, other])
def __rand__(self, other): def __rand__(self, other) -> "ParserElement":
""" """
Implementation of ``&`` operator when left operand is not a :class:`ParserElement` Implementation of ``&`` operator when left operand is not a :class:`ParserElement`
""" """
@ -1603,7 +1610,7 @@ class ParserElement(ABC):
) )
return other & self return other & self
def __invert__(self): def __invert__(self) -> "ParserElement":
""" """
Implementation of ``~`` operator - returns :class:`NotAny` Implementation of ``~`` operator - returns :class:`NotAny`
""" """
@ -1653,7 +1660,7 @@ class ParserElement(ABC):
ret = self * tuple(key[:2]) ret = self * tuple(key[:2])
return ret return ret
def __call__(self, name: str = None): def __call__(self, name: str = None) -> "ParserElement":
""" """
Shortcut for :class:`set_results_name`, with ``list_all_matches=False``. Shortcut for :class:`set_results_name`, with ``list_all_matches=False``.
@ -1729,7 +1736,7 @@ class ParserElement(ABC):
Example:: Example::
patt = OneOrMore(Word(alphas)) patt = Word(alphas)[1, ...]
patt.parse_string('ablaj /* comment */ lskjd') patt.parse_string('ablaj /* comment */ lskjd')
# -> ['ablaj'] # -> ['ablaj']
@ -1789,7 +1796,7 @@ class ParserElement(ABC):
# turn on debugging for wd # turn on debugging for wd
wd.set_debug() wd.set_debug()
OneOrMore(term).parse_string("abc 123 xyz 890") term[1, ...].parse_string("abc 123 xyz 890")
prints:: prints::
@ -1944,12 +1951,12 @@ class ParserElement(ABC):
self, self,
tests: Union[str, List[str]], tests: Union[str, List[str]],
parse_all: bool = True, parse_all: bool = True,
comment: OptionalType[Union["ParserElement", str]] = "#", comment: typing.Optional[Union["ParserElement", str]] = "#",
full_dump: bool = True, full_dump: bool = True,
print_results: bool = True, print_results: bool = True,
failure_tests: bool = False, failure_tests: bool = False,
post_parse: Callable[[str, ParseResults], str] = None, post_parse: Callable[[str, ParseResults], str] = None,
file: OptionalType[TextIO] = None, file: typing.Optional[TextIO] = None,
with_line_numbers: bool = False, with_line_numbers: bool = False,
*, *,
parseAll: bool = True, parseAll: bool = True,
@ -2140,6 +2147,7 @@ class ParserElement(ABC):
output_html: Union[TextIO, Path, str], output_html: Union[TextIO, Path, str],
vertical: int = 3, vertical: int = 3,
show_results_names: bool = False, show_results_names: bool = False,
show_groups: bool = False,
**kwargs, **kwargs,
) -> None: ) -> None:
""" """
@ -2152,7 +2160,7 @@ class ParserElement(ABC):
instead of horizontally (default=3) instead of horizontally (default=3)
- show_results_names - bool flag whether diagram should show annotations for - show_results_names - bool flag whether diagram should show annotations for
defined results names defined results names
- show_groups - bool flag whether groups should be highlighted with an unlabeled surrounding box
Additional diagram-formatting keyword arguments can also be included; Additional diagram-formatting keyword arguments can also be included;
see railroad.Diagram class. see railroad.Diagram class.
""" """
@ -2170,6 +2178,7 @@ class ParserElement(ABC):
self, self,
vertical=vertical, vertical=vertical,
show_results_names=show_results_names, show_results_names=show_results_names,
show_groups=show_groups,
diagram_kwargs=kwargs, diagram_kwargs=kwargs,
) )
if isinstance(output_html, (str, Path)): if isinstance(output_html, (str, Path)):
@ -2219,7 +2228,7 @@ class _PendingSkip(ParserElement):
def _generateDefaultName(self): def _generateDefaultName(self):
return str(self.anchor + Empty()).replace("Empty", "...") return str(self.anchor + Empty()).replace("Empty", "...")
def __add__(self, other): def __add__(self, other) -> "ParserElement":
skipper = SkipTo(other).set_name("...")("_skipped*") skipper = SkipTo(other).set_name("...")("_skipped*")
if self.must_skip: if self.must_skip:
@ -2374,11 +2383,11 @@ class Keyword(Token):
def __init__( def __init__(
self, self,
match_string: str = "", match_string: str = "",
ident_chars: OptionalType[str] = None, ident_chars: typing.Optional[str] = None,
caseless: bool = False, caseless: bool = False,
*, *,
matchString: str = "", matchString: str = "",
identChars: OptionalType[str] = None, identChars: typing.Optional[str] = None,
): ):
super().__init__() super().__init__()
identChars = identChars or ident_chars identChars = identChars or ident_chars
@ -2468,7 +2477,7 @@ class CaselessLiteral(Literal):
Example:: Example::
OneOrMore(CaselessLiteral("CMD")).parse_string("cmd CMD Cmd10") CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10")
# -> ['CMD', 'CMD', 'CMD'] # -> ['CMD', 'CMD', 'CMD']
(Contrast with example for :class:`CaselessKeyword`.) (Contrast with example for :class:`CaselessKeyword`.)
@ -2493,7 +2502,7 @@ class CaselessKeyword(Keyword):
Example:: Example::
OneOrMore(CaselessKeyword("CMD")).parse_string("cmd CMD Cmd10") CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10")
# -> ['CMD', 'CMD'] # -> ['CMD', 'CMD']
(Contrast with example for :class:`CaselessLiteral`.) (Contrast with example for :class:`CaselessLiteral`.)
@ -2502,10 +2511,10 @@ class CaselessKeyword(Keyword):
def __init__( def __init__(
self, self,
match_string: str = "", match_string: str = "",
ident_chars: OptionalType[str] = None, ident_chars: typing.Optional[str] = None,
*, *,
matchString: str = "", matchString: str = "",
identChars: OptionalType[str] = None, identChars: typing.Optional[str] = None,
): ):
identChars = identChars or ident_chars identChars = identChars or ident_chars
match_string = matchString or match_string match_string = matchString or match_string
@ -2669,17 +2678,17 @@ class Word(Token):
def __init__( def __init__(
self, self,
init_chars: str = "", init_chars: str = "",
body_chars: OptionalType[str] = None, body_chars: typing.Optional[str] = None,
min: int = 1, min: int = 1,
max: int = 0, max: int = 0,
exact: int = 0, exact: int = 0,
as_keyword: bool = False, as_keyword: bool = False,
exclude_chars: OptionalType[str] = None, exclude_chars: typing.Optional[str] = None,
*, *,
initChars: OptionalType[str] = None, initChars: typing.Optional[str] = None,
bodyChars: OptionalType[str] = None, bodyChars: typing.Optional[str] = None,
asKeyword: bool = False, asKeyword: bool = False,
excludeChars: OptionalType[str] = None, excludeChars: typing.Optional[str] = None,
): ):
initChars = initChars or init_chars initChars = initChars or init_chars
bodyChars = bodyChars or body_chars bodyChars = bodyChars or body_chars
@ -2773,7 +2782,7 @@ class Word(Token):
try: try:
self.re = re.compile(self.reString) self.re = re.compile(self.reString)
except sre_constants.error: except re.error:
self.re = None self.re = None
else: else:
self.re_match = self.re.match self.re_match = self.re.match
@ -2861,10 +2870,10 @@ class Char(_WordRegex):
self, self,
charset: str, charset: str,
as_keyword: bool = False, as_keyword: bool = False,
exclude_chars: OptionalType[str] = None, exclude_chars: typing.Optional[str] = None,
*, *,
asKeyword: bool = False, asKeyword: bool = False,
excludeChars: OptionalType[str] = None, excludeChars: typing.Optional[str] = None,
): ):
asKeyword = asKeyword or as_keyword asKeyword = asKeyword or as_keyword
excludeChars = excludeChars or exclude_chars excludeChars = excludeChars or exclude_chars
@ -2926,19 +2935,12 @@ class Regex(Token):
if not pattern: if not pattern:
raise ValueError("null string passed to Regex; use Empty() instead") raise ValueError("null string passed to Regex; use Empty() instead")
self.pattern = pattern self._re = None
self.reString = self.pattern = pattern
self.flags = flags self.flags = flags
try:
self.re = re.compile(self.pattern, self.flags)
self.reString = self.pattern
except sre_constants.error:
raise ValueError(
"invalid pattern ({!r}) passed to Regex".format(pattern)
)
elif hasattr(pattern, "pattern") and hasattr(pattern, "match"): elif hasattr(pattern, "pattern") and hasattr(pattern, "match"):
self.re = pattern self._re = pattern
self.pattern = self.reString = pattern.pattern self.pattern = self.reString = pattern.pattern
self.flags = flags self.flags = flags
@ -2947,11 +2949,8 @@ class Regex(Token):
"Regex may only be constructed with a string or a compiled RE object" "Regex may only be constructed with a string or a compiled RE object"
) )
self.re_match = self.re.match
self.errmsg = "Expected " + self.name self.errmsg = "Expected " + self.name
self.mayIndexError = False self.mayIndexError = False
self.mayReturnEmpty = self.re_match("") is not None
self.asGroupList = asGroupList self.asGroupList = asGroupList
self.asMatch = asMatch self.asMatch = asMatch
if self.asGroupList: if self.asGroupList:
@ -2959,6 +2958,26 @@ class Regex(Token):
if self.asMatch: if self.asMatch:
self.parseImpl = self.parseImplAsMatch self.parseImpl = self.parseImplAsMatch
@cached_property
def re(self):
if self._re:
return self._re
else:
try:
return re.compile(self.pattern, self.flags)
except re.error:
raise ValueError(
"invalid pattern ({!r}) passed to Regex".format(self.pattern)
)
@cached_property
def re_match(self):
return self.re.match
@cached_property
def mayReturnEmpty(self):
return self.re_match("") is not None
def _generateDefaultName(self): def _generateDefaultName(self):
return "Re:({})".format(repr(self.pattern).replace("\\\\", "\\")) return "Re:({})".format(repr(self.pattern).replace("\\\\", "\\"))
@ -3067,18 +3086,18 @@ class QuotedString(Token):
def __init__( def __init__(
self, self,
quote_char: str = "", quote_char: str = "",
esc_char: OptionalType[str] = None, esc_char: typing.Optional[str] = None,
esc_quote: OptionalType[str] = None, esc_quote: typing.Optional[str] = None,
multiline: bool = False, multiline: bool = False,
unquote_results: bool = True, unquote_results: bool = True,
end_quote_char: OptionalType[str] = None, end_quote_char: typing.Optional[str] = None,
convert_whitespace_escapes: bool = True, convert_whitespace_escapes: bool = True,
*, *,
quoteChar: str = "", quoteChar: str = "",
escChar: OptionalType[str] = None, escChar: typing.Optional[str] = None,
escQuote: OptionalType[str] = None, escQuote: typing.Optional[str] = None,
unquoteResults: bool = True, unquoteResults: bool = True,
endQuoteChar: OptionalType[str] = None, endQuoteChar: typing.Optional[str] = None,
convertWhitespaceEscapes: bool = True, convertWhitespaceEscapes: bool = True,
): ):
super().__init__() super().__init__()
@ -3168,7 +3187,7 @@ class QuotedString(Token):
self.re = re.compile(self.pattern, self.flags) self.re = re.compile(self.pattern, self.flags)
self.reString = self.pattern self.reString = self.pattern
self.re_match = self.re.match self.re_match = self.re.match
except sre_constants.error: except re.error:
raise ValueError( raise ValueError(
"invalid pattern {!r} passed to Regex".format(self.pattern) "invalid pattern {!r} passed to Regex".format(self.pattern)
) )
@ -3579,7 +3598,7 @@ class ParseExpression(ParserElement):
post-processing parsed tokens. post-processing parsed tokens.
""" """
def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False): def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(savelist) super().__init__(savelist)
self.exprs: List[ParserElement] self.exprs: List[ParserElement]
if isinstance(exprs, _generatorType): if isinstance(exprs, _generatorType):
@ -3746,7 +3765,7 @@ class And(ParseExpression):
Example:: Example::
integer = Word(nums) integer = Word(nums)
name_expr = OneOrMore(Word(alphas)) name_expr = Word(alphas)[1, ...]
expr = And([integer("id"), name_expr("name"), integer("age")]) expr = And([integer("id"), name_expr("name"), integer("age")])
# more easily written as: # more easily written as:
@ -3761,7 +3780,9 @@ class And(ParseExpression):
def _generateDefaultName(self): def _generateDefaultName(self):
return "-" return "-"
def __init__(self, exprs_arg: IterableType[ParserElement], savelist: bool = True): def __init__(
self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True
):
exprs: List[ParserElement] = list(exprs_arg) exprs: List[ParserElement] = list(exprs_arg)
if exprs and Ellipsis in exprs: if exprs and Ellipsis in exprs:
tmp = [] tmp = []
@ -3826,7 +3847,9 @@ class And(ParseExpression):
seen.add(id(cur)) seen.add(id(cur))
if isinstance(cur, IndentedBlock): if isinstance(cur, IndentedBlock):
prev.add_parse_action( prev.add_parse_action(
lambda s, l, t, cur_=cur: setattr(cur_, "parent_anchor", col(l, s)) lambda s, l, t, cur_=cur: setattr(
cur_, "parent_anchor", col(l, s)
)
) )
break break
subs = cur.recurse() subs = cur.recurse()
@ -3903,7 +3926,7 @@ class Or(ParseExpression):
[['123'], ['3.1416'], ['789']] [['123'], ['3.1416'], ['789']]
""" """
def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False): def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist) super().__init__(exprs, savelist)
if self.exprs: if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
@ -4058,7 +4081,7 @@ class MatchFirst(ParseExpression):
print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']]
""" """
def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False): def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist) super().__init__(exprs, savelist)
if self.exprs: if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
@ -4209,7 +4232,7 @@ class Each(ParseExpression):
- size: 20 - size: 20
""" """
def __init__(self, exprs: IterableType[ParserElement], savelist: bool = True): def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True):
super().__init__(exprs, savelist) super().__init__(exprs, savelist)
if self.exprs: if self.exprs:
self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
@ -4545,7 +4568,7 @@ class FollowedBy(ParseElementEnhance):
label = data_word + FollowedBy(':') label = data_word + FollowedBy(':')
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
OneOrMore(attr_expr).parse_string("shape: SQUARE color: BLACK posn: upper left").pprint() attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
prints:: prints::
@ -4596,7 +4619,7 @@ class PrecededBy(ParseElementEnhance):
""" """
def __init__( def __init__(
self, expr: Union[ParserElement, str], retreat: OptionalType[int] = None self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None
): ):
super().__init__(expr) super().__init__(expr)
self.expr = self.expr().leave_whitespace() self.expr = self.expr().leave_whitespace()
@ -4707,7 +4730,7 @@ class NotAny(ParseElementEnhance):
# very crude boolean expression - to support parenthesis groups and # very crude boolean expression - to support parenthesis groups and
# operation hierarchy, use infix_notation # operation hierarchy, use infix_notation
boolean_expr = boolean_term + ZeroOrMore((AND | OR) + boolean_term) boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...]
# integers that are followed by "." are actually floats # integers that are followed by "." are actually floats
integer = Word(nums) + ~Char(".") integer = Word(nums) + ~Char(".")
@ -4735,9 +4758,9 @@ class _MultipleMatch(ParseElementEnhance):
def __init__( def __init__(
self, self,
expr: ParserElement, expr: ParserElement,
stop_on: OptionalType[Union[ParserElement, str]] = None, stop_on: typing.Optional[Union[ParserElement, str]] = None,
*, *,
stopOn: OptionalType[Union[ParserElement, str]] = None, stopOn: typing.Optional[Union[ParserElement, str]] = None,
): ):
super().__init__(expr) super().__init__(expr)
stopOn = stopOn or stop_on stopOn = stopOn or stop_on
@ -4826,7 +4849,7 @@ class OneOrMore(_MultipleMatch):
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join)) attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join))
text = "shape: SQUARE posn: upper left color: BLACK" text = "shape: SQUARE posn: upper left color: BLACK"
OneOrMore(attr_expr).parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] attr_expr[1, ...].parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
# use stop_on attribute for OneOrMore to avoid reading label string as part of the data # use stop_on attribute for OneOrMore to avoid reading label string as part of the data
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
@ -4856,9 +4879,9 @@ class ZeroOrMore(_MultipleMatch):
def __init__( def __init__(
self, self,
expr: ParserElement, expr: ParserElement,
stop_on: OptionalType[Union[ParserElement, str]] = None, stop_on: typing.Optional[Union[ParserElement, str]] = None,
*, *,
stopOn: OptionalType[Union[ParserElement, str]] = None, stopOn: typing.Optional[Union[ParserElement, str]] = None,
): ):
super().__init__(expr, stopOn=stopOn or stop_on) super().__init__(expr, stopOn=stopOn or stop_on)
self.mayReturnEmpty = True self.mayReturnEmpty = True
@ -5002,20 +5025,20 @@ class SkipTo(ParseElementEnhance):
prints:: prints::
['101', 'Critical', 'Intermittent system crash', '6'] ['101', 'Critical', 'Intermittent system crash', '6']
- days_open: 6 - days_open: '6'
- desc: Intermittent system crash - desc: 'Intermittent system crash'
- issue_num: 101 - issue_num: '101'
- sev: Critical - sev: 'Critical'
['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14'] ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
- days_open: 14 - days_open: '14'
- desc: Spelling error on Login ('log|n') - desc: "Spelling error on Login ('log|n')"
- issue_num: 94 - issue_num: '94'
- sev: Cosmetic - sev: 'Cosmetic'
['79', 'Minor', 'System slow when running too many reports', '47'] ['79', 'Minor', 'System slow when running too many reports', '47']
- days_open: 47 - days_open: '47'
- desc: System slow when running too many reports - desc: 'System slow when running too many reports'
- issue_num: 79 - issue_num: '79'
- sev: Minor - sev: 'Minor'
""" """
def __init__( def __init__(
@ -5023,7 +5046,7 @@ class SkipTo(ParseElementEnhance):
other: Union[ParserElement, str], other: Union[ParserElement, str],
include: bool = False, include: bool = False,
ignore: bool = None, ignore: bool = None,
fail_on: OptionalType[Union[ParserElement, str]] = None, fail_on: typing.Optional[Union[ParserElement, str]] = None,
*, *,
failOn: Union[ParserElement, str] = None, failOn: Union[ParserElement, str] = None,
): ):
@ -5120,7 +5143,7 @@ class Forward(ParseElementEnhance):
parser created using ``Forward``. parser created using ``Forward``.
""" """
def __init__(self, other: OptionalType[Union[ParserElement, str]] = None): def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None):
self.caller_frame = traceback.extract_stack(limit=2)[0] self.caller_frame = traceback.extract_stack(limit=2)[0]
super().__init__(other, savelist=False) super().__init__(other, savelist=False)
self.lshift_line = None self.lshift_line = None
@ -5372,7 +5395,7 @@ class Combine(TokenConverter):
join_string: str = "", join_string: str = "",
adjacent: bool = True, adjacent: bool = True,
*, *,
joinString: OptionalType[str] = None, joinString: typing.Optional[str] = None,
): ):
super().__init__(expr) super().__init__(expr)
joinString = joinString if joinString is not None else join_string joinString = joinString if joinString is not None else join_string
@ -5459,10 +5482,10 @@ class Dict(TokenConverter):
attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
# print attributes as plain groups # print attributes as plain groups
print(OneOrMore(attr_expr).parse_string(text).dump()) print(attr_expr[1, ...].parse_string(text).dump())
# instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names
result = Dict(OneOrMore(Group(attr_expr))).parse_string(text) result = Dict(Group(attr_expr)[1, ...]).parse_string(text)
print(result.dump()) print(result.dump())
# access named fields as dict entries, or output as dict # access named fields as dict entries, or output as dict
@ -5473,10 +5496,10 @@ class Dict(TokenConverter):
['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
[['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
- color: light blue - color: 'light blue'
- posn: upper left - posn: 'upper left'
- shape: SQUARE - shape: 'SQUARE'
- texture: burlap - texture: 'burlap'
SQUARE SQUARE
{'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'} {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
@ -5535,12 +5558,12 @@ class Suppress(TokenConverter):
source = "a, b, c,d" source = "a, b, c,d"
wd = Word(alphas) wd = Word(alphas)
wd_list1 = wd + ZeroOrMore(',' + wd) wd_list1 = wd + (',' + wd)[...]
print(wd_list1.parse_string(source)) print(wd_list1.parse_string(source))
# often, delimiters that are useful during parsing are just in the # often, delimiters that are useful during parsing are just in the
# way afterward - use Suppress to keep them out of the parsed output # way afterward - use Suppress to keep them out of the parsed output
wd_list2 = wd + ZeroOrMore(Suppress(',') + wd) wd_list2 = wd + (Suppress(',') + wd)[...]
print(wd_list2.parse_string(source)) print(wd_list2.parse_string(source))
# Skipped text (using '...') can be suppressed as well # Skipped text (using '...') can be suppressed as well
@ -5564,13 +5587,13 @@ class Suppress(TokenConverter):
expr = _PendingSkip(NoMatch()) expr = _PendingSkip(NoMatch())
super().__init__(expr) super().__init__(expr)
def __add__(self, other): def __add__(self, other) -> "ParserElement":
if isinstance(self.expr, _PendingSkip): if isinstance(self.expr, _PendingSkip):
return Suppress(SkipTo(other)) + other return Suppress(SkipTo(other)) + other
else: else:
return super().__add__(other) return super().__add__(other)
def __sub__(self, other): def __sub__(self, other) -> "ParserElement":
if isinstance(self.expr, _PendingSkip): if isinstance(self.expr, _PendingSkip):
return Suppress(SkipTo(other)) - other return Suppress(SkipTo(other)) - other
else: else:
@ -5599,7 +5622,7 @@ def trace_parse_action(f: ParseAction) -> ParseAction:
def remove_duplicate_chars(tokens): def remove_duplicate_chars(tokens):
return ''.join(sorted(set(''.join(tokens)))) return ''.join(sorted(set(''.join(tokens))))
wds = OneOrMore(wd).set_parse_action(remove_duplicate_chars) wds = wd[1, ...].set_parse_action(remove_duplicate_chars)
print(wds.parse_string("slkdjs sld sldd sdlf sdljf")) print(wds.parse_string("slkdjs sld sldd sdlf sdljf"))
prints:: prints::
@ -5705,18 +5728,18 @@ def token_map(func, *args) -> ParseAction:
Example (compare the last to example in :class:`ParserElement.transform_string`:: Example (compare the last to example in :class:`ParserElement.transform_string`::
hex_ints = OneOrMore(Word(hexnums)).set_parse_action(token_map(int, 16)) hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16))
hex_ints.run_tests(''' hex_ints.run_tests('''
00 11 22 aa FF 0a 0d 1a 00 11 22 aa FF 0a 0d 1a
''') ''')
upperword = Word(alphas).set_parse_action(token_map(str.upper)) upperword = Word(alphas).set_parse_action(token_map(str.upper))
OneOrMore(upperword).run_tests(''' upperword[1, ...].run_tests('''
my kingdom for a horse my kingdom for a horse
''') ''')
wd = Word(alphas).set_parse_action(token_map(str.title)) wd = Word(alphas).set_parse_action(token_map(str.title))
OneOrMore(wd).set_parse_action(' '.join).run_tests(''' wd[1, ...].set_parse_action(' '.join).run_tests('''
now is the winter of our discontent made glorious summer by this sun of york now is the winter of our discontent made glorious summer by this sun of york
''') ''')
@ -5772,7 +5795,9 @@ punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]")
# build list of built-in expressions, for future reference if a global default value # build list of built-in expressions, for future reference if a global default value
# gets updated # gets updated
_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)] _builtin_exprs: List[ParserElement] = [
v for v in vars().values() if isinstance(v, ParserElement)
]
# backward compatibility names # backward compatibility names
tokenMap = token_map tokenMap = token_map

View file

@ -1,9 +1,8 @@
import railroad import railroad
import pyparsing import pyparsing
from pkg_resources import resource_filename import typing
from typing import ( from typing import (
List, List,
Optional,
NamedTuple, NamedTuple,
Generic, Generic,
TypeVar, TypeVar,
@ -16,13 +15,42 @@ from jinja2 import Template
from io import StringIO from io import StringIO
import inspect import inspect
with open(resource_filename(__name__, "template.jinja2"), encoding="utf-8") as fp:
template = Template(fp.read()) jinja2_template_source = """\
<!DOCTYPE html>
<html>
<head>
{% if not head %}
<style type="text/css">
.railroad-heading {
font-family: monospace;
}
</style>
{% else %}
{{ head | safe }}
{% endif %}
</head>
<body>
{{ body | safe }}
{% for diagram in diagrams %}
<div class="railroad-group">
<h1 class="railroad-heading">{{ diagram.title }}</h1>
<div class="railroad-description">{{ diagram.text }}</div>
<div class="railroad-svg">
{{ diagram.svg }}
</div>
</div>
{% endfor %}
</body>
</html>
"""
template = Template(jinja2_template_source)
# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet # Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet
NamedDiagram = NamedTuple( NamedDiagram = NamedTuple(
"NamedDiagram", "NamedDiagram",
[("name", str), ("diagram", Optional[railroad.DiagramItem]), ("index", int)], [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)],
) )
""" """
A simple structure for associating a name with a railroad diagram A simple structure for associating a name with a railroad diagram
@ -54,7 +82,7 @@ class AnnotatedItem(railroad.Group):
""" """
def __init__(self, label: str, item): def __init__(self, label: str, item):
super().__init__(item=item, label="[{}]".format(label)) super().__init__(item=item, label="[{}]".format(label) if label else label)
class EditablePartial(Generic[T]): class EditablePartial(Generic[T]):
@ -106,6 +134,8 @@ def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str:
""" """
data = [] data = []
for diagram in diagrams: for diagram in diagrams:
if diagram.diagram is None:
continue
io = StringIO() io = StringIO()
diagram.diagram.writeSvg(io.write) diagram.diagram.writeSvg(io.write)
title = diagram.name title = diagram.name
@ -134,9 +164,10 @@ def resolve_partial(partial: "EditablePartial[T]") -> T:
def to_railroad( def to_railroad(
element: pyparsing.ParserElement, element: pyparsing.ParserElement,
diagram_kwargs: Optional[dict] = None, diagram_kwargs: typing.Optional[dict] = None,
vertical: int = 3, vertical: int = 3,
show_results_names: bool = False, show_results_names: bool = False,
show_groups: bool = False,
) -> List[NamedDiagram]: ) -> List[NamedDiagram]:
""" """
Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram
@ -147,6 +178,8 @@ def to_railroad(
shown vertically instead of horizontally shown vertically instead of horizontally
:param show_results_names - bool to indicate whether results name annotations should be :param show_results_names - bool to indicate whether results name annotations should be
included in the diagram included in the diagram
:param show_groups - bool to indicate whether groups should be highlighted with an unlabeled
surrounding box
""" """
# Convert the whole tree underneath the root # Convert the whole tree underneath the root
lookup = ConverterState(diagram_kwargs=diagram_kwargs or {}) lookup = ConverterState(diagram_kwargs=diagram_kwargs or {})
@ -156,6 +189,7 @@ def to_railroad(
parent=None, parent=None,
vertical=vertical, vertical=vertical,
show_results_names=show_results_names, show_results_names=show_results_names,
show_groups=show_groups,
) )
root_id = id(element) root_id = id(element)
@ -211,12 +245,12 @@ class ElementState:
parent: EditablePartial, parent: EditablePartial,
number: int, number: int,
name: str = None, name: str = None,
parent_index: Optional[int] = None, parent_index: typing.Optional[int] = None,
): ):
#: The pyparsing element that this represents #: The pyparsing element that this represents
self.element: pyparsing.ParserElement = element self.element: pyparsing.ParserElement = element
#: The name of the element #: The name of the element
self.name: str = name self.name: typing.Optional[str] = name
#: The output Railroad element in an unconverted state #: The output Railroad element in an unconverted state
self.converted: EditablePartial = converted self.converted: EditablePartial = converted
#: The parent Railroad element, which we store so that we can extract this if it's duplicated #: The parent Railroad element, which we store so that we can extract this if it's duplicated
@ -224,7 +258,7 @@ class ElementState:
#: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram
self.number: int = number self.number: int = number
#: The index of this inside its parent #: The index of this inside its parent
self.parent_index: Optional[int] = parent_index self.parent_index: typing.Optional[int] = parent_index
#: If true, we should extract this out into a subdiagram #: If true, we should extract this out into a subdiagram
self.extract: bool = False self.extract: bool = False
#: If true, all of this element's children have been filled out #: If true, all of this element's children have been filled out
@ -265,7 +299,7 @@ class ConverterState:
Stores some state that persists between recursions into the element tree Stores some state that persists between recursions into the element tree
""" """
def __init__(self, diagram_kwargs: Optional[dict] = None): def __init__(self, diagram_kwargs: typing.Optional[dict] = None):
#: A dictionary mapping ParserElements to state relating to them #: A dictionary mapping ParserElements to state relating to them
self._element_diagram_states: Dict[int, ElementState] = {} self._element_diagram_states: Dict[int, ElementState] = {}
#: A dictionary mapping ParserElement IDs to subdiagrams generated from them #: A dictionary mapping ParserElement IDs to subdiagrams generated from them
@ -356,13 +390,14 @@ def _apply_diagram_item_enhancements(fn):
def _inner( def _inner(
element: pyparsing.ParserElement, element: pyparsing.ParserElement,
parent: Optional[EditablePartial], parent: typing.Optional[EditablePartial],
lookup: ConverterState = None, lookup: ConverterState = None,
vertical: int = None, vertical: int = None,
index: int = 0, index: int = 0,
name_hint: str = None, name_hint: str = None,
show_results_names: bool = False, show_results_names: bool = False,
) -> Optional[EditablePartial]: show_groups: bool = False,
) -> typing.Optional[EditablePartial]:
ret = fn( ret = fn(
element, element,
@ -372,6 +407,7 @@ def _apply_diagram_item_enhancements(fn):
index, index,
name_hint, name_hint,
show_results_names, show_results_names,
show_groups,
) )
# apply annotation for results name, if present # apply annotation for results name, if present
@ -405,13 +441,14 @@ def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]):
@_apply_diagram_item_enhancements @_apply_diagram_item_enhancements
def _to_diagram_element( def _to_diagram_element(
element: pyparsing.ParserElement, element: pyparsing.ParserElement,
parent: Optional[EditablePartial], parent: typing.Optional[EditablePartial],
lookup: ConverterState = None, lookup: ConverterState = None,
vertical: int = None, vertical: int = None,
index: int = 0, index: int = 0,
name_hint: str = None, name_hint: str = None,
show_results_names: bool = False, show_results_names: bool = False,
) -> Optional[EditablePartial]: show_groups: bool = False,
) -> typing.Optional[EditablePartial]:
""" """
Recursively converts a PyParsing Element to a railroad Element Recursively converts a PyParsing Element to a railroad Element
:param lookup: The shared converter state that keeps track of useful things :param lookup: The shared converter state that keeps track of useful things
@ -423,6 +460,7 @@ def _to_diagram_element(
:param name_hint: If provided, this will override the generated name :param name_hint: If provided, this will override the generated name
:param show_results_names: bool flag indicating whether to add annotations for results names :param show_results_names: bool flag indicating whether to add annotations for results names
:returns: The converted version of the input element, but as a Partial that hasn't yet been constructed :returns: The converted version of the input element, but as a Partial that hasn't yet been constructed
:param show_groups: bool flag indicating whether to show groups using bounding box
""" """
exprs = element.recurse() exprs = element.recurse()
name = name_hint or element.customName or element.__class__.__name__ name = name_hint or element.customName or element.__class__.__name__
@ -437,7 +475,7 @@ def _to_diagram_element(
if isinstance( if isinstance(
element, element,
( (
pyparsing.TokenConverter, # pyparsing.TokenConverter,
# pyparsing.Forward, # pyparsing.Forward,
pyparsing.Located, pyparsing.Located,
), ),
@ -457,6 +495,7 @@ def _to_diagram_element(
index=index, index=index,
name_hint=propagated_name, name_hint=propagated_name,
show_results_names=show_results_names, show_results_names=show_results_names,
show_groups=show_groups,
) )
# If the element isn't worth extracting, we always treat it as the first time we say it # If the element isn't worth extracting, we always treat it as the first time we say it
@ -510,6 +549,15 @@ def _to_diagram_element(
ret = EditablePartial.from_call(AnnotatedItem, label="LOOKAHEAD", item="") ret = EditablePartial.from_call(AnnotatedItem, label="LOOKAHEAD", item="")
elif isinstance(element, pyparsing.PrecededBy): elif isinstance(element, pyparsing.PrecededBy):
ret = EditablePartial.from_call(AnnotatedItem, label="LOOKBEHIND", item="") ret = EditablePartial.from_call(AnnotatedItem, label="LOOKBEHIND", item="")
elif isinstance(element, pyparsing.Group):
if show_groups:
ret = EditablePartial.from_call(AnnotatedItem, label="", item="")
else:
ret = EditablePartial.from_call(railroad.Group, label="", item="")
elif isinstance(element, pyparsing.TokenConverter):
ret = EditablePartial.from_call(
AnnotatedItem, label=type(element).__name__.lower(), item=""
)
elif isinstance(element, pyparsing.Opt): elif isinstance(element, pyparsing.Opt):
ret = EditablePartial.from_call(railroad.Optional, item="") ret = EditablePartial.from_call(railroad.Optional, item="")
elif isinstance(element, pyparsing.OneOrMore): elif isinstance(element, pyparsing.OneOrMore):
@ -558,6 +606,7 @@ def _to_diagram_element(
vertical=vertical, vertical=vertical,
index=i, index=i,
show_results_names=show_results_names, show_results_names=show_results_names,
show_groups=show_groups,
) )
# Some elements don't need to be shown in the diagram # Some elements don't need to be shown in the diagram

View file

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html>
<head>
{% if not head %}
<style type="text/css">
.railroad-heading {
font-family: monospace;
}
</style>
{% else %}
{{ hear | safe }}
{% endif %}
</head>
<body>
{{ body | safe }}
{% for diagram in diagrams %}
<div class="railroad-group">
<h1 class="railroad-heading">{{ diagram.title }}</h1>
<div class="railroad-description">{{ diagram.text }}</div>
<div class="railroad-svg">
{{ diagram.svg }}
</div>
</div>
{% endfor %}
</body>
</html>

View file

@ -2,7 +2,7 @@
import re import re
import sys import sys
from typing import Optional import typing
from .util import col, line, lineno, _collapse_string_to_ranges from .util import col, line, lineno, _collapse_string_to_ranges
from .unicode import pyparsing_unicode as ppu from .unicode import pyparsing_unicode as ppu
@ -25,7 +25,7 @@ class ParseBaseException(Exception):
self, self,
pstr: str, pstr: str,
loc: int = 0, loc: int = 0,
msg: Optional[str] = None, msg: typing.Optional[str] = None,
elem=None, elem=None,
): ):
self.loc = loc self.loc = loc

View file

@ -1,6 +1,7 @@
# helpers.py # helpers.py
import html.entities import html.entities
import re import re
import typing
from . import __diag__ from . import __diag__
from .core import * from .core import *
@ -14,8 +15,8 @@ def delimited_list(
expr: Union[str, ParserElement], expr: Union[str, ParserElement],
delim: Union[str, ParserElement] = ",", delim: Union[str, ParserElement] = ",",
combine: bool = False, combine: bool = False,
min: OptionalType[int] = None, min: typing.Optional[int] = None,
max: OptionalType[int] = None, max: typing.Optional[int] = None,
*, *,
allow_trailing_delim: bool = False, allow_trailing_delim: bool = False,
) -> ParserElement: ) -> ParserElement:
@ -69,9 +70,9 @@ def delimited_list(
def counted_array( def counted_array(
expr: ParserElement, expr: ParserElement,
int_expr: OptionalType[ParserElement] = None, int_expr: typing.Optional[ParserElement] = None,
*, *,
intExpr: OptionalType[ParserElement] = None, intExpr: typing.Optional[ParserElement] = None,
) -> ParserElement: ) -> ParserElement:
"""Helper to define a counted list of expressions. """Helper to define a counted list of expressions.
@ -185,7 +186,9 @@ def match_previous_expr(expr: ParserElement) -> ParserElement:
def must_match_these_tokens(s, l, t): def must_match_these_tokens(s, l, t):
theseTokens = _flatten(t.as_list()) theseTokens = _flatten(t.as_list())
if theseTokens != matchTokens: if theseTokens != matchTokens:
raise ParseException(s, l, "Expected {}, found{}".format(matchTokens, theseTokens)) raise ParseException(
s, l, "Expected {}, found{}".format(matchTokens, theseTokens)
)
rep.set_parse_action(must_match_these_tokens, callDuringTry=True) rep.set_parse_action(must_match_these_tokens, callDuringTry=True)
@ -195,7 +198,7 @@ def match_previous_expr(expr: ParserElement) -> ParserElement:
def one_of( def one_of(
strs: Union[IterableType[str], str], strs: Union[typing.Iterable[str], str],
caseless: bool = False, caseless: bool = False,
use_regex: bool = True, use_regex: bool = True,
as_keyword: bool = False, as_keyword: bool = False,
@ -310,7 +313,7 @@ def one_of(
return ret return ret
except sre_constants.error: except re.error:
warnings.warn( warnings.warn(
"Exception creating Regex for one_of, building MatchFirst", stacklevel=2 "Exception creating Regex for one_of, building MatchFirst", stacklevel=2
) )
@ -335,7 +338,7 @@ def dict_of(key: ParserElement, value: ParserElement) -> ParserElement:
text = "shape: SQUARE posn: upper left color: light blue texture: burlap" text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
print(OneOrMore(attr_expr).parse_string(text).dump()) print(attr_expr[1, ...].parse_string(text).dump())
attr_label = label attr_label = label
attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join) attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)
@ -350,10 +353,10 @@ def dict_of(key: ParserElement, value: ParserElement) -> ParserElement:
prints:: prints::
[['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
- color: light blue - color: 'light blue'
- posn: upper left - posn: 'upper left'
- shape: SQUARE - shape: 'SQUARE'
- texture: burlap - texture: 'burlap'
SQUARE SQUARE
SQUARE SQUARE
{'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
@ -459,7 +462,7 @@ def locatedExpr(expr: ParserElement) -> ParserElement:
def nested_expr( def nested_expr(
opener: Union[str, ParserElement] = "(", opener: Union[str, ParserElement] = "(",
closer: Union[str, ParserElement] = ")", closer: Union[str, ParserElement] = ")",
content: OptionalType[ParserElement] = None, content: typing.Optional[ParserElement] = None,
ignore_expr: ParserElement = quoted_string(), ignore_expr: ParserElement = quoted_string(),
*, *,
ignoreExpr: ParserElement = quoted_string(), ignoreExpr: ParserElement = quoted_string(),
@ -680,6 +683,8 @@ def make_xml_tags(
return _makeTags(tag_str, True) return _makeTags(tag_str, True)
any_open_tag: ParserElement
any_close_tag: ParserElement
any_open_tag, any_close_tag = make_html_tags( any_open_tag, any_close_tag = make_html_tags(
Word(alphas, alphanums + "_:").set_name("any tag") Word(alphas, alphanums + "_:").set_name("any tag")
) )
@ -708,7 +713,7 @@ InfixNotationOperatorSpec = Union[
InfixNotationOperatorArgType, InfixNotationOperatorArgType,
int, int,
OpAssoc, OpAssoc,
OptionalType[ParseAction], typing.Optional[ParseAction],
], ],
Tuple[ Tuple[
InfixNotationOperatorArgType, InfixNotationOperatorArgType,
@ -758,10 +763,14 @@ def infix_notation(
a tuple or list of functions, this is equivalent to calling a tuple or list of functions, this is equivalent to calling
``set_parse_action(*fn)`` ``set_parse_action(*fn)``
(:class:`ParserElement.set_parse_action`) (:class:`ParserElement.set_parse_action`)
- ``lpar`` - expression for matching left-parentheses - ``lpar`` - expression for matching left-parentheses; if passed as a
(default= ``Suppress('(')``) str, then will be parsed as Suppress(lpar). If lpar is passed as
- ``rpar`` - expression for matching right-parentheses an expression (such as ``Literal('(')``), then it will be kept in
(default= ``Suppress(')')``) the parsed results, and grouped with them. (default= ``Suppress('(')``)
- ``rpar`` - expression for matching right-parentheses; if passed as a
str, then will be parsed as Suppress(rpar). If rpar is passed as
an expression (such as ``Literal(')')``), then it will be kept in
the parsed results, and grouped with them. (default= ``Suppress(')')``)
Example:: Example::
@ -803,9 +812,17 @@ def infix_notation(
_FB.__name__ = "FollowedBy>" _FB.__name__ = "FollowedBy>"
ret = Forward() ret = Forward()
lpar = Suppress(lpar) if isinstance(lpar, str):
rpar = Suppress(rpar) lpar = Suppress(lpar)
lastExpr = base_expr | (lpar + ret + rpar) if isinstance(rpar, str):
rpar = Suppress(rpar)
# if lpar and rpar are not suppressed, wrap in group
if not (isinstance(rpar, Suppress) and isinstance(rpar, Suppress)):
lastExpr = base_expr | Group(lpar + ret + rpar)
else:
lastExpr = base_expr | (lpar + ret + rpar)
for i, operDef in enumerate(op_list): for i, operDef in enumerate(op_list):
opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4]
if isinstance(opExpr, str_type): if isinstance(opExpr, str_type):
@ -826,7 +843,7 @@ def infix_notation(
if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT): if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
raise ValueError("operator must indicate right or left associativity") raise ValueError("operator must indicate right or left associativity")
thisExpr = Forward().set_name(term_name) thisExpr: Forward = Forward().set_name(term_name)
if rightLeftAssoc is OpAssoc.LEFT: if rightLeftAssoc is OpAssoc.LEFT:
if arity == 1: if arity == 1:
matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...]) matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
@ -931,7 +948,7 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]
assignment = Group(identifier + "=" + rvalue) assignment = Group(identifier + "=" + rvalue)
stmt << (funcDef | assignment | identifier) stmt << (funcDef | assignment | identifier)
module_body = OneOrMore(stmt) module_body = stmt[1, ...]
parseTree = module_body.parseString(data) parseTree = module_body.parseString(data)
parseTree.pprint() parseTree.pprint()
@ -1041,7 +1058,9 @@ python_style_comment = Regex(r"#.*").set_name("Python style comment")
# build list of built-in expressions, for future reference if a global default value # build list of built-in expressions, for future reference if a global default value
# gets updated # gets updated
_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)] _builtin_exprs: List[ParserElement] = [
v for v in vars().values() if isinstance(v, ParserElement)
]
# pre-PEP8 compatible names # pre-PEP8 compatible names

0
lib/pyparsing/py.typed Normal file
View file

View file

@ -65,9 +65,9 @@ class ParseResults:
'month' in result -> True 'month' in result -> True
'minutes' in result -> False 'minutes' in result -> False
result.dump() -> ['1999', '/', '12', '/', '31'] result.dump() -> ['1999', '/', '12', '/', '31']
- day: 31 - day: '31'
- month: 12 - month: '12'
- year: 1999 - year: '1999'
""" """
_null_values: Tuple[Any, ...] = (None, [], "", ()) _null_values: Tuple[Any, ...] = (None, [], "", ())
@ -287,7 +287,7 @@ class ParseResults:
print(numlist.parse_string("0 123 321")) # -> ['123', '321'] print(numlist.parse_string("0 123 321")) # -> ['123', '321']
label = Word(alphas) label = Word(alphas)
patt = label("LABEL") + OneOrMore(Word(nums)) patt = label("LABEL") + Word(nums)[1, ...]
print(patt.parse_string("AAB 123 321").dump()) print(patt.parse_string("AAB 123 321").dump())
# Use pop() in a parse action to remove named result (note that corresponding value is not # Use pop() in a parse action to remove named result (note that corresponding value is not
@ -301,7 +301,7 @@ class ParseResults:
prints:: prints::
['AAB', '123', '321'] ['AAB', '123', '321']
- LABEL: AAB - LABEL: 'AAB'
['AAB', '123', '321'] ['AAB', '123', '321']
""" """
@ -394,7 +394,7 @@ class ParseResults:
Example:: Example::
patt = OneOrMore(Word(alphas)) patt = Word(alphas)[1, ...]
# use a parse action to append the reverse of the matched strings, to make a palindrome # use a parse action to append the reverse of the matched strings, to make a palindrome
def make_palindrome(tokens): def make_palindrome(tokens):
@ -487,7 +487,7 @@ class ParseResults:
Example:: Example::
patt = OneOrMore(Word(alphas)) patt = Word(alphas)[1, ...]
result = patt.parse_string("sldkj lsdkj sldkj") result = patt.parse_string("sldkj lsdkj sldkj")
# even though the result prints in string-like form, it is actually a pyparsing ParseResults # even though the result prints in string-like form, it is actually a pyparsing ParseResults
print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj'] print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
@ -554,7 +554,7 @@ class ParseResults:
user_data = (Group(house_number_expr)("house_number") user_data = (Group(house_number_expr)("house_number")
| Group(ssn_expr)("ssn") | Group(ssn_expr)("ssn")
| Group(integer)("age")) | Group(integer)("age"))
user_info = OneOrMore(user_data) user_info = user_data[1, ...]
result = user_info.parse_string("22 111-22-3333 #221B") result = user_info.parse_string("22 111-22-3333 #221B")
for item in result: for item in result:
@ -603,15 +603,15 @@ class ParseResults:
integer = Word(nums) integer = Word(nums)
date_str = integer("year") + '/' + integer("month") + '/' + integer("day") date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
result = date_str.parse_string('12/31/1999') result = date_str.parse_string('1999/12/31')
print(result.dump()) print(result.dump())
prints:: prints::
['12', '/', '31', '/', '1999'] ['1999', '/', '12', '/', '31']
- day: 1999 - day: '31'
- month: 31 - month: '12'
- year: 12 - year: '1999'
""" """
out = [] out = []
NL = "\n" NL = "\n"

View file

@ -1,7 +1,7 @@
# testing.py # testing.py
from contextlib import contextmanager from contextlib import contextmanager
from typing import Optional import typing
from .core import ( from .core import (
ParserElement, ParserElement,
@ -237,12 +237,12 @@ class pyparsing_test:
@staticmethod @staticmethod
def with_line_numbers( def with_line_numbers(
s: str, s: str,
start_line: Optional[int] = None, start_line: typing.Optional[int] = None,
end_line: Optional[int] = None, end_line: typing.Optional[int] = None,
expand_tabs: bool = True, expand_tabs: bool = True,
eol_mark: str = "|", eol_mark: str = "|",
mark_spaces: Optional[str] = None, mark_spaces: typing.Optional[str] = None,
mark_control: Optional[str] = None, mark_control: typing.Optional[str] = None,
) -> str: ) -> str:
""" """
Helpful method for debugging a parser - prints a string with line and column numbers. Helpful method for debugging a parser - prints a string with line and column numbers.

View file

@ -120,7 +120,18 @@ class pyparsing_unicode(unicode_set):
A namespace class for defining common language unicode_sets. A namespace class for defining common language unicode_sets.
""" """
_ranges: UnicodeRangeList = [(32, sys.maxunicode)] # fmt: off
# define ranges in language character sets
_ranges: UnicodeRangeList = [
(0x0020, sys.maxunicode),
]
class BasicMultilingualPlane(unicode_set):
"Unicode set for the Basic Multilingual Plane"
_ranges: UnicodeRangeList = [
(0x0020, 0xFFFF),
]
class Latin1(unicode_set): class Latin1(unicode_set):
"Unicode set for Latin-1 Unicode Character Range" "Unicode set for Latin-1 Unicode Character Range"
@ -278,11 +289,13 @@ class pyparsing_unicode(unicode_set):
class CJK(Chinese, Japanese, Hangul): class CJK(Chinese, Japanese, Hangul):
"Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range" "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
pass
class Thai(unicode_set): class Thai(unicode_set):
"Unicode set for Thai Unicode Character Range" "Unicode set for Thai Unicode Character Range"
_ranges: UnicodeRangeList = [(0x0E01, 0x0E3A), (0x0E3F, 0x0E5B)] _ranges: UnicodeRangeList = [
(0x0E01, 0x0E3A),
(0x0E3F, 0x0E5B)
]
class Arabic(unicode_set): class Arabic(unicode_set):
"Unicode set for Arabic Unicode Character Range" "Unicode set for Arabic Unicode Character Range"
@ -308,7 +321,12 @@ class pyparsing_unicode(unicode_set):
class Devanagari(unicode_set): class Devanagari(unicode_set):
"Unicode set for Devanagari Unicode Character Range" "Unicode set for Devanagari Unicode Character Range"
_ranges: UnicodeRangeList = [(0x0900, 0x097F), (0xA8E0, 0xA8FF)] _ranges: UnicodeRangeList = [
(0x0900, 0x097F),
(0xA8E0, 0xA8FF)
]
# fmt: on
pyparsing_unicode.Japanese._ranges = ( pyparsing_unicode.Japanese._ranges = (
@ -317,7 +335,9 @@ pyparsing_unicode.Japanese._ranges = (
+ pyparsing_unicode.Japanese.Katakana._ranges + pyparsing_unicode.Japanese.Katakana._ranges
) )
# define ranges in language character sets pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
# add language identifiers using language Unicode
pyparsing_unicode.العربية = pyparsing_unicode.Arabic pyparsing_unicode.العربية = pyparsing_unicode.Arabic
pyparsing_unicode.中文 = pyparsing_unicode.Chinese pyparsing_unicode.中文 = pyparsing_unicode.Chinese
pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic

View file

@ -31,7 +31,7 @@ plexapi==4.9.2
portend==3.1.0 portend==3.1.0
profilehooks==1.12.0 profilehooks==1.12.0
PyJWT==2.3.0 PyJWT==2.3.0
pyparsing==3.0.7 pyparsing==3.0.9
python-dateutil==2.8.2 python-dateutil==2.8.2
python-twitter==3.5 python-twitter==3.5
pytz==2022.1 pytz==2022.1