List,
TextIO,
Set,
+ Dict as DictType,
)
from abc import ABC, abstractmethod
from enum import Enum
_FifoCache,
_UnboundedCache,
__config_flags,
- _collapseStringToRanges,
- _escapeRegexRangeChars,
+ _collapse_string_to_ranges,
+ _escape_regex_range_chars,
_bslash,
_flatten,
LRUMemo as _LRUMemo,
from .unicode import pyparsing_unicode
_MAX_INT = sys.maxsize
-str_type = (str, bytes)
+str_type: Tuple[type, ...] = (str, bytes)
#
# Copyright (c) 2003-2021 Paul T. McGuire
def _should_enable_warnings(
- cmd_line_warn_options: List[str], warn_env_var: str
+ cmd_line_warn_options: List[str], warn_env_var: OptionalType[str]
) -> bool:
enable = bool(warn_env_var)
for warn_opt in cmd_line_warn_options:
# update whitespace all parse expressions defined in this module
for expr in _builtin_exprs:
if expr.copyDefaultWhiteChars:
- expr.whiteChars = chars
+ expr.whiteChars = set(chars)
@staticmethod
def inline_literals_using(cls: type):
return tokenlist
# @profile
- def _parseNoCache(self, instring, loc, doActions=True, callPreParse=True):
+ def _parseNoCache(
+ self, instring, loc, doActions=True, callPreParse=True
+ ) -> Tuple[int, ParseResults]:
TRY, MATCH, FAIL = 0, 1, 2
debugging = self.debug # and doActions)
len_instring = len(instring)
# print("Match {} at loc {}({}, {})".format(self, loc, lineno(loc, instring), col(loc, instring)))
try:
if callPreParse and self.callPreparse:
- preloc = self.preParse(instring, loc)
+ pre_loc = self.preParse(instring, loc)
else:
- preloc = loc
- tokensStart = preloc
+ pre_loc = loc
+ tokens_start = pre_loc
if self.debugActions[TRY]:
- self.debugActions[TRY](instring, tokensStart, self)
- if self.mayIndexError or preloc >= len_instring:
+ self.debugActions[TRY](instring, tokens_start, self)
+ if self.mayIndexError or pre_loc >= len_instring:
try:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
except IndexError:
raise ParseException(instring, len_instring, self.errmsg, self)
else:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
except Exception as err:
# print("Exception raised:", err)
if self.debugActions[FAIL]:
- self.debugActions[FAIL](instring, tokensStart, self, err)
+ self.debugActions[FAIL](instring, tokens_start, self, err)
if self.failAction:
- self.failAction(instring, tokensStart, self, err)
+ self.failAction(instring, tokens_start, self, err)
raise
else:
if callPreParse and self.callPreparse:
- preloc = self.preParse(instring, loc)
+ pre_loc = self.preParse(instring, loc)
else:
- preloc = loc
- tokensStart = preloc
- if self.mayIndexError or preloc >= len_instring:
+ pre_loc = loc
+ tokens_start = pre_loc
+ if self.mayIndexError or pre_loc >= len_instring:
try:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
except IndexError:
raise ParseException(instring, len_instring, self.errmsg, self)
else:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
tokens = self.postParse(instring, loc, tokens)
- retTokens = ParseResults(
+ ret_tokens = ParseResults(
tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults
)
if self.parseAction and (doActions or self.callDuringTry):
try:
for fn in self.parseAction:
try:
- tokens = fn(instring, tokensStart, retTokens)
+ tokens = fn(instring, tokens_start, ret_tokens)
except IndexError as parse_action_exc:
exc = ParseException("exception raised in parse action")
raise exc from parse_action_exc
- if tokens is not None and tokens is not retTokens:
- retTokens = ParseResults(
+ if tokens is not None and tokens is not ret_tokens:
+ ret_tokens = ParseResults(
tokens,
self.resultsName,
asList=self.saveAsList
except Exception as err:
# print "Exception raised in user parse action:", err
if self.debugActions[FAIL]:
- self.debugActions[FAIL](instring, tokensStart, self, err)
+ self.debugActions[FAIL](instring, tokens_start, self, err)
raise
else:
for fn in self.parseAction:
try:
- tokens = fn(instring, tokensStart, retTokens)
+ tokens = fn(instring, tokens_start, ret_tokens)
except IndexError as parse_action_exc:
exc = ParseException("exception raised in parse action")
raise exc from parse_action_exc
- if tokens is not None and tokens is not retTokens:
- retTokens = ParseResults(
+ if tokens is not None and tokens is not ret_tokens:
+ ret_tokens = ParseResults(
tokens,
self.resultsName,
asList=self.saveAsList
modal=self.modalResults,
)
if debugging:
- # print("Matched", self, "->", retTokens.as_list())
+ # print("Matched", self, "->", ret_tokens.as_list())
if self.debugActions[MATCH]:
- self.debugActions[MATCH](instring, tokensStart, loc, self, retTokens)
+ self.debugActions[MATCH](instring, tokens_start, loc, self, ret_tokens)
- return loc, retTokens
+ return loc, ret_tokens
def try_parse(self, instring: str, loc: int, raise_fatal: bool = False) -> int:
try:
# cache for left-recursion in Forward references
recursion_lock = RLock()
- recursion_memos = (
- {}
- ) # type: dict[tuple[int, Forward, bool], tuple[int, ParseResults | Exception]]
+ recursion_memos: DictType[
+ Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]]
+ ] = {}
# argument cache for optimizing repeated calls when backtracking through recursive expressions
packrat_cache = (
failure_tests: bool = False,
post_parse: Callable[[str, ParseResults], str] = None,
file: OptionalType[TextIO] = None,
+ with_line_numbers: bool = False,
*,
parseAll: bool = True,
fullDump: bool = True,
`fn(test_string, parse_results)` and returns a string to be added to the test output
- ``file`` - (default= ``None``) optional file-like object to which test output will be written;
if None, will default to ``sys.stdout``
+ - ``with_line_numbers`` - default= ``False``) show test strings with line and column numbers
Returns: a (success, results) tuple, where success indicates that all tests succeeded
(or failed if ``failure_tests`` is True), and the results contain a list of lines of each
continue
out = [
"\n" + "\n".join(comments) if comments else "",
- pyparsing_test.with_line_numbers(t),
+ pyparsing_test.with_line_numbers(t) if with_line_numbers else t,
]
comments = []
try:
)
else:
out.append(result.dump(full=fullDump))
- out.append("")
+ out.append("")
if printResults:
print_("\n".join(out))
self.minLen, "" if self.maxLen == _MAX_INT else self.maxLen
)
self.reString = "[{}]{}".format(
- _collapseStringToRanges(self.initChars),
+ _collapse_string_to_ranges(self.initChars),
repeat,
)
elif len(self.initChars) == 1:
repeat = "{{0,{}}}".format(max - 1)
self.reString = "{}[{}]{}".format(
re.escape(self.initCharsOrig),
- _collapseStringToRanges(self.bodyChars),
+ _collapse_string_to_ranges(self.bodyChars),
repeat,
)
else:
else:
repeat = "{{0,{}}}".format(max - 1)
self.reString = "[{}][{}]{}".format(
- _collapseStringToRanges(self.initChars),
- _collapseStringToRanges(self.bodyChars),
+ _collapse_string_to_ranges(self.initChars),
+ _collapse_string_to_ranges(self.bodyChars),
repeat,
)
if self.asKeyword:
def _generateDefaultName(self):
def charsAsStr(s):
max_repr_len = 16
- s = _collapseStringToRanges(s, re_escape=False)
+ s = _collapse_string_to_ranges(s, re_escape=False)
if len(s) > max_repr_len:
return s[: max_repr_len - 3] + "..."
else:
super().__init__(
charset, exact=1, asKeyword=asKeyword, excludeChars=excludeChars
)
- self.reString = "[{}]".format(_collapseStringToRanges(self.initChars))
+ self.reString = "[{}]".format(_collapse_string_to_ranges(self.initChars))
if asKeyword:
self.reString = r"\b{}\b".format(self.reString)
self.re = re.compile(self.reString)
+ "|".join(
"(?:{}(?!{}))".format(
re.escape(self.endQuoteChar[:i]),
- _escapeRegexRangeChars(self.endQuoteChar[i:]),
+ _escape_regex_range_chars(self.endQuoteChar[i:]),
)
for i in range(len(self.endQuoteChar) - 1, 0, -1)
)
self.flags = re.MULTILINE | re.DOTALL
inner_pattern += r"{}(?:[^{}{}])".format(
sep,
- _escapeRegexRangeChars(self.endQuoteChar[0]),
- (_escapeRegexRangeChars(escChar) if escChar is not None else ""),
+ _escape_regex_range_chars(self.endQuoteChar[0]),
+ (_escape_regex_range_chars(escChar) if escChar is not None else ""),
)
else:
self.flags = 0
inner_pattern += r"{}(?:[^{}\n\r{}])".format(
sep,
- _escapeRegexRangeChars(self.endQuoteChar[0]),
- (_escapeRegexRangeChars(escChar) if escChar is not None else ""),
+ _escape_regex_range_chars(self.endQuoteChar[0]),
+ (_escape_regex_range_chars(escChar) if escChar is not None else ""),
)
self.pattern = "".join(
self.mayIndexError = False
def _generateDefaultName(self):
- not_chars_str = _collapseStringToRanges(self.notChars)
+ not_chars_str = _collapse_string_to_ranges(self.notChars)
if len(not_chars_str) > 16:
return "!W:({}...)".format(self.notChars[: 16 - 3])
else:
def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
super().__init__(savelist)
self.exprs: List[ParserElement]
- exprs: Iterable[ParserElement]
if isinstance(exprs, _generatorType):
exprs = list(exprs)
def streamline(self):
if self.streamlined:
- return
+ return self
super().streamline()
def _setResultsName(self, name, listAllMatches=False):
if __diag__.warn_ungrouped_named_tokens_in_collection:
for e in self.exprs:
- if isinstance(e, ParserElement) and e.resultsName:
+ if (
+ isinstance(e, ParserElement)
+ and e.resultsName
+ and not e.resultsName.startswith("_NOWARN")
+ ):
warnings.warn(
"{}: setting results name {!r} on {} expression "
"collides with {!r} on contained expression".format(
self.mayReturnEmpty = True
self.callPreparse = True
- def streamline(self):
+ def streamline(self) -> ParserElement:
# collapse any _PendingSkip's
if self.exprs:
if any(
else:
self.mayReturnEmpty = True
- def streamline(self):
+ def streamline(self) -> ParserElement:
super().streamline()
if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
else:
self.mayReturnEmpty = True
- def streamline(self):
+ def streamline(self) -> ParserElement:
if self.streamlined:
- return
+ return self
super().streamline()
if self.exprs:
self.initExprGroups = True
self.saveAsList = True
- def streamline(self):
+ def streamline(self) -> ParserElement:
super().streamline()
if self.exprs:
self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
def _setResultsName(self, name, listAllMatches=False):
if __diag__.warn_ungrouped_named_tokens_in_collection:
for e in [self.expr] + self.expr.recurse():
- if isinstance(e, ParserElement) and e.resultsName:
+ if (
+ isinstance(e, ParserElement)
+ and e.resultsName
+ and not e.resultsName.startswith("_NOWARN")
+ ):
warnings.warn(
"{}: setting results name {!r} on {} expression "
"collides with {!r} on contained expression".format(
import collections
import itertools
from functools import lru_cache
-from typing import List
-
+from typing import List, Union, Iterable
_bslash = chr(92)
@lru_cache(maxsize=128)
-def col(loc: int, strg: str):
+def col(loc: int, strg: str) -> int:
"""
Returns current column within a string, counting newlines as line separators.
The first column is number 1.
@lru_cache(maxsize=128)
-def lineno(loc: int, strg: str):
+def lineno(loc: int, strg: str) -> int:
"""Returns current line number within a string, counting newlines as line separators.
The first line is number 1.
@lru_cache(maxsize=128)
-def line(loc: int, strg: str):
+def line(loc: int, strg: str) -> str:
"""
Returns the line of text containing loc within a string, counting newlines as line separators.
"""
- lastCR = strg.rfind("\n", 0, loc)
- nextCR = strg.find("\n", loc)
- return strg[lastCR + 1 : nextCR] if nextCR >= 0 else strg[lastCR + 1 :]
+ last_cr = strg.rfind("\n", 0, loc)
+ next_cr = strg.find("\n", loc)
+ return strg[last_cr + 1 : next_cr] if next_cr >= 0 else strg[last_cr + 1 :]
class _UnboundedCache:
cache_get = cache.get
self.not_in_cache = not_in_cache = object()
- def get(self, key):
+ def get(_, key):
return cache_get(key, not_in_cache)
- def set(self, key, value):
+ def set_(_, key, value):
cache[key] = value
- def clear(self):
+ def clear(_):
cache.clear()
self.size = None
self.get = types.MethodType(get, self)
- self.set = types.MethodType(set, self)
+ self.set = types.MethodType(set_, self)
self.clear = types.MethodType(clear, self)
cache = collections.OrderedDict()
cache_get = cache.get
- def get(self, key):
+ def get(_, key):
return cache_get(key, not_in_cache)
- def set(self, key, value):
+ def set_(_, key, value):
cache[key] = value
while len(cache) > size:
cache.popitem(last=False)
- def clear(self):
+ def clear(_):
cache.clear()
self.size = size
self.get = types.MethodType(get, self)
- self.set = types.MethodType(set, self)
+ self.set = types.MethodType(set_, self)
self.clear = types.MethodType(clear, self)
pass
-def _escapeRegexRangeChars(s: str):
+def _escape_regex_range_chars(s: str) -> str:
# escape these chars: ^-[]
for c in r"\^-[]":
s = s.replace(c, _bslash + c)
return str(s)
-def _collapseStringToRanges(s: str, re_escape: bool = True):
+def _collapse_string_to_ranges(
+ s: Union[str, Iterable[str]], re_escape: bool = True
+) -> str:
def is_consecutive(c):
c_int = ord(c)
is_consecutive.prev, prev = c_int, is_consecutive.prev
return "".join(ret)
-def _flatten(ll: List):
+def _flatten(ll: list) -> list:
ret = []
for i in ll:
if isinstance(i, list):
name is defined on a containing expression with ungrouped subexpressions that also
have results names (default=True)
"""
+ with self.assertDoesNotWarn(
+ msg="raised {} warning when not enabled".format(
+ pp.Diagnostics.warn_ungrouped_named_tokens_in_collection
+ )
+ ):
+ COMMA = pp.Suppress(",").setName("comma")
+ coord = ppc.integer("x") + COMMA + ppc.integer("y")
+ path = coord[...].setResultsName("path")
with ppt.reset_pyparsing_context():
pp.enable_diag(pp.Diagnostics.warn_ungrouped_named_tokens_in_collection)
):
path = coord[...].setResultsName("path")
+ def testDontWarnUngroupedNamedTokensIfUngroupedNamesStartWithNOWARN(self):
+ """
+ - warn_ungrouped_named_tokens_in_collection - flag to enable warnings when a results
+ name is defined on a containing expression with ungrouped subexpressions that also
+ have results names (default=True)
+ """
+ with ppt.reset_pyparsing_context():
+ pp.enable_diag(pp.Diagnostics.warn_ungrouped_named_tokens_in_collection)
+
+ with self.assertDoesNotWarn(
+ msg="raised {} warning inner names start with '_NOWARN'".format(
+ pp.Diagnostics.warn_ungrouped_named_tokens_in_collection
+ )
+ ):
+ pp.originalTextFor(pp.Word("ABC")[...])("words")
+
def testWarnNameSetOnEmptyForward(self):
"""
- warn_name_set_on_empty_Forward - flag to enable warnings when a Forward is defined
with a results name, but has no contents defined (default=False)
"""
+ with self.assertDoesNotWarn(
+ msg="raised {} warning when not enabled".format(
+ pp.Diagnostics.warn_name_set_on_empty_Forward
+ )
+ ):
+ base = pp.Forward()("z")
+
with ppt.reset_pyparsing_context():
pp.enable_diag(pp.Diagnostics.warn_name_set_on_empty_Forward)
has no contents defined (default=False)
"""
+ with self.assertDoesNotWarn(
+ msg="raised {} warning when not enabled".format(
+ pp.Diagnostics.warn_on_parse_using_empty_Forward
+ )
+ ):
+ base = pp.Forward()
+ try:
+ print(base.parseString("x"))
+ except ParseException as pe:
+ pass
+
with ppt.reset_pyparsing_context():
pp.enable_diag(pp.Diagnostics.warn_on_parse_using_empty_Forward)
print("warn_on_assignment_to_Forward not supported on PyPy")
return
+ def a_method():
+ base = pp.Forward()
+ base = pp.Word(pp.alphas)[...] | "(" + base + ")"
+
+ with self.assertDoesNotWarn(
+ msg="raised {} warning when not enabled".format(
+ pp.Diagnostics.warn_on_assignment_to_Forward
+ )
+ ):
+ a_method()
+
with ppt.reset_pyparsing_context():
pp.enable_diag(pp.Diagnostics.warn_on_assignment_to_Forward)
- def a_method():
- base = pp.Forward()
- base = pp.Word(pp.alphas)[...] | "(" + base + ")"
-
with self.assertWarns(
UserWarning,
msg="failed to warn when using '=' to assign expression to a Forward",
incorrectly called with multiple str arguments (default=True)
"""
with self.assertDoesNotWarn(
- "warn_on_multiple_string_args_to_oneof warning raised when not enabled"
+ msg="raised {} warning when not enabled".format(
+ pp.Diagnostics.warn_on_multiple_string_args_to_oneof
+ )
):
a = pp.oneOf("A", "B")
self.assertEqual("a", a.name)
self.assertEqual("bbb", b.name)
+ def testDelimitedListName(self):
+ bool_constant = pp.Literal("True") | "true" | "False" | "false"
+ bool_list = pp.delimitedList(bool_constant)
+ print(bool_list)
+ self.assertEqual(
+ "{'True' | 'true' | 'False' | 'false'} [, {'True' | 'true' | 'False' | 'false'}]...",
+ str(bool_list),
+ )
+
+ bool_constant.setName("bool")
+ print(bool_constant)
+ print(bool_constant.streamline())
+ bool_list2 = pp.delimitedList(bool_constant)
+ print(bool_constant)
+ print(bool_constant.streamline())
+ print(bool_list2)
+ self.assertEqual("bool [, bool]...", str(bool_list2))
+
def testEnableDebugOnNamedExpressions(self):
"""
- enable_debug_on_named_expressions - flag to auto-enable debug on all subsequent
len(results) > 0, "MatchFirst error - not iterating over all choices"
)
+ def testStreamlineOfExpressionsAfterSetName(self):
+ bool_constant = pp.Literal("True") | "true" | "False" | "false"
+ self.assertEqual(
+ "{'True' | 'true' | 'False' | 'false'}", str(bool_constant.streamline())
+ )
+ bool_constant.setName("bool")
+ self.assertEqual("bool", str(bool_constant.streamline()))
+
def testStreamlineOfSubexpressions(self):
# verify streamline of subexpressions
print("verify proper streamline logic")