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

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