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

114 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1# exceptions.py 

2 

3import re 

4import sys 

5import typing 

6 

7from .util import ( 

8 col, 

9 line, 

10 lineno, 

11 _collapse_string_to_ranges, 

12 replaced_by_pep8, 

13) 

14from .unicode import pyparsing_unicode as ppu 

15 

16 

17class ExceptionWordUnicode(ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic): 

18 pass 

19 

20 

21_extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums) 

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

23 

24 

25class ParseBaseException(Exception): 

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

27 

28 loc: int 

29 msg: str 

30 pstr: str 

31 parser_element: typing.Any # "ParserElement" 

32 args: typing.Tuple[str, int, typing.Optional[str]] 

33 

34 __slots__ = ( 

35 "loc", 

36 "msg", 

37 "pstr", 

38 "parser_element", 

39 "args", 

40 ) 

41 

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

43 # constructor as small and fast as possible 

44 def __init__( 

45 self, 

46 pstr: str, 

47 loc: int = 0, 

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

49 elem=None, 

50 ): 

51 self.loc = loc 

52 if msg is None: 

53 self.msg = pstr 

54 self.pstr = "" 

55 else: 

56 self.msg = msg 

57 self.pstr = pstr 

58 self.parser_element = elem 

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

60 

61 @staticmethod 

62 def explain_exception(exc, depth=16): 

63 """ 

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

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

66 

67 Parameters: 

68 

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

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

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

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

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

74 

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

76 exception's stack trace. 

77 """ 

78 import inspect 

79 from .core import ParserElement 

80 

81 if depth is None: 

82 depth = sys.getrecursionlimit() 

83 ret = [] 

84 if isinstance(exc, ParseBaseException): 

85 ret.append(exc.line) 

86 ret.append(" " * (exc.column - 1) + "^") 

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

88 

89 if depth > 0: 

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

91 seen = set() 

92 for i, ff in enumerate(callers[-depth:]): 

93 frm = ff[0] 

94 

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

96 if isinstance(f_self, ParserElement): 

97 if not frm.f_code.co_name.startswith( 

98 ("parseImpl", "_parseNoCache") 

99 ): 

100 continue 

101 if id(f_self) in seen: 

102 continue 

103 seen.add(id(f_self)) 

104 

105 self_type = type(f_self) 

106 ret.append( 

107 f"{self_type.__module__}.{self_type.__name__} - {f_self}" 

108 ) 

109 

110 elif f_self is not None: 

111 self_type = type(f_self) 

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

113 

114 else: 

115 code = frm.f_code 

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

117 continue 

118 

119 ret.append(code.co_name) 

120 

121 depth -= 1 

122 if not depth: 

123 break 

124 

125 return "\n".join(ret) 

126 

127 @classmethod 

128 def _from_exception(cls, pe): 

129 """ 

130 internal factory method to simplify creating one type of ParseException 

131 from another - avoids having __init__ signature conflicts among subclasses 

132 """ 

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

134 

135 @property 

136 def line(self) -> str: 

137 """ 

138 Return the line of text where the exception occurred. 

139 """ 

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

141 

142 @property 

143 def lineno(self) -> int: 

144 """ 

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

146 """ 

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

148 

149 @property 

150 def col(self) -> int: 

151 """ 

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

153 """ 

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

155 

156 @property 

157 def column(self) -> int: 

158 """ 

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

160 """ 

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

162 

163 # pre-PEP8 compatibility 

164 @property 

165 def parserElement(self): 

166 return self.parser_element 

167 

168 @parserElement.setter 

169 def parserElement(self, elem): 

170 self.parser_element = elem 

171 

172 def __str__(self) -> str: 

173 if self.pstr: 

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

175 foundstr = ", found end of text" 

176 else: 

177 # pull out next word at error location 

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

179 if found_match is not None: 

180 found = found_match.group(0) 

181 else: 

182 found = self.pstr[self.loc : self.loc + 1] 

183 foundstr = (", found %r" % found).replace(r"\\", "\\") 

184 else: 

185 foundstr = "" 

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

187 

188 def __repr__(self): 

189 return str(self) 

190 

191 def mark_input_line( 

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

193 ) -> str: 

194 """ 

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

196 the location of the exception with a special symbol. 

197 """ 

198 markerString = marker_string if marker_string is not None else markerString 

199 line_str = self.line 

200 line_column = self.column - 1 

201 if markerString: 

202 line_str = "".join( 

203 (line_str[:line_column], markerString, line_str[line_column:]) 

204 ) 

205 return line_str.strip() 

206 

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

208 """ 

209 Method to translate the Python internal traceback into a list 

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

211 

212 Parameters: 

213 

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

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

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

217 

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

219 exception's stack trace. 

220 

221 Example:: 

222 

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

224 try: 

225 expr.parse_string("123 456 A789") 

226 except pp.ParseException as pe: 

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

228 

229 prints:: 

230 

231 123 456 A789 

232 ^ 

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

234 

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

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

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

238 forms, which may be cryptic to read. 

239 

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

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

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

243 """ 

244 return self.explain_exception(self, depth) 

245 

246 # fmt: off 

247 @replaced_by_pep8(mark_input_line) 

248 def markInputline(self): ... 

249 # fmt: on 

250 

251 

252class ParseException(ParseBaseException): 

253 """ 

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

255 

256 Example:: 

257 

258 try: 

259 Word(nums).set_name("integer").parse_string("ABC") 

260 except ParseException as pe: 

261 print(pe) 

262 print("column: {}".format(pe.column)) 

263 

264 prints:: 

265 

266 Expected integer (at char 0), (line:1, col:1) 

267 column: 1 

268 

269 """ 

270 

271 

272class ParseFatalException(ParseBaseException): 

273 """ 

274 User-throwable exception thrown when inconsistent parse content 

275 is found; stops all parsing immediately 

276 """ 

277 

278 

279class ParseSyntaxException(ParseFatalException): 

280 """ 

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

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

283 that parsing is to stop immediately because an unbacktrackable 

284 syntax error has been found. 

285 """ 

286 

287 

288class RecursiveGrammarException(Exception): 

289 """ 

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

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

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

293 """ 

294 

295 def __init__(self, parseElementList): 

296 self.parseElementTrace = parseElementList 

297 

298 def __str__(self) -> str: 

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