Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pyparsing/exceptions.py: 45%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# exceptions.py
2from __future__ import annotations
4import copy
5import re
6import sys
7import typing
8import warnings
9from functools import cached_property
11from .warnings import PyparsingDeprecationWarning
12from .unicode import pyparsing_unicode as ppu
13from .util import (
14 _collapse_string_to_ranges,
15 col,
16 deprecate_argument,
17 line,
18 lineno,
19 replaced_by_pep8,
20)
23class _ExceptionWordUnicodeSet(
24 ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic
25):
26 pass
29_extract_alphanums = _collapse_string_to_ranges(_ExceptionWordUnicodeSet.alphanums)
30_exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.")
33class ParseBaseException(Exception):
34 """base exception class for all parsing runtime exceptions"""
36 loc: int
37 msg: str
38 pstr: str
39 parser_element: typing.Any # "ParserElement"
40 args: tuple[str, int, typing.Optional[str]]
42 __slots__ = (
43 "loc",
44 "msg",
45 "pstr",
46 "parser_element",
47 "args",
48 )
50 # Performance tuning: we construct a *lot* of these, so keep this
51 # constructor as small and fast as possible
52 def __init__(
53 self,
54 pstr: str,
55 loc: int = 0,
56 msg: typing.Optional[str] = None,
57 elem=None,
58 ) -> None:
59 if msg is None:
60 msg, pstr = pstr, ""
62 self.loc = loc
63 self.msg = msg
64 self.pstr = pstr
65 self.parser_element = elem
66 self.args = (pstr, loc, msg)
68 @staticmethod
69 def explain_exception(exc: Exception, depth: int = 16) -> str:
70 """
71 Method to take an exception and translate the Python internal traceback into a list
72 of the pyparsing expressions that caused the exception to be raised.
74 Parameters:
76 - exc - exception raised during parsing (need not be a ParseException, in support
77 of Python exceptions that might be raised in a parse action)
78 - depth (default=16) - number of levels back in the stack trace to list expression
79 and function names; if None, the full stack trace names will be listed; if 0, only
80 the failing input line, marker, and exception string will be shown
82 Returns a multi-line string listing the ParserElements and/or function names in the
83 exception's stack trace.
84 """
85 import inspect
86 from .core import ParserElement
88 if depth is None:
89 depth = sys.getrecursionlimit()
90 ret: list[str] = []
91 if isinstance(exc, ParseBaseException):
92 ret.append(exc.line)
93 ret.append(f"{'^':>{exc.column}}")
94 ret.append(f"{type(exc).__name__}: {exc}")
96 if depth <= 0 or exc.__traceback__ is None:
97 return "\n".join(ret)
99 callers = inspect.getinnerframes(exc.__traceback__, context=depth)
100 seen: set[int] = set()
101 for ff in callers[-depth:]:
102 frm = ff[0]
104 f_self = frm.f_locals.get("self", None)
105 if isinstance(f_self, ParserElement):
106 if not frm.f_code.co_name.startswith(("parseImpl", "_parseNoCache")):
107 continue
108 if id(f_self) in seen:
109 continue
110 seen.add(id(f_self))
112 self_type = type(f_self)
113 ret.append(f"{self_type.__module__}.{self_type.__name__} - {f_self}")
115 elif f_self is not None:
116 self_type = type(f_self)
117 ret.append(f"{self_type.__module__}.{self_type.__name__}")
119 else:
120 code = frm.f_code
121 if code.co_name in ("wrapper", "<module>"):
122 continue
124 ret.append(code.co_name)
126 depth -= 1
127 if not depth:
128 break
130 return "\n".join(ret)
132 @classmethod
133 def _from_exception(cls, pe) -> ParseBaseException:
134 """
135 internal factory method to simplify creating one type of ParseException
136 from another - avoids having __init__ signature conflicts among subclasses
137 """
138 return cls(pe.pstr, pe.loc, pe.msg, pe.parser_element)
140 @cached_property
141 def line(self) -> str:
142 """
143 Return the line of text where the exception occurred.
144 """
145 return line(self.loc, self.pstr)
147 @cached_property
148 def lineno(self) -> int:
149 """
150 Return the 1-based line number of text where the exception occurred.
151 """
152 return lineno(self.loc, self.pstr)
154 @cached_property
155 def col(self) -> int:
156 """
157 Return the 1-based column on the line of text where the exception occurred.
158 """
159 return col(self.loc, self.pstr)
161 @cached_property
162 def column(self) -> int:
163 """
164 Return the 1-based column on the line of text where the exception occurred.
165 """
166 return col(self.loc, self.pstr)
168 @cached_property
169 def found(self) -> str:
170 if not self.pstr:
171 return ""
173 if self.loc >= len(self.pstr):
174 return "end of text"
176 # pull out next word at error location
177 found_match = _exception_word_extractor.match(self.pstr, self.loc)
178 if found_match is not None:
179 found_text = found_match[0]
180 else:
181 found_text = self.pstr[self.loc : self.loc + 1]
183 return repr(found_text).replace(r"\\", "\\")
185 # pre-PEP8 compatibility
186 @property
187 def parserElement(self):
188 warnings.warn(
189 "parserElement is deprecated, use parser_element",
190 PyparsingDeprecationWarning,
191 stacklevel=2,
192 )
193 return self.parser_element
195 @parserElement.setter
196 def parserElement(self, elem):
197 warnings.warn(
198 "parserElement is deprecated, use parser_element",
199 PyparsingDeprecationWarning,
200 stacklevel=2,
201 )
202 self.parser_element = elem
204 def copy(self):
205 return copy.copy(self)
207 def formatted_message(self) -> str:
208 """
209 Output the formatted exception message.
210 Can be overridden to customize the message formatting or contents.
212 .. versionadded:: 3.2.0
213 """
214 found_phrase = f", found {self.found}" if self.found else ""
215 return f"{self.msg}{found_phrase} (at char {self.loc}), (line:{self.lineno}, col:{self.column})"
217 def __str__(self) -> str:
218 """
219 .. versionchanged:: 3.2.0
220 Now uses :meth:`formatted_message` to format message.
221 """
222 try:
223 return self.formatted_message()
224 except Exception as ex:
225 return (
226 f"{type(self).__name__}: {self.msg}"
227 f" ({type(ex).__name__}: {ex} while formatting message)"
228 )
230 def __repr__(self):
231 return str(self)
233 def mark_input_line(
234 self, marker_string: typing.Optional[str] = None, **kwargs
235 ) -> str:
236 """
237 Extracts the exception line from the input string, and marks
238 the location of the exception with a special symbol.
239 """
240 markerString: str = deprecate_argument(kwargs, "markerString", ">!<")
242 markerString = marker_string if marker_string is not None else markerString
243 line_str = self.line
244 line_column = self.column - 1
245 if markerString:
246 line_str = f"{line_str[:line_column]}{markerString}{line_str[line_column:]}"
247 return line_str.strip()
249 def explain(self, depth: int = 16) -> str:
250 """
251 Method to translate the Python internal traceback into a list
252 of the pyparsing expressions that caused the exception to be raised.
254 Parameters:
256 - depth (default=16) - number of levels back in the stack trace to list expression
257 and function names; if None, the full stack trace names will be listed; if 0, only
258 the failing input line, marker, and exception string will be shown
260 Returns a multi-line string listing the ParserElements and/or function names in the
261 exception's stack trace.
263 Example:
265 .. testcode::
267 # an expression to parse 3 integers
268 expr = pp.Word(pp.nums) * 3
269 try:
270 # a failing parse - the third integer is prefixed with "A"
271 expr.parse_string("123 456 A789")
272 except pp.ParseException as pe:
273 print(pe.explain(depth=0))
275 prints:
277 .. testoutput::
279 123 456 A789
280 ^
281 ParseException: Expected W:(0-9), found 'A789' (at char 8), (line:1, col:9)
283 Note: the diagnostic output will include string representations of the expressions
284 that failed to parse. These representations will be more helpful if you use `set_name` to
285 give identifiable names to your expressions. Otherwise they will use the default string
286 forms, which may be cryptic to read.
288 Note: pyparsing's default truncation of exception tracebacks may also truncate the
289 stack of expressions that are displayed in the ``explain`` output. To get the full listing
290 of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True``
291 """
292 return self.explain_exception(self, depth)
294 # Compatibility synonyms
295 # fmt: off
296 markInputline = replaced_by_pep8("markInputline", mark_input_line)
297 # fmt: on
300class ParseException(ParseBaseException):
301 """
302 Exception thrown when a parse expression doesn't match the input string
304 Example:
306 .. testcode::
308 integer = Word(nums).set_name("integer")
309 try:
310 integer.parse_string("ABC")
311 except ParseException as pe:
312 print(pe, f"column: {pe.column}")
314 prints:
316 .. testoutput::
318 Expected integer, found 'ABC' (at char 0), (line:1, col:1) column: 1
320 """
323class ParseFatalException(ParseBaseException):
324 """
325 User-throwable exception thrown when inconsistent parse content
326 is found; stops all parsing immediately
327 """
330class ParseSyntaxException(ParseFatalException):
331 """
332 Just like :class:`ParseFatalException`, but thrown internally
333 when an :class:`ErrorStop<And._ErrorStop>` ('-' operator) indicates
334 that parsing is to stop immediately because an unbacktrackable
335 syntax error has been found.
336 """
339class RecursiveGrammarException(Exception):
340 """
341 .. deprecated:: 3.0.0
342 Only used by the deprecated :meth:`ParserElement.validate`.
344 Exception thrown by :class:`ParserElement.validate` if the
345 grammar could be left-recursive; parser may need to enable
346 left recursion using :class:`ParserElement.enable_left_recursion<ParserElement.enable_left_recursion>`
347 """
349 def __init__(self, parseElementList) -> None:
350 self.parseElementTrace = parseElementList
352 def __str__(self) -> str:
353 return f"RecursiveGrammarException: {self.parseElementTrace}"