Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pyparsing/exceptions.py: 46%

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

124 statements  

1# exceptions.py 

2from __future__ import annotations 

3 

4import copy 

5import re 

6import sys 

7import typing 

8from functools import cached_property 

9 

10from .unicode import pyparsing_unicode as ppu 

11from .util import ( 

12 _collapse_string_to_ranges, 

13 col, 

14 line, 

15 lineno, 

16 replaced_by_pep8, 

17) 

18 

19 

20class _ExceptionWordUnicodeSet( 

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

22): 

23 pass 

24 

25 

26_extract_alphanums = _collapse_string_to_ranges(_ExceptionWordUnicodeSet.alphanums) 

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

28 

29 

30class ParseBaseException(Exception): 

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

32 

33 loc: int 

34 msg: str 

35 pstr: str 

36 parser_element: typing.Any # "ParserElement" 

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

38 

39 __slots__ = ( 

40 "loc", 

41 "msg", 

42 "pstr", 

43 "parser_element", 

44 "args", 

45 ) 

46 

47 # Performance tuning: we construct a *lot* of these, so keep this 

48 # constructor as small and fast as possible 

49 def __init__( 

50 self, 

51 pstr: str, 

52 loc: int = 0, 

53 msg: typing.Optional[str] = None, 

54 elem=None, 

55 ) -> None: 

56 if msg is None: 

57 msg, pstr = pstr, "" 

58 

59 self.loc = loc 

60 self.msg = msg 

61 self.pstr = pstr 

62 self.parser_element = elem 

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

64 

65 @staticmethod 

66 def explain_exception(exc: Exception, depth: int = 16) -> str: 

67 """ 

68 Method to take an exception and translate the Python internal traceback into a list 

69 of the pyparsing expressions that caused the exception to be raised. 

70 

71 Parameters: 

72 

73 - exc - exception raised during parsing (need not be a ParseException, in support 

74 of Python exceptions that might be raised in a parse action) 

75 - depth (default=16) - number of levels back in the stack trace to list expression 

76 and function names; if None, the full stack trace names will be listed; if 0, only 

77 the failing input line, marker, and exception string will be shown 

78 

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

80 exception's stack trace. 

81 """ 

82 import inspect 

83 from .core import ParserElement 

84 

85 if depth is None: 

86 depth = sys.getrecursionlimit() 

87 ret: list[str] = [] 

88 if isinstance(exc, ParseBaseException): 

89 ret.append(exc.line) 

90 ret.append(f"{'^':>{exc.column}}") 

91 ret.append(f"{type(exc).__name__}: {exc}") 

92 

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

94 return "\n".join(ret) 

95 

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

97 seen: set[int] = set() 

98 for ff in callers[-depth:]: 

99 frm = ff[0] 

100 

101 f_self = frm.f_locals.get("self", None) 

102 if isinstance(f_self, ParserElement): 

103 if not frm.f_code.co_name.startswith(("parseImpl", "_parseNoCache")): 

104 continue 

105 if id(f_self) in seen: 

106 continue 

107 seen.add(id(f_self)) 

108 

109 self_type = type(f_self) 

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

111 

112 elif f_self is not None: 

113 self_type = type(f_self) 

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

115 

116 else: 

117 code = frm.f_code 

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

119 continue 

120 

121 ret.append(code.co_name) 

122 

123 depth -= 1 

124 if not depth: 

125 break 

126 

127 return "\n".join(ret) 

128 

129 @classmethod 

130 def _from_exception(cls, pe) -> ParseBaseException: 

131 """ 

132 internal factory method to simplify creating one type of ParseException 

133 from another - avoids having __init__ signature conflicts among subclasses 

134 """ 

135 return cls(pe.pstr, pe.loc, pe.msg, pe.parser_element) 

136 

137 @cached_property 

138 def line(self) -> str: 

139 """ 

140 Return the line of text where the exception occurred. 

141 """ 

142 return line(self.loc, self.pstr) 

143 

144 @cached_property 

145 def lineno(self) -> int: 

146 """ 

147 Return the 1-based line number of text where the exception occurred. 

148 """ 

149 return lineno(self.loc, self.pstr) 

150 

151 @cached_property 

152 def col(self) -> int: 

153 """ 

154 Return the 1-based column on the line of text where the exception occurred. 

155 """ 

156 return col(self.loc, self.pstr) 

157 

158 @cached_property 

159 def column(self) -> int: 

160 """ 

161 Return the 1-based column on the line of text where the exception occurred. 

162 """ 

163 return col(self.loc, self.pstr) 

164 

165 @cached_property 

166 def found(self) -> str: 

167 if not self.pstr: 

168 return "" 

169 

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

171 return "end of text" 

172 

173 # pull out next word at error location 

174 found_match = _exception_word_extractor.match(self.pstr, self.loc) 

175 if found_match is not None: 

176 found_text = found_match.group(0) 

177 else: 

178 found_text = self.pstr[self.loc : self.loc + 1] 

179 

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

181 

182 # pre-PEP8 compatibility 

183 @property 

184 def parserElement(self): 

185 return self.parser_element 

186 

187 @parserElement.setter 

188 def parserElement(self, elem): 

189 self.parser_element = elem 

190 

191 def copy(self): 

192 return copy.copy(self) 

193 

194 def formatted_message(self) -> str: 

195 """ 

196 Output the formatted exception message. 

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

198 

199 .. versionadded:: 3.2.0 

200 """ 

201 found_phrase = f", found {self.found}" if self.found else "" 

202 return f"{self.msg}{found_phrase} (at char {self.loc}), (line:{self.lineno}, col:{self.column})" 

203 

204 def __str__(self) -> str: 

205 """ 

206 .. versionchanged:: 3.2.0 

207 Now uses :meth:`formatted_message` to format message. 

208 """ 

209 return self.formatted_message() 

210 

211 def __repr__(self): 

212 return str(self) 

213 

214 def mark_input_line( 

215 self, marker_string: typing.Optional[str] = None, *, markerString: str = ">!<" 

216 ) -> str: 

217 """ 

218 Extracts the exception line from the input string, and marks 

219 the location of the exception with a special symbol. 

220 """ 

221 markerString = marker_string if marker_string is not None else markerString 

222 line_str = self.line 

223 line_column = self.column - 1 

224 if markerString: 

225 line_str = f"{line_str[:line_column]}{markerString}{line_str[line_column:]}" 

226 return line_str.strip() 

227 

228 def explain(self, depth: int = 16) -> str: 

229 """ 

230 Method to translate the Python internal traceback into a list 

231 of the pyparsing expressions that caused the exception to be raised. 

232 

233 Parameters: 

234 

235 - depth (default=16) - number of levels back in the stack trace to list expression 

236 and function names; if None, the full stack trace names will be listed; if 0, only 

237 the failing input line, marker, and exception string will be shown 

238 

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

240 exception's stack trace. 

241 

242 Example:: 

243 

244 # an expression to parse 3 integers 

245 expr = pp.Word(pp.nums) * 3 

246 try: 

247 # a failing parse - the third integer is prefixed with "A" 

248 expr.parse_string("123 456 A789") 

249 except pp.ParseException as pe: 

250 print(pe.explain(depth=0)) 

251 

252 prints:: 

253 

254 123 456 A789 

255 ^ 

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

257 

258 Note: the diagnostic output will include string representations of the expressions 

259 that failed to parse. These representations will be more helpful if you use `set_name` to 

260 give identifiable names to your expressions. Otherwise they will use the default string 

261 forms, which may be cryptic to read. 

262 

263 Note: pyparsing's default truncation of exception tracebacks may also truncate the 

264 stack of expressions that are displayed in the ``explain`` output. To get the full listing 

265 of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True`` 

266 """ 

267 return self.explain_exception(self, depth) 

268 

269 # Compatibility synonyms 

270 # fmt: off 

271 markInputline = replaced_by_pep8("markInputline", mark_input_line) 

272 # fmt: on 

273 

274 

275class ParseException(ParseBaseException): 

276 """ 

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

278 

279 Example:: 

280 

281 integer = Word(nums).set_name("integer") 

282 try: 

283 integer.parse_string("ABC") 

284 except ParseException as pe: 

285 print(pe, f"column: {pe.column}") 

286 

287 prints:: 

288 

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

290 

291 """ 

292 

293 

294class ParseFatalException(ParseBaseException): 

295 """ 

296 User-throwable exception thrown when inconsistent parse content 

297 is found; stops all parsing immediately 

298 """ 

299 

300 

301class ParseSyntaxException(ParseFatalException): 

302 """ 

303 Just like :class:`ParseFatalException`, but thrown internally 

304 when an :class:`ErrorStop<And._ErrorStop>` ('-' operator) indicates 

305 that parsing is to stop immediately because an unbacktrackable 

306 syntax error has been found. 

307 """ 

308 

309 

310class RecursiveGrammarException(Exception): 

311 """ 

312 .. deprecated:: 3.0.0 

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

314 

315 Exception thrown by :class:`ParserElement.validate` if the 

316 grammar could be left-recursive; parser may need to enable 

317 left recursion using :class:`ParserElement.enable_left_recursion<ParserElement.enable_left_recursion>` 

318 """ 

319 

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

321 self.parseElementTrace = parseElementList 

322 

323 def __str__(self) -> str: 

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