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

132 statements  

1# exceptions.py 

2from __future__ import annotations 

3 

4import copy 

5import re 

6import sys 

7import typing 

8import warnings 

9from functools import cached_property 

10 

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) 

21 

22 

23class _ExceptionWordUnicodeSet( 

24 ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic 

25): 

26 pass 

27 

28 

29_extract_alphanums = _collapse_string_to_ranges(_ExceptionWordUnicodeSet.alphanums) 

30_exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.") 

31 

32 

33class ParseBaseException(Exception): 

34 """base exception class for all parsing runtime exceptions""" 

35 

36 loc: int 

37 msg: str 

38 pstr: str 

39 parser_element: typing.Any # "ParserElement" 

40 args: tuple[str, int, typing.Optional[str]] 

41 

42 __slots__ = ( 

43 "loc", 

44 "msg", 

45 "pstr", 

46 "parser_element", 

47 "args", 

48 ) 

49 

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, "" 

61 

62 self.loc = loc 

63 self.msg = msg 

64 self.pstr = pstr 

65 self.parser_element = elem 

66 self.args = (pstr, loc, msg) 

67 

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. 

73 

74 Parameters: 

75 

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 

81 

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 

87 

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}") 

95 

96 if depth <= 0 or exc.__traceback__ is None: 

97 return "\n".join(ret) 

98 

99 callers = inspect.getinnerframes(exc.__traceback__, context=depth) 

100 seen: set[int] = set() 

101 for ff in callers[-depth:]: 

102 frm = ff[0] 

103 

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)) 

111 

112 self_type = type(f_self) 

113 ret.append(f"{self_type.__module__}.{self_type.__name__} - {f_self}") 

114 

115 elif f_self is not None: 

116 self_type = type(f_self) 

117 ret.append(f"{self_type.__module__}.{self_type.__name__}") 

118 

119 else: 

120 code = frm.f_code 

121 if code.co_name in ("wrapper", "<module>"): 

122 continue 

123 

124 ret.append(code.co_name) 

125 

126 depth -= 1 

127 if not depth: 

128 break 

129 

130 return "\n".join(ret) 

131 

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) 

139 

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) 

146 

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) 

153 

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) 

160 

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) 

167 

168 @cached_property 

169 def found(self) -> str: 

170 if not self.pstr: 

171 return "" 

172 

173 if self.loc >= len(self.pstr): 

174 return "end of text" 

175 

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] 

182 

183 return repr(found_text).replace(r"\\", "\\") 

184 

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 

194 

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 

203 

204 def copy(self): 

205 return copy.copy(self) 

206 

207 def formatted_message(self) -> str: 

208 """ 

209 Output the formatted exception message. 

210 Can be overridden to customize the message formatting or contents. 

211 

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})" 

216 

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 ) 

229 

230 def __repr__(self): 

231 return str(self) 

232 

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", ">!<") 

241 

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() 

248 

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. 

253 

254 Parameters: 

255 

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 

259 

260 Returns a multi-line string listing the ParserElements and/or function names in the 

261 exception's stack trace. 

262 

263 Example: 

264 

265 .. testcode:: 

266 

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)) 

274 

275 prints: 

276 

277 .. testoutput:: 

278 

279 123 456 A789 

280 ^ 

281 ParseException: Expected W:(0-9), found 'A789' (at char 8), (line:1, col:9) 

282 

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. 

287 

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) 

293 

294 # Compatibility synonyms 

295 # fmt: off 

296 markInputline = replaced_by_pep8("markInputline", mark_input_line) 

297 # fmt: on 

298 

299 

300class ParseException(ParseBaseException): 

301 """ 

302 Exception thrown when a parse expression doesn't match the input string 

303 

304 Example: 

305 

306 .. testcode:: 

307 

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}") 

313 

314 prints: 

315 

316 .. testoutput:: 

317 

318 Expected integer, found 'ABC' (at char 0), (line:1, col:1) column: 1 

319 

320 """ 

321 

322 

323class ParseFatalException(ParseBaseException): 

324 """ 

325 User-throwable exception thrown when inconsistent parse content 

326 is found; stops all parsing immediately 

327 """ 

328 

329 

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 """ 

337 

338 

339class RecursiveGrammarException(Exception): 

340 """ 

341 .. deprecated:: 3.0.0 

342 Only used by the deprecated :meth:`ParserElement.validate`. 

343 

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 """ 

348 

349 def __init__(self, parseElementList) -> None: 

350 self.parseElementTrace = parseElementList 

351 

352 def __str__(self) -> str: 

353 return f"RecursiveGrammarException: {self.parseElementTrace}"