OILS / builtin / error_ysh.py View on Github | oils.pub

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