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

344 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 # _error.code is better than _error.status
185 self.properties['code'] = num.ToBig(self.ExitStatus())
186 self.properties['message'] = value.Str(self.msg)
187
188 return value.Dict(self.properties)
189
190
191class AssertionErr(Expr):
192 """An assertion."""
193
194 def __init__(self, msg, location):
195 # type: (str, loc_t) -> None
196 Expr.__init__(self, msg, location)
197
198
199class TypeErrVerbose(Expr):
200 """e.g. ~ on a bool or float, 'not' on an int."""
201
202 def __init__(self, msg, location):
203 # type: (str, loc_t) -> None
204 Expr.__init__(self, msg, location)
205
206
207class TypeErr(TypeErrVerbose):
208
209 def __init__(self, actual_val, msg, location):
210 # type: (value_t, str, loc_t) -> None
211 TypeErrVerbose.__init__(self,
212 "%s, got %s" % (msg, _ValType(actual_val)),
213 location)
214
215
216class Runtime(Exception):
217 """An error that's meant to be caught, i.e. it's non-fatal.
218
219 Thrown by core/state.py and caught by builtins
220 """
221
222 def __init__(self, msg):
223 # type: (str) -> None
224 self.msg = msg
225
226 def UserErrorString(self):
227 # type: () -> str
228 return self.msg
229
230
231class Decode(Exception):
232 """
233 List of J8 errors:
234 - message isn't UTF-8 - Id.Lit_Chars - need loc
235 - Invalid token Id.Unkown_Tok - need loc
236 - Unclosed double quote string -- need loc
237 - Parse error, e.g. [}{]
238
239 - Invalid escapes:
240 - b"" and u"" don't accept \\u1234
241 - u"" doesn't accept \\yff
242 - "" doesn't accept \\yff or \\u{123456}
243 """
244
245 def __init__(self, msg, s, start_pos, end_pos, line_num):
246 # type: (str, str, int, int, int) -> None
247 self.msg = msg
248 self.s = s # string being decoded
249 self.start_pos = start_pos
250 self.end_pos = end_pos
251 self.line_num = line_num
252
253 def Message(self):
254 # type: () -> str
255
256 # Show 10 chars of context for now
257 start = max(0, self.start_pos - 4)
258 end = min(len(self.s), self.end_pos + 4)
259
260 part = self.s[start:end]
261 return self.msg + ' (line %d, offset %d-%d: %r)' % (
262 self.line_num, self.start_pos, self.end_pos, part)
263
264 def __str__(self):
265 # type: () -> str
266 return self.Message()
267
268
269class Encode(Exception):
270 """
271 List of J8 encode errors:
272 - object cycle
273 - unprintable object like Eggex
274 When encoding JSON:
275 - binary data that can't be represented in JSON
276 - if using Unicode replacement char, then it won't fail
277 """
278
279 def __init__(self, msg):
280 # type: (str) -> None
281 self.msg = msg
282
283 def Message(self):
284 # type: () -> str
285 return self.msg
286
287
288def e_usage(msg, location):
289 # type: (str, loc_t) -> NoReturn
290 """Convenience wrapper for arg parsing / validation errors.
291
292 Usually causes a builtin to fail with status 2, but the script can continue
293 if 'set +o errexit'. Main programs like bin/oil also use this.
294
295 Caught by
296
297 - RunAssignBuiltin and RunBuiltin, with optional LOCATION INFO
298 - various main() programs, without location info
299
300 Probably should separate these two cases?
301
302 - builtins pass Token() or loc::Missing()
303 - tool interfaces don't pass any location info
304 """
305 raise Usage(msg, location)
306
307
308def e_strict(msg, location):
309 # type: (str, loc_t) -> NoReturn
310 """Convenience wrapper for strictness errors.
311
312 Like e_die(), except the script MAY continue executing after these errors.
313
314 TODO: This could have a level too?
315 """
316 raise Strict(msg, location)
317
318
319def p_die(msg, location):
320 # type: (str, loc_t) -> NoReturn
321 """Convenience wrapper for parse errors.
322
323 Exits with status 2. See core/main_loop.py.
324 """
325 raise Parse(msg, location)
326
327
328def e_die(msg, location=None):
329 # type: (str, loc_t) -> NoReturn
330 """Convenience wrapper for fatal runtime errors.
331
332 Usually exits with status 1. See osh/cmd_eval.py.
333 """
334 raise FatalRuntime(1, msg, location)
335
336
337def e_die_status(status, msg, location=None):
338 # type: (int, str, loc_t) -> NoReturn
339 """Wrapper for C++ semantics.
340
341 Note that it doesn't take positional args, so you should use %
342 formatting.
343 """
344 raise FatalRuntime(status, msg, location)