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

299 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 core import vm
14from data_lang import j8
15from frontend import flag_util
16from frontend import typed_args
17from mycpp import mops
18from mycpp.mylib import tagswitch, log
19from ysh import val_ops
20
21_ = log
22
23from typing import Any, cast, TYPE_CHECKING
24if TYPE_CHECKING:
25 from core import ui
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.RequiredBlock()
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.EvalCommand(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.typed_args,
216 cmd_val.pos_args, cmd_val.named_args,
217 cmd_val.block_arg)
218
219 cmd_st = CommandStatus.CreateNull(alloc_lists=True)
220 status = self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st,
221 executor.DO_FORK)
222
223 if status not in (0, 1):
224 e_die_status(status,
225 'boolstatus expected status 0 or 1, got %d' % status,
226 locs[0])
227
228 return status
229
230
231class Assert(vm._Builtin):
232
233 def __init__(self, expr_ev, errfmt):
234 # type: (expr_eval.ExprEvaluator, ui.ErrorFormatter) -> None
235 self.expr_ev = expr_ev
236 self.errfmt = errfmt
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.errfmt.StderrLine('')
249 self.errfmt.StderrLine(' Expected: %s' % j8.Repr(expected))
250 self.errfmt.StderrLine(' Got: %s' % j8.Repr(actual))
251
252 raise error.Expr("Not equal", exp.ops[0])
253
254 def _AssertExpression(self, val, blame_loc):
255 # type: (value.Expr, loc_t) -> None
256
257 # Special case for assert [true === f()]
258 exp = val.e
259 UP_exp = exp
260 with tagswitch(exp) as case:
261 if case(expr_e.Compare):
262 exp = cast(expr.Compare, UP_exp)
263
264 # Only assert [x === y] is treated as special
265 # Not assert [x === y === z]
266 if len(exp.ops) == 1 and exp.ops[0].id == Id.Expr_TEqual:
267 self._AssertComparison(exp, blame_loc)
268 return
269
270 # Any other expression
271 result = self.expr_ev.EvalExpr(val.e, blame_loc)
272 b = val_ops.ToBool(result)
273 if not b:
274 s = j8.Repr(result)
275 raise error.Expr("Expression isn't true: %s" % s, blame_loc)
276
277 def Run(self, cmd_val):
278 # type: (cmd_value.Argv) -> int
279
280 _, arg_r = flag_util.ParseCmdVal('assert',
281 cmd_val,
282 accept_typed_args=True)
283
284 rd = typed_args.ReaderForProc(cmd_val)
285 val = rd.PosValue()
286 rd.Done()
287
288 UP_val = val
289 with tagswitch(val) as case:
290 if case(value_e.Expr): # Destructured assert [true === f()]
291 val = cast(value.Expr, UP_val)
292 self._AssertExpression(val, rd.LeftParenToken())
293 else:
294 b = val_ops.ToBool(val)
295 if not b:
296 raise error.Expr("Value isn't true: %s" % j8.Repr(val),
297 rd.LeftParenToken())
298
299 return 0