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