Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meaningful repr for ParserElements #423

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
125 changes: 117 additions & 8 deletions pyparsing/core.py
Expand Up @@ -6,6 +6,7 @@
from typing import (
Any,
Callable,
ClassVar,
Generator,
List,
NamedTuple,
Expand Down Expand Up @@ -475,6 +476,9 @@ def __init__(self, savelist: bool = False):
self.callDuringTry = False
self.suppress_warnings_: List[Diagnostics] = []

def __repr__(self):
return f"{type(self).__name__}()"

def suppress_warning(self, warning_type: Diagnostics) -> "ParserElement":
"""
Suppress warnings emitted for a particular diagnostic on this expression.
Expand Down Expand Up @@ -1408,7 +1412,8 @@ def __sub__(self, other) -> "ParserElement":
raise TypeError(
f"Cannot combine element of type {type(other).__name__} with ParserElement"
)
return self + And._ErrorStop() + other
# Construct And directly to ensure that an _ErrorStop is never the last element
ptmcg marked this conversation as resolved.
Show resolved Hide resolved
return And([self, And._ErrorStop(), other])

def __rsub__(self, other) -> "ParserElement":
"""
Expand Down Expand Up @@ -1857,9 +1862,6 @@ def name(self) -> str:
def __str__(self) -> str:
return self.name

def __repr__(self) -> str:
return str(self)

def streamline(self) -> "ParserElement":
self.streamlined = True
self._defaultName = None
Expand Down Expand Up @@ -2299,7 +2301,7 @@ def must_skip(t):
def show_skip(t):
if t._skipped.as_list()[-1:] == [""]:
t.pop("_skipped")
t["_skipped"] = "missing <" + repr(self.anchor) + ">"
t["_skipped"] = f"missing <{self.anchor}>"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used to show repr(self.anchor) - should change to {self.anchor!r}"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was so that an existing unit test would pass. I'm still hazy on the what/why of SkipTo in this context - should the test be changed instead?


return (
self.anchor + skipper().add_parse_action(must_skip)
Expand All @@ -2308,9 +2310,6 @@ def show_skip(t):

return self.anchor + skipper + other

def __repr__(self):
return self.defaultName

def parseImpl(self, *args):
raise Exception(
"use of `...` expression without following SkipTo target expression"
Expand Down Expand Up @@ -2390,6 +2389,9 @@ def __copy__(self) -> "Literal":
obj.__dict__.update(self.__dict__)
return obj

def __repr__(self):
return f"{type(self).__name__}({self.match!r})"

def _generateDefaultName(self) -> str:
return repr(self.match)

Expand All @@ -2411,6 +2413,9 @@ def __init__(self, match_string="", *, matchString=""):
self.mayReturnEmpty = True
self.mayIndexError = False

def __repr__(self):
return "Empty()"

def _generateDefaultName(self) -> str:
return "Empty"

Expand All @@ -2419,6 +2424,9 @@ def parseImpl(self, instring, loc, doActions=True):


class _SingleCharLiteral(Literal):
def __repr__(self):
return f"Literal({self.match!r})"

def parseImpl(self, instring, loc, doActions=True):
if instring[loc] == self.firstMatchChar:
return loc + 1, self.match
Expand Down Expand Up @@ -2486,6 +2494,9 @@ def __init__(
identChars = identChars.upper()
self.identChars = set(identChars)

def __repr__(self) -> str:
return f"Keyword({self.match!r})"

def _generateDefaultName(self) -> str:
return repr(self.match)

Expand Down Expand Up @@ -2567,6 +2578,9 @@ def __init__(self, match_string: str = "", *, matchString: str = ""):
self.returnString = match_string
self.errmsg = "Expected " + self.name

def __repr__(self) -> str:
return f"CaselessLiteral({self.returnString!r})"

def parseImpl(self, instring, loc, doActions=True):
if instring[loc : loc + self.matchLen].upper() == self.match:
return loc + self.matchLen, self.returnString
Expand Down Expand Up @@ -2650,6 +2664,9 @@ def __init__(
self.mayIndexError = False
self.mayReturnEmpty = False

def __repr__(self) -> str:
return f"{type(self).__name__}({self.match_string!r}, {self.maxMismatches})"

def _generateDefaultName(self) -> str:
return f"{type(self).__name__}:{self.match_string!r}"

Expand Down Expand Up @@ -2870,6 +2887,14 @@ def __init__(
self.re_match = self.re.match
self.parseImpl = self.parseImpl_regex

def __repr__(self) -> str:
init = "".join(sorted(self.initChars))
args = f"{init!r}"
if self.initChars != self.bodyChars:
body = "".join(sorted(self.bodyChars))
args += f", {body!r}"
return f"{type(self).__name__}({args})"

def _generateDefaultName(self) -> str:
def charsAsStr(s):
max_repr_len = 16
Expand Down Expand Up @@ -3031,6 +3056,9 @@ def __init__(
if self.asMatch:
self.parseImpl = self.parseImplAsMatch # type: ignore [assignment]

def __repr__(self) -> str:
return f"Regex({self.pattern!r})"

@cached_property
def re(self):
if self._re:
Expand Down Expand Up @@ -3263,6 +3291,11 @@ def __init__(
self.mayIndexError = False
self.mayReturnEmpty = True

def __repr__(self) -> str:
if self.quoteChar == self.endQuoteChar and isinstance(self.quoteChar, str_type):
return f"QuotedString({self.quoteChar!r})"
return f"QuotedString({self.quoteChar!r}, end_quote_char={self.endQuoteChar!r})"

def _generateDefaultName(self) -> str:
if self.quoteChar == self.endQuoteChar and isinstance(self.quoteChar, str_type):
return f"string enclosed in {self.quoteChar!r}"
Expand Down Expand Up @@ -3359,6 +3392,10 @@ def __init__(
self.mayReturnEmpty = self.minLen == 0
self.mayIndexError = False

def __repr__(self) -> str:
not_chars = "".join(sorted(self.notCharsSet))
return f"NotChars({not_chars!r})"

def _generateDefaultName(self) -> str:
not_chars_str = _collapse_string_to_ranges(self.notChars)
if len(not_chars_str) > 16:
Expand Down Expand Up @@ -3441,6 +3478,11 @@ def __init__(self, ws: str = " \t\r\n", min: int = 1, max: int = 0, exact: int =
self.maxLen = exact
self.minLen = exact

def __repr__(self) -> str:
if set(self.matchWhite) == set(" \t\r\n"):
return f"{type(self).__name__}()"
return f"{type(self).__name__}({self.matchWhite!r})"

def _generateDefaultName(self) -> str:
return "".join(White.whiteStrs[c] for c in self.matchWhite)

Expand Down Expand Up @@ -3476,6 +3518,9 @@ def __init__(self, colno: int):
super().__init__()
self.col = colno

def __repr__(self):
return f"{type(self).__name__}({self.col!r})"

def preParse(self, instring: str, loc: int) -> int:
if col(loc, instring) != self.col:
instrlen = len(instring)
Expand Down Expand Up @@ -3621,6 +3666,12 @@ def __init__(self, word_chars: str = printables, *, wordChars: str = printables)
self.wordChars = set(wordChars)
self.errmsg = "Not at the start of a word"

def __repr__(self):
if self.wordChars == set(printables):
return f"{type(self).__name__}()"
chars = "".join(sorted(self.wordChars))
return f"{type(self).__name__}({chars!r})"

def parseImpl(self, instring, loc, doActions=True):
if loc != 0:
if (
Expand All @@ -3647,6 +3698,12 @@ def __init__(self, word_chars: str = printables, *, wordChars: str = printables)
self.skipWhitespace = False
self.errmsg = "Not at the end of a word"

def __repr__(self):
if self.wordChars == set(printables):
return f"{type(self).__name__}()"
chars = "".join(sorted(self.wordChars))
return f"{type(self).__name__}({chars!r})"

def parseImpl(self, instring, loc, doActions=True):
instrlen = len(instring)
if instrlen > 0 and loc < instrlen:
Expand All @@ -3663,6 +3720,8 @@ class ParseExpression(ParserElement):
post-processing parsed tokens.
"""

OPERATOR: ClassVar[str] = ""

def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(savelist)
self.exprs: List[ParserElement]
Expand All @@ -3689,6 +3748,14 @@ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False
self.exprs = [exprs]
self.callPreparse = False

def __repr__(self):
if not self.exprs:
return f"{type(self).__name__}()"
if len(self.exprs) == 1:
return f"{type(self).__name__}([{self.exprs[0]!r}])"
op = f" {type(self).OPERATOR} "
return "(" + op.join(map(repr, self.exprs)) + ")"

def recurse(self) -> Sequence[ParserElement]:
return self.exprs[:]

Expand Down Expand Up @@ -3844,6 +3911,8 @@ class And(ParseExpression):
expr = integer("id") + name_expr("name") + integer("age")
"""

OPERATOR = "+"

class _ErrorStop(Empty):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -3887,6 +3956,23 @@ def __init__(
self.mayReturnEmpty = True
self.callPreparse = True

def __repr__(self):
if not self.exprs:
return f"{type(self).__name__}()"
if len(self.exprs) == 1:
return f"{type(self).__name__}([{self.exprs[0]!r}])"

builder = [repr(self.exprs[0])]
nextOp = "+"
for expr in self.exprs[1:]:
if isinstance(expr, And._ErrorStop):
nextOp = "-"
continue
builder.append(nextOp)
builder.append(repr(expr))
nextOp = "+"
return " ".join(builder)

def streamline(self) -> ParserElement:
# collapse any _PendingSkip's
if self.exprs:
Expand Down Expand Up @@ -3999,6 +4085,8 @@ class Or(ParseExpression):
[['123'], ['3.1416'], ['789']]
"""

OPERATOR = "^"

def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist)
if self.exprs:
Expand Down Expand Up @@ -4154,6 +4242,8 @@ class MatchFirst(ParseExpression):
print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']]
"""

OPERATOR = "|"

def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist)
if self.exprs:
Expand Down Expand Up @@ -4305,6 +4395,8 @@ class Each(ParseExpression):
- size: 20
"""

OPERATOR = "&"

def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True):
super().__init__(exprs, savelist)
if self.exprs:
Expand Down Expand Up @@ -4441,6 +4533,9 @@ def __init__(self, expr: Union[ParserElement, str], savelist: bool = False):
self.callPreparse = expr.callPreparse
self.ignoreExprs.extend(expr.ignoreExprs)

def __repr__(self):
return f"{type(self).__name__}({self.expr!r})"

def recurse(self) -> Sequence[ParserElement]:
return [self.expr] if self.expr is not None else []

Expand Down Expand Up @@ -4829,6 +4924,9 @@ def __init__(self, expr: Union[ParserElement, str]):
self.mayReturnEmpty = True
self.errmsg = "Found unwanted token, " + str(self.expr)

def __repr__(self):
return f"~{self.expr!r}"

def parseImpl(self, instring, loc, doActions=True):
if self.expr.can_parse_next(instring, loc):
raise ParseException(instring, loc, self.errmsg, self)
Expand Down Expand Up @@ -4943,6 +5041,9 @@ class OneOrMore(_MultipleMatch):
(attr_expr * (1,)).parse_string(text).pprint()
"""

def __repr__(self):
return f"{self.expr!r}[1,...]"

def _generateDefaultName(self) -> str:
return "{" + str(self.expr) + "}..."

Expand Down Expand Up @@ -4971,6 +5072,9 @@ def __init__(
super().__init__(expr, stopOn=stopOn or stop_on)
self.mayReturnEmpty = True

def __repr__(self):
return f"{self.expr!r}[...]"

def parseImpl(self, instring, loc, doActions=True):
try:
return super().parseImpl(instring, loc, doActions)
Expand Down Expand Up @@ -5284,6 +5388,11 @@ def __del__(self):
lineno=self.caller_frame.lineno,
)

def __repr__(self):
if self.expr is None:
return f"{type(self).__name__}()"
return f"{type(self).__name__}(...)"

def parseImpl(self, instring, loc, doActions=True):
if (
self.expr is None
Expand Down