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

224 lines, 123 significant
1from __future__ import print_function
2
3from _devbuild.gen.option_asdl import option_i
4from _devbuild.gen.runtime_asdl import cmd_value, CommandStatus
5from _devbuild.gen.syntax_asdl import loc
6from _devbuild.gen.value_asdl import value, value_e
7from core import error
8from core.error import e_die_status, e_usage
9from core import executor
10from core import num
11from core import state
12from core import vm
13from frontend import flag_util
14from frontend import typed_args
15from mycpp import mops
16from mycpp.mylib import tagswitch, log
17
18_ = log
19
20from typing import Any, cast, TYPE_CHECKING
21if TYPE_CHECKING:
22 from core import ui
23 from osh import cmd_eval
24
25
26class ctx_Try(object):
27
28 def __init__(self, mutable_opts):
29 # type: (state.MutableOpts) -> None
30
31 mutable_opts.Push(option_i.errexit, True)
32 self.mutable_opts = mutable_opts
33
34 def __enter__(self):
35 # type: () -> None
36 pass
37
38 def __exit__(self, type, value, traceback):
39 # type: (Any, Any, Any) -> None
40 self.mutable_opts.Pop(option_i.errexit)
41
42
43class Try(vm._Builtin):
44 """Allows explicit handling of errors.
45
46 Takes command argv, or a block:
47
48 try ls /bad
49
50 try {
51 var x = 1 / 0
52
53 ls | wc -l
54
55 diff <(sort left.txt) <(sort right.txt)
56 }
57
58 TODO:
59 - Set _error_str (e.UserErrorString())
60 - Set _error_location
61 - These could be used by a 'raise' builtin? Or 'reraise'
62
63 try {
64 foo
65 }
66 if (_status !== 0) {
67 echo 'hello'
68 raise # reads _status, _error_str, and _error_location ?
69 }
70 """
71
72 def __init__(
73 self,
74 mutable_opts, # type: state.MutableOpts
75 mem, # type: state.Mem
76 cmd_ev, # type: cmd_eval.CommandEvaluator
77 shell_ex, # type: vm._Executor
78 errfmt, # type: ui.ErrorFormatter
79 ):
80 # type: (...) -> None
81 self.mutable_opts = mutable_opts
82 self.mem = mem
83 self.shell_ex = shell_ex
84 self.cmd_ev = cmd_ev
85 self.errfmt = errfmt
86
87 def Run(self, cmd_val):
88 # type: (cmd_value.Argv) -> int
89 _, arg_r = flag_util.ParseCmdVal('try_',
90 cmd_val,
91 accept_typed_args=True)
92
93 rd = typed_args.ReaderForProc(cmd_val)
94 cmd = rd.RequiredBlock()
95 rd.Done()
96
97 error_dict = None # type: value.Dict
98
99 status = 0 # success by default
100 try:
101 with ctx_Try(self.mutable_opts):
102 unused = self.cmd_ev.EvalCommand(cmd)
103 except error.Expr as e:
104 status = e.ExitStatus()
105 except error.ErrExit as e:
106 status = e.ExitStatus()
107
108 except error.Structured as e:
109 #log('*** STRUC %s', e)
110 status = e.ExitStatus()
111 error_dict = e.ToDict()
112
113 if error_dict is None:
114 error_dict = value.Dict({'code': num.ToBig(status)})
115
116 # Always set _error
117 self.mem.SetTryError(error_dict)
118
119 # TODO: remove _status in favor of _error.code. This is marked in
120 # spec/TODO-deprecate
121 self.mem.SetTryStatus(status)
122 return 0
123
124
125class Failed(vm._Builtin):
126
127 def __init__(self, mem):
128 # type: (state.Mem) -> None
129 self.mem = mem
130
131 def Run(self, cmd_val):
132 # type: (cmd_value.Argv) -> int
133 _, arg_r = flag_util.ParseCmdVal('failed', cmd_val)
134
135 # No args
136 arg_r.Done()
137
138 # Should we have
139 # failed (_error) ?
140
141 err = self.mem.TryError()
142 code = err.d.get('code')
143 if code is None:
144 # No error
145 return 1
146
147 UP_code = code
148 with tagswitch(code) as case:
149 if case(value_e.Int):
150 code = cast(value.Int, UP_code)
151 # return 0 if and only if it failed
152 return 1 if mops.Equal(code.i, mops.ZERO) else 0
153 else:
154 # This should never happen because the interpreter controls the
155 # contents of TryError()
156 raise AssertionError()
157
158
159class Error(vm._Builtin):
160
161 def __init__(self):
162 # type: () -> None
163 pass
164
165 def Run(self, cmd_val):
166 # type: (cmd_value.Argv) -> int
167 _, arg_r = flag_util.ParseCmdVal('error',
168 cmd_val,
169 accept_typed_args=True)
170
171 message = arg_r.Peek()
172 if message is None:
173 raise error.Usage('expected a message to display',
174 cmd_val.arg_locs[0])
175
176 rd = typed_args.ReaderForProc(cmd_val)
177 # Status 10 is distinct from what the Oils interpreter itself uses. We
178 # use status 3 for expressions and 4 for encode/decode, and 10 "leaves
179 # room" for others.
180 # The user is of course free to choose status 1.
181 status = mops.BigTruncate(rd.NamedInt('code', 10))
182
183 # attach rest of named args to _error Dict
184 properties = rd.RestNamed()
185 rd.Done()
186
187 if status == 0:
188 raise error.Usage('status must be a non-zero integer',
189 cmd_val.arg_locs[0])
190
191 raise error.Structured(status, message, cmd_val.arg_locs[0],
192 properties)
193
194
195class BoolStatus(vm._Builtin):
196
197 def __init__(self, shell_ex, errfmt):
198 # type: (vm._Executor, ui.ErrorFormatter) -> None
199 self.shell_ex = shell_ex
200 self.errfmt = errfmt
201
202 def Run(self, cmd_val):
203 # type: (cmd_value.Argv) -> int
204
205 _, arg_r = flag_util.ParseCmdVal('boolstatus', cmd_val)
206
207 if arg_r.Peek() is None:
208 e_usage('expected a command to run', loc.Missing)
209
210 argv, locs = arg_r.Rest2()
211 cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.typed_args,
212 cmd_val.pos_args, cmd_val.named_args,
213 cmd_val.block_arg)
214
215 cmd_st = CommandStatus.CreateNull(alloc_lists=True)
216 status = self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st,
217 executor.DO_FORK)
218
219 if status not in (0, 1):
220 e_die_status(status,
221 'boolstatus expected status 0 or 1, got %d' % status,
222 locs[0])
223
224 return status