OILS / core / error.py View on Github | oilshell.org

343 lines, 106 significant
1""" core/error.py """
2from __future__ import print_function
3
4from _devbuild.gen.syntax_asdl import loc_e, loc_t, loc
5from _devbuild.gen.value_asdl import (value, value_t, value_str)
6from core import num
7
8from typing import Dict, Union, NoReturn, TYPE_CHECKING
9
10# For storing errors in List[T]
11if TYPE_CHECKING:
12 IOError_OSError = Union[IOError, OSError]
13
14
15def _ValType(val):
16 # type: (value_t) -> str
17 """Duplicate ui.ValType for now"""
18 return value_str(val.tag(), dot=False)
19
20
21class _ErrorWithLocation(Exception):
22 """A parse error that can be formatted.
23
24 Formatting is in ui.PrintError.
25 """
26
27 def __init__(self, msg, location):
28 # type: (str, loc_t) -> None
29
30 self.msg = msg
31
32 # Ensure that the location field is always populated
33 if location is None:
34 self.location = loc.Missing # type: loc_t
35 else:
36 self.location = location
37
38 def HasLocation(self):
39 # type: () -> bool
40 return self.location.tag() != loc_e.Missing
41
42 def UserErrorString(self):
43 # type: () -> str
44 return self.msg
45
46 def __repr__(self):
47 # type: () -> str
48 return '<%s %r>' % (self.msg, self.location)
49
50
51class Usage(_ErrorWithLocation):
52 """For flag parsing errors in builtins and main()
53
54 Called by e_usage(). TODO: Should settle on a single interface that
55 can be translated. Sometimes we use 'raise error.Usage()'
56 """
57
58 def __init__(self, msg, location):
59 # type: (str, loc_t) -> None
60 _ErrorWithLocation.__init__(self, msg, location)
61
62
63class Parse(_ErrorWithLocation):
64 """Used in the parsers."""
65
66 def __init__(self, msg, location):
67 # type: (str, loc_t) -> None
68 _ErrorWithLocation.__init__(self, msg, location)
69
70
71class FailGlob(_ErrorWithLocation):
72 """Raised when a glob matches nothing when failglob is set.
73
74 Meant to be caught.
75 """
76
77 def __init__(self, msg, location):
78 # type: (str, loc_t) -> None
79 _ErrorWithLocation.__init__(self, msg, location)
80
81
82class RedirectEval(_ErrorWithLocation):
83 """Used in the CommandEvaluator.
84
85 A bad redirect causes the SimpleCommand to return with status 1. To
86 make it fatal, use set -o errexit.
87 """
88
89 def __init__(self, msg, location):
90 # type: (str, loc_t) -> None
91 _ErrorWithLocation.__init__(self, msg, location)
92
93
94class FatalRuntime(_ErrorWithLocation):
95 """An exception that propagates to the top level.
96
97 Used in the evaluators, and also also used in test builtin for
98 invalid argument.
99 """
100
101 def __init__(self, exit_status, msg, location):
102 # type: (int, str, loc_t) -> None
103 _ErrorWithLocation.__init__(self, msg, location)
104 self.exit_status = exit_status
105
106 def ExitStatus(self):
107 # type: () -> int
108 return self.exit_status
109
110
111class Strict(FatalRuntime):
112 """Depending on shell options, these errors may be caught and ignored.
113
114 For example, if options like these are ON:
115
116 set -o strict_arith
117 set -o strict_word_eval
118
119 then we re-raise the error so it's caught by the top level. Otherwise
120 we catch it and return a dummy value like '' or -1 (i.e. what bash commonly
121 does.)
122
123 TODO: Have levels, like:
124
125 OILS_STRICT_PRINT=2 # print warnings at level 2 and above
126 OILS_STRICT_DIE=1 # abort the program at level 1 and above
127 """
128
129 def __init__(self, msg, location):
130 # type: (str, loc_t) -> None
131 FatalRuntime.__init__(self, 1, msg, location)
132
133
134class ErrExit(FatalRuntime):
135 """For set -e.
136
137 Travels between WordEvaluator and CommandEvaluator.
138 """
139
140 def __init__(self, exit_status, msg, location, show_code=False):
141 # type: (int, str, loc_t, bool) -> None
142 FatalRuntime.__init__(self, exit_status, msg, location)
143 self.show_code = show_code
144
145
146class Expr(FatalRuntime):
147 """e.g. KeyError, IndexError, ZeroDivisionError."""
148
149 def __init__(self, msg, location):
150 # type: (str, loc_t) -> None
151
152 # Unique status of 3 for expression errors -- for both the caught and
153 # uncaught case.
154 #
155 # Caught: try sets _status register to 3
156 # Uncaught: shell exits with status 3
157 FatalRuntime.__init__(self, 3, msg, location)
158
159
160class Structured(FatalRuntime):
161 """An error that can be exposed via the _error Dict.
162
163 Including:
164 - Errors raised by the 'error' builtin
165 - J8 encode and decode errors.
166 """
167
168 def __init__(self, status, msg, location, properties=None):
169 # type: (int, str, loc_t, Dict[str, value_t]) -> None
170 FatalRuntime.__init__(self, status, msg, location)
171 self.properties = properties
172
173 def ToDict(self):
174 # type: () -> value.Dict
175
176 if self.properties is None:
177 self.properties = {}
178
179 # Override status and message.
180 # The _error Dict order is a bit quirky -- the optional properties come
181 # before these required fields. But we always want the required fields
182 # to take precedence, so it makes sense.
183
184 self.properties['status'] = num.ToBig(self.ExitStatus())
185 self.properties['message'] = value.Str(self.msg)
186
187 return value.Dict(self.properties)
188
189
190class AssertionErr(Expr):
191 """An assertion."""
192
193 def __init__(self, msg, location):
194 # type: (str, loc_t) -> None
195 Expr.__init__(self, msg, location)
196
197
198class TypeErrVerbose(Expr):
199 """e.g. ~ on a bool or float, 'not' on an int."""
200
201 def __init__(self, msg, location):
202 # type: (str, loc_t) -> None
203 Expr.__init__(self, msg, location)
204
205
206class TypeErr(TypeErrVerbose):
207
208 def __init__(self, actual_val, msg, location):
209 # type: (value_t, str, loc_t) -> None
210 TypeErrVerbose.__init__(self,
211 "%s, got %s" % (msg, _ValType(actual_val)),
212 location)
213
214
215class Runtime(Exception):
216 """An error that's meant to be caught, i.e. it's non-fatal.
217
218 Thrown by core/state.py and caught by builtins
219 """
220
221 def __init__(self, msg):
222 # type: (str) -> None
223 self.msg = msg
224
225 def UserErrorString(self):
226 # type: () -> str
227 return self.msg
228
229
230class Decode(Exception):
231 """
232 List of J8 errors:
233 - message isn't UTF-8 - Id.Lit_Chars - need loc
234 - Invalid token Id.Unkown_Tok - need loc
235 - Unclosed double quote string -- need loc
236 - Parse error, e.g. [}{]
237
238 - Invalid escapes:
239 - b"" and u"" don't accept \\u1234
240 - u"" doesn't accept \\yff
241 - "" doesn't accept \\yff or \\u{123456}
242 """
243
244 def __init__(self, msg, s, start_pos, end_pos, line_num):
245 # type: (str, str, int, int, int) -> None
246 self.msg = msg
247 self.s = s # string being decoded
248 self.start_pos = start_pos
249 self.end_pos = end_pos
250 self.line_num = line_num
251
252 def Message(self):
253 # type: () -> str
254
255 # Show 10 chars of context for now
256 start = max(0, self.start_pos - 4)
257 end = min(len(self.s), self.end_pos + 4)
258
259 part = self.s[start:end]
260 return self.msg + ' (line %d, offset %d-%d: %r)' % (
261 self.line_num, self.start_pos, self.end_pos, part)
262
263 def __str__(self):
264 # type: () -> str
265 return self.Message()
266
267
268class Encode(Exception):
269 """
270 List of J8 encode errors:
271 - object cycle
272 - unprintable object like Eggex
273 When encoding JSON:
274 - binary data that can't be represented in JSON
275 - if using Unicode replacement char, then it won't fail
276 """
277
278 def __init__(self, msg):
279 # type: (str) -> None
280 self.msg = msg
281
282 def Message(self):
283 # type: () -> str
284 return self.msg
285
286
287def e_usage(msg, location):
288 # type: (str, loc_t) -> NoReturn
289 """Convenience wrapper for arg parsing / validation errors.
290
291 Usually causes a builtin to fail with status 2, but the script can continue
292 if 'set +o errexit'. Main programs like bin/oil also use this.
293
294 Caught by
295
296 - RunAssignBuiltin and RunBuiltin, with optional LOCATION INFO
297 - various main() programs, without location info
298
299 Probably should separate these two cases?
300
301 - builtins pass Token() or loc::Missing()
302 - tool interfaces don't pass any location info
303 """
304 raise Usage(msg, location)
305
306
307def e_strict(msg, location):
308 # type: (str, loc_t) -> NoReturn
309 """Convenience wrapper for strictness errors.
310
311 Like e_die(), except the script MAY continue executing after these errors.
312
313 TODO: This could have a level too?
314 """
315 raise Strict(msg, location)
316
317
318def p_die(msg, location):
319 # type: (str, loc_t) -> NoReturn
320 """Convenience wrapper for parse errors.
321
322 Exits with status 2. See core/main_loop.py.
323 """
324 raise Parse(msg, location)
325
326
327def e_die(msg, location=None):
328 # type: (str, loc_t) -> NoReturn
329 """Convenience wrapper for fatal runtime errors.
330
331 Usually exits with status 1. See osh/cmd_eval.py.
332 """
333 raise FatalRuntime(1, msg, location)
334
335
336def e_die_status(status, msg, location=None):
337 # type: (int, str, loc_t) -> NoReturn
338 """Wrapper for C++ semantics.
339
340 Note that it doesn't take positional args, so you should use %
341 formatting.
342 """
343 raise FatalRuntime(status, msg, location)