OILS / builtin / error_ysh.py View on Github | oilshell.org

310 lines, 175 significant
1from __future__ import print_function
2
3from _devbuild.gen.option_asdl import option_i
4from _devbuild.gen.id_kind_asdl import Id
5from _devbuild.gen.runtime_asdl import cmd_value, CommandStatus
6from _devbuild.gen.syntax_asdl import loc, loc_t, expr, expr_e
7from _devbuild.gen.value_asdl import value, value_e
8from core import error
9from core.error import e_die_status, e_usage
10from core import executor
11from core import num
12from core import state
13from core import ui
14from core import vm
15from data_lang import j8
16from frontend import flag_util
17from frontend import typed_args
18from mycpp import mops
19from mycpp import mylib
20from mycpp.mylib import tagswitch, log
21from ysh import val_ops
22
23_ = log
24
25from typing import Any, cast, TYPE_CHECKING
26if TYPE_CHECKING:
27 from core import ui
28 from osh import cmd_eval
29 from ysh import expr_eval
30
31
32class ctx_Try(object):
33
34 def __init__(self, mutable_opts):
35 # type: (state.MutableOpts) -> None
36
37 mutable_opts.Push(option_i.errexit, True)
38 self.mutable_opts = mutable_opts
39
40 def __enter__(self):
41 # type: () -> None
42 pass
43
44 def __exit__(self, type, value, traceback):
45 # type: (Any, Any, Any) -> None
46 self.mutable_opts.Pop(option_i.errexit)
47
48
49class Try(vm._Builtin):
50 """Allows explicit handling of errors.
51
52 Takes command argv, or a block:
53
54 try ls /bad
55
56 try {
57 var x = 1 / 0
58
59 ls | wc -l
60
61 diff <(sort left.txt) <(sort right.txt)
62 }
63
64 TODO:
65 - Set _error_str (e.UserErrorString())
66 - Set _error_location
67 - These could be used by a 'raise' builtin? Or 'reraise'
68
69 try {
70 foo
71 }
72 if (_status !== 0) {
73 echo 'hello'
74 raise # reads _status, _error_str, and _error_location ?
75 }
76 """
77
78 def __init__(
79 self,
80 mutable_opts, # type: state.MutableOpts
81 mem, # type: state.Mem
82 cmd_ev, # type: cmd_eval.CommandEvaluator
83 shell_ex, # type: vm._Executor
84 errfmt, # type: ui.ErrorFormatter
85 ):
86 # type: (...) -> None
87 self.mutable_opts = mutable_opts
88 self.mem = mem
89 self.shell_ex = shell_ex
90 self.cmd_ev = cmd_ev
91 self.errfmt = errfmt
92
93 def Run(self, cmd_val):
94 # type: (cmd_value.Argv) -> int
95 _, arg_r = flag_util.ParseCmdVal('try_',
96 cmd_val,
97 accept_typed_args=True)
98
99 rd = typed_args.ReaderForProc(cmd_val)
100 cmd = rd.RequiredBlock()
101 rd.Done()
102
103 error_dict = None # type: value.Dict
104
105 status = 0 # success by default
106 try:
107 with ctx_Try(self.mutable_opts):
108 unused = self.cmd_ev.EvalCommand(cmd)
109 except error.Expr as e:
110 status = e.ExitStatus()
111 except error.ErrExit as e:
112 status = e.ExitStatus()
113
114 except error.Structured as e:
115 #log('*** STRUC %s', e)
116 status = e.ExitStatus()
117 error_dict = e.ToDict()
118
119 if error_dict is None:
120 error_dict = value.Dict({'code': num.ToBig(status)})
121
122 # Always set _error
123 self.mem.SetTryError(error_dict)
124
125 # TODO: remove _status in favor of _error.code. This is marked in
126 # spec/TODO-deprecate
127 self.mem.SetTryStatus(status)
128 return 0
129
130
131class Failed(vm._Builtin):
132
133 def __init__(self, mem):
134 # type: (state.Mem) -> None
135 self.mem = mem
136
137 def Run(self, cmd_val):
138 # type: (cmd_value.Argv) -> int
139 _, arg_r = flag_util.ParseCmdVal('failed', cmd_val)
140
141 # No args
142 arg_r.Done()
143
144 # Should we have
145 # failed (_error) ?
146
147 err = self.mem.TryError()
148 code = err.d.get('code')
149 if code is None:
150 # No error
151 return 1
152
153 UP_code = code
154 with tagswitch(code) as case:
155 if case(value_e.Int):
156 code = cast(value.Int, UP_code)
157 # return 0 if and only if it failed
158 return 1 if mops.Equal(code.i, mops.ZERO) else 0
159 else:
160 # This should never happen because the interpreter controls the
161 # contents of TryError()
162 raise AssertionError()
163
164
165class Error(vm._Builtin):
166
167 def __init__(self):
168 # type: () -> None
169 pass
170
171 def Run(self, cmd_val):
172 # type: (cmd_value.Argv) -> int
173 _, arg_r = flag_util.ParseCmdVal('error',
174 cmd_val,
175 accept_typed_args=True)
176
177 message = arg_r.Peek()
178 if message is None:
179 raise error.Usage('expected a message to display',
180 cmd_val.arg_locs[0])
181
182 rd = typed_args.ReaderForProc(cmd_val)
183 # Status 10 is distinct from what the Oils interpreter itself uses. We
184 # use status 3 for expressions and 4 for encode/decode, and 10 "leaves
185 # room" for others.
186 # The user is of course free to choose status 1.
187 status = mops.BigTruncate(rd.NamedInt('code', 10))
188
189 # attach rest of named args to _error Dict
190 properties = rd.RestNamed()
191 rd.Done()
192
193 if status == 0:
194 raise error.Usage('status must be a non-zero integer',
195 cmd_val.arg_locs[0])
196
197 raise error.Structured(status, message, cmd_val.arg_locs[0],
198 properties)
199
200
201class BoolStatus(vm._Builtin):
202
203 def __init__(self, shell_ex, errfmt):
204 # type: (vm._Executor, ui.ErrorFormatter) -> None
205 self.shell_ex = shell_ex
206 self.errfmt = errfmt
207
208 def Run(self, cmd_val):
209 # type: (cmd_value.Argv) -> int
210
211 _, arg_r = flag_util.ParseCmdVal('boolstatus', cmd_val)
212
213 if arg_r.Peek() is None:
214 e_usage('expected a command to run', loc.Missing)
215
216 argv, locs = arg_r.Rest2()
217 cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.typed_args,
218 cmd_val.pos_args, cmd_val.named_args,
219 cmd_val.block_arg)
220
221 cmd_st = CommandStatus.CreateNull(alloc_lists=True)
222 status = self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st,
223 executor.DO_FORK)
224
225 if status not in (0, 1):
226 e_die_status(status,
227 'boolstatus expected status 0 or 1, got %d' % status,
228 locs[0])
229
230 return status
231
232
233class Assert(vm._Builtin):
234
235 def __init__(self, expr_ev, errfmt):
236 # type: (expr_eval.ExprEvaluator, ui.ErrorFormatter) -> None
237 self.expr_ev = expr_ev
238 self.errfmt = errfmt
239 self.f = mylib.Stdout()
240
241 def _AssertComparison(self, exp, blame_loc):
242 # type: (expr.Compare, loc_t) -> None
243
244 # We checked exp.ops
245 assert len(exp.comparators) == 1, exp.comparators
246
247 expected = self.expr_ev.EvalExpr(exp.left, loc.Missing)
248 actual = self.expr_ev.EvalExpr(exp.comparators[0], loc.Missing)
249
250 if not val_ops.ExactlyEqual(expected, actual, blame_loc):
251 self.f.write('\n')
252 # Long values could also show DIFF, rather than wrapping
253 # We could have assert --diff or something
254 ui.PrettyPrintValue('Expected: ', expected, self.f)
255 ui.PrettyPrintValue('Got: ', actual, self.f)
256
257 raise error.Expr("Not equal", exp.ops[0])
258
259 def _AssertExpression(self, val, blame_loc):
260 # type: (value.Expr, loc_t) -> None
261
262 # Special case for assert [true === f()]
263 exp = val.e
264 UP_exp = exp
265 with tagswitch(exp) as case:
266 if case(expr_e.Compare):
267 exp = cast(expr.Compare, UP_exp)
268
269 # Only assert [x === y] is treated as special
270 # Not assert [x === y === z]
271 if len(exp.ops) == 1 and exp.ops[0].id == Id.Expr_TEqual:
272 self._AssertComparison(exp, blame_loc)
273 return
274
275 # Any other expression
276 result = self.expr_ev.EvalExpr(val.e, blame_loc)
277 b = val_ops.ToBool(result)
278 if not b:
279 # Don't print the value for something like assert [x < 4]
280 #self.f.write('\n')
281 #ui.PrettyPrintValue("Expression isn't true: ", result, self.f)
282 raise error.Expr("Expression isn't true", blame_loc)
283
284 def Run(self, cmd_val):
285 # type: (cmd_value.Argv) -> int
286
287 _, arg_r = flag_util.ParseCmdVal('assert',
288 cmd_val,
289 accept_typed_args=True)
290
291 rd = typed_args.ReaderForProc(cmd_val)
292 val = rd.PosValue()
293 rd.Done()
294
295 UP_val = val
296 with tagswitch(val) as case:
297 if case(value_e.Expr): # Destructured assert [true === f()]
298 val = cast(value.Expr, UP_val)
299 self._AssertExpression(val, rd.LeftParenToken())
300 else:
301 b = val_ops.ToBool(val)
302 if not b:
303 # assert (42 === null) should be written
304 # assert [42 === null] to get a better error message
305 # But show the value anyway
306 self.f.write('\n')
307 ui.PrettyPrintValue("Value isn't true: ", val, self.f)
308 raise error.Expr('assertion', rd.LeftParenToken())
309
310 return 0