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 found_phrase = f", found {self.found}" if self.found else "" 

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

197 

198 def __str__(self) -> str: 

199 return self.formatted_message() 

200 

201 def __repr__(self): 

202 return str(self) 

203 

204 def mark_input_line( 

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

206 ) -> str: 

207 """ 

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

209 the location of the exception with a special symbol. 

210 """ 

211 markerString = marker_string if marker_string is not None else markerString 

212 line_str = self.line 

213 line_column = self.column - 1 

214 if markerString: 

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

216 return line_str.strip() 

217 

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

219 """ 

220 Method to translate the Python internal traceback into a list 

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

222 

223 Parameters: 

224 

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

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

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

228 

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

230 exception's stack trace. 

231 

232 Example:: 

233 

234 # an expression to parse 3 integers 

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

236 try: 

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

238 expr.parse_string("123 456 A789") 

239 except pp.ParseException as pe: 

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

241 

242 prints:: 

243 

244 123 456 A789 

245 ^ 

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

247 

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

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

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

251 forms, which may be cryptic to read. 

252 

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

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

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

256 """ 

257 return self.explain_exception(self, depth) 

258 

259 # Compatibility synonyms 

260 # fmt: off 

261 markInputline = replaced_by_pep8("markInputline", mark_input_line) 

262 # fmt: on 

263 

264 

265class ParseException(ParseBaseException): 

266 """ 

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

268 

269 Example:: 

270 

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

272 try: 

273 integer.parse_string("ABC") 

274 except ParseException as pe: 

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

276 

277 prints:: 

278 

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

280 

281 """ 

282 

283 

284class ParseFatalException(ParseBaseException): 

285 """ 

286 User-throwable exception thrown when inconsistent parse content 

287 is found; stops all parsing immediately 

288 """ 

289 

290 

291class ParseSyntaxException(ParseFatalException): 

292 """ 

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

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

295 that parsing is to stop immediately because an unbacktrackable 

296 syntax error has been found. 

297 """ 

298 

299 

300class RecursiveGrammarException(Exception): 

301 """ 

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

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

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

305 

306 Deprecated: only used by deprecated method ParserElement.validate. 

307 """ 

308 

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

310 self.parseElementTrace = parseElementList 

311 

312 def __str__(self) -> str: 

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