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

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