OILS / osh / cmd_eval.py View on Github | oilshell.org

2230 lines, 1372 significant
1#!/usr/bin/env python2
2# Copyright 2016 Andy Chu. All rights reserved.
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8"""
9cmd_eval.py -- Interpreter for the command language.
10
11Problems:
12$ < Makefile cat | < NOTES.txt head
13
14This just does head? Last one wins.
15"""
16from __future__ import print_function
17
18import sys
19
20from _devbuild.gen.id_kind_asdl import Id
21from _devbuild.gen.option_asdl import option_i
22from _devbuild.gen.syntax_asdl import (
23 IntParamBox,
24 loc,
25 loc_t,
26 loc_e,
27 Token,
28 CompoundWord,
29 command,
30 command_e,
31 command_t,
32 command_str,
33 condition,
34 condition_e,
35 condition_t,
36 case_arg,
37 case_arg_e,
38 case_arg_t,
39 BraceGroup,
40 Proc,
41 Func,
42 assign_op_e,
43 expr_t,
44 proc_sig,
45 proc_sig_e,
46 redir_param,
47 redir_param_e,
48 for_iter,
49 for_iter_e,
50 pat,
51 pat_e,
52 word,
53 Eggex,
54)
55from _devbuild.gen.runtime_asdl import (
56 cmd_value,
57 cmd_value_e,
58 CommandStatus,
59 flow_e,
60 RedirValue,
61 redirect_arg,
62 ProcArgs,
63 scope_e,
64 StatusArray,
65)
66from _devbuild.gen.types_asdl import redir_arg_type_e
67from _devbuild.gen.value_asdl import (value, value_e, value_t, y_lvalue,
68 y_lvalue_e, y_lvalue_t, LeftName)
69
70from core import dev
71from core import error
72from core import executor
73from core.error import e_die, e_die_status
74from core import num
75from core import pyos # Time(). TODO: rename
76from core import pyutil
77from core import state
78from display import ui
79from core import util
80from core import vm
81from frontend import consts
82from frontend import lexer
83from frontend import location
84from osh import braces
85from osh import sh_expr_eval
86from osh import word_eval
87from mycpp import mops
88from mycpp import mylib
89from mycpp.mylib import log, probe, switch, tagswitch
90from ysh import expr_eval
91from ysh import func_proc
92from ysh import val_ops
93
94import posix_ as posix
95import libc # for fnmatch
96# Import this name directly because the C++ translation uses macros literally.
97from libc import FNM_CASEFOLD
98
99from typing import List, Dict, Tuple, Optional, Any, cast, TYPE_CHECKING
100
101if TYPE_CHECKING:
102 from _devbuild.gen.option_asdl import builtin_t
103 from _devbuild.gen.runtime_asdl import cmd_value_t
104 from _devbuild.gen.syntax_asdl import Redir, EnvPair
105 from core.alloc import Arena
106 from core import optview
107 from core.vm import _Executor, _AssignBuiltin
108 from builtin import trap_osh
109
110# flags for main_loop.Batch, ExecuteAndCatch. TODO: Should probably in
111# ExecuteAndCatch, along with SetValue() flags.
112IsMainProgram = 1 << 0 # the main shell program, not eval/source/subshell
113RaiseControlFlow = 1 << 1 # eval/source builtins
114Optimize = 1 << 2
115NoDebugTrap = 1 << 3
116NoErrTrap = 1 << 4
117
118
119def MakeBuiltinArgv(argv1):
120 # type: (List[str]) -> cmd_value.Argv
121 argv = [''] # dummy for argv[0]
122 argv.extend(argv1)
123 missing = None # type: CompoundWord
124 return cmd_value.Argv(argv, [missing] * len(argv), False, None)
125
126
127class Deps(object):
128
129 def __init__(self):
130 # type: () -> None
131 self.mutable_opts = None # type: state.MutableOpts
132 self.dumper = None # type: dev.CrashDumper
133 self.debug_f = None # type: util._DebugFile
134
135
136def _HasManyStatuses(node):
137 # type: (command_t) -> bool
138 """Code patterns that are bad for POSIX errexit. For YSH strict_errexit.
139
140 Note: strict_errexit also uses
141 shopt --unset _allow_command_sub _allow_process_sub
142 """
143 UP_node = node
144 with tagswitch(node) as case:
145 # Atoms.
146 # TODO: Do we need YSH atoms here?
147 if case(command_e.Simple, command_e.DBracket, command_e.DParen):
148 return False
149
150 elif case(command_e.Redirect):
151 node = cast(command.Redirect, UP_node)
152 return _HasManyStatuses(node.child)
153
154 elif case(command_e.Sentence):
155 # Sentence check is for if false; versus if false
156 node = cast(command.Sentence, UP_node)
157 return _HasManyStatuses(node.child)
158
159 elif case(command_e.Pipeline):
160 node = cast(command.Pipeline, UP_node)
161 if len(node.children) == 1:
162 # '! false' is a pipeline that we want to ALLOW
163 # '! ( echo subshell )' is DISALLWOED
164 return _HasManyStatuses(node.children[0])
165 else:
166 # Multiple parts like 'ls | wc' is disallowed
167 return True
168
169 # - ShAssignment could be allowed, though its exit code will always be
170 # 0 without command subs
171 # - Naively, (non-singleton) pipelines could be allowed because pipefail.
172 # BUT could be a proc executed inside a child process, which causes a
173 # problem: the strict_errexit check has to occur at runtime and there's
174 # no way to signal it ot the parent.
175
176 return True
177
178
179def PlusEquals(old_val, val):
180 # type: (value_t, value_t) -> value_t
181 """Implement s+=val, typeset s+=val, etc."""
182
183 UP_old_val = old_val
184 UP_val = val
185
186 tag = val.tag()
187
188 with tagswitch(old_val) as case:
189 if case(value_e.Undef):
190 pass # val is RHS
191
192 elif case(value_e.Str):
193 if tag == value_e.Str:
194 old_val = cast(value.Str, UP_old_val)
195 str_to_append = cast(value.Str, UP_val)
196 val = value.Str(old_val.s + str_to_append.s)
197
198 elif tag == value_e.BashArray:
199 e_die("Can't append array to string")
200
201 else:
202 raise AssertionError() # parsing should prevent this
203
204 elif case(value_e.BashArray):
205 if tag == value_e.Str:
206 e_die("Can't append string to array")
207
208 elif tag == value_e.BashArray:
209 old_val = cast(value.BashArray, UP_old_val)
210 to_append = cast(value.BashArray, UP_val)
211
212 # TODO: MUTATE the existing value for efficiency?
213 strs = [] # type: List[str]
214 strs.extend(old_val.strs)
215 strs.extend(to_append.strs)
216 val = value.BashArray(strs)
217
218 else:
219 raise AssertionError() # parsing should prevent this
220
221 elif case(value_e.BashAssoc):
222 # TODO: Could try to match bash, it will append to ${A[0]}
223 pass
224
225 else:
226 e_die("Can't append to value of type %s" % ui.ValType(old_val))
227
228 return val
229
230
231class ctx_LoopLevel(object):
232 """For checking for invalid control flow."""
233
234 def __init__(self, cmd_ev):
235 # type: (CommandEvaluator) -> None
236 cmd_ev.loop_level += 1
237 self.cmd_ev = cmd_ev
238
239 def __enter__(self):
240 # type: () -> None
241 pass
242
243 def __exit__(self, type, value, traceback):
244 # type: (Any, Any, Any) -> None
245 self.cmd_ev.loop_level -= 1
246
247
248class CommandEvaluator(object):
249 """Executes the program by tree-walking.
250
251 It also does some double-dispatch by passing itself into Eval() for
252 Compound/WordPart.
253 """
254
255 def __init__(
256 self,
257 mem, # type: state.Mem
258 exec_opts, # type: optview.Exec
259 errfmt, # type: ui.ErrorFormatter
260 procs, # type: state.Procs
261 assign_builtins, # type: Dict[builtin_t, _AssignBuiltin]
262 arena, # type: Arena
263 cmd_deps, # type: Deps
264 trap_state, # type: trap_osh.TrapState
265 signal_safe, # type: pyos.SignalSafe
266 ):
267 # type: (...) -> None
268 """
269 Args:
270 mem: Mem instance for storing variables
271 procs: dict of SHELL functions or 'procs'
272 builtins: dict of builtin callables
273 TODO: This should only be for assignment builtins?
274 cmd_deps: A bundle of stateless code
275 """
276 self.shell_ex = None # type: _Executor
277 self.arith_ev = None # type: sh_expr_eval.ArithEvaluator
278 self.bool_ev = None # type: sh_expr_eval.BoolEvaluator
279 self.expr_ev = None # type: expr_eval.ExprEvaluator
280 self.word_ev = None # type: word_eval.AbstractWordEvaluator
281 self.tracer = None # type: dev.Tracer
282
283 self.mem = mem
284 # This is for shopt and set -o. They are initialized by flags.
285 self.exec_opts = exec_opts
286 self.errfmt = errfmt
287 self.procs = procs
288 self.assign_builtins = assign_builtins
289 self.arena = arena
290
291 self.mutable_opts = cmd_deps.mutable_opts
292 self.dumper = cmd_deps.dumper
293 self.debug_f = cmd_deps.debug_f # Used by ShellFuncAction too
294
295 self.trap_state = trap_state
296 self.signal_safe = signal_safe
297
298 self.loop_level = 0 # for detecting bad top-level break/continue
299 self.check_command_sub_status = False # a hack. Modified by ShellExecutor
300
301 self.status_array_pool = [] # type: List[StatusArray]
302
303 def CheckCircularDeps(self):
304 # type: () -> None
305 assert self.arith_ev is not None
306 assert self.bool_ev is not None
307 # Disabled for push OSH
308 #assert self.expr_ev is not None
309 assert self.word_ev is not None
310
311 def _RunAssignBuiltin(self, cmd_val):
312 # type: (cmd_value.Assign) -> int
313 """Run an assignment builtin.
314
315 Except blocks copied from RunBuiltin.
316 """
317 builtin_func = self.assign_builtins.get(cmd_val.builtin_id)
318 if builtin_func is None:
319 # This only happens with alternative Oils interpreters.
320 e_die("Assignment builtin %r not configured" % cmd_val.argv[0],
321 cmd_val.arg_locs[0])
322
323 io_errors = [] # type: List[error.IOError_OSError]
324 with vm.ctx_FlushStdout(io_errors):
325 with ui.ctx_Location(self.errfmt, cmd_val.arg_locs[0]):
326 try:
327 status = builtin_func.Run(cmd_val)
328 except (IOError, OSError) as e:
329 # e.g. declare -p > /dev/full
330 self.errfmt.PrintMessage(
331 '%s builtin I/O error: %s' %
332 (cmd_val.argv[0], pyutil.strerror(e)),
333 cmd_val.arg_locs[0])
334 return 1
335 except error.Usage as e: # Copied from RunBuiltin
336 arg0 = cmd_val.argv[0]
337 self.errfmt.PrefixPrint(e.msg, '%r ' % arg0, e.location)
338 return 2 # consistent error code for usage error
339
340 if len(io_errors): # e.g. declare -p > /dev/full
341 self.errfmt.PrintMessage(
342 '%s builtin I/O: %s' %
343 (cmd_val.argv[0], pyutil.strerror(io_errors[0])),
344 cmd_val.arg_locs[0])
345 return 1
346
347 return status
348
349 def _CheckStatus(self, status, cmd_st, node, default_loc):
350 # type: (int, CommandStatus, command_t, loc_t) -> None
351 """Raises error.ErrExit, maybe with location info attached."""
352
353 assert status >= 0, status
354
355 if status == 0:
356 return # Nothing to do
357
358 self._MaybeRunErrTrap()
359
360 if self.exec_opts.errexit():
361 # NOTE: Sometimes we print 2 errors
362 # - 'type -z' has a UsageError with location, then errexit
363 # - '> /nonexistent' has an I/O error, then errexit
364 # - Pipelines and subshells are compound. Commands within them fail.
365 # - however ( exit 33 ) only prints one message.
366 #
367 # But we will want something like 'false' to have location info.
368
369 UP_node = node
370 with tagswitch(node) as case:
371 if case(command_e.ShAssignment):
372 node = cast(command.ShAssignment, UP_node)
373 cmd_st.show_code = True # leaf
374 # Note: we show errors from assignments a=$(false) rarely: when
375 # errexit, inherit_errexit, verbose_errexit are on, but
376 # command_sub_errexit is off!
377
378 # Note: a subshell often doesn't fail on its own.
379 elif case(command_e.Subshell):
380 node = cast(command.Subshell, UP_node)
381 cmd_st.show_code = True # not sure about this, e.g. ( exit 42 )
382
383 elif case(command_e.Pipeline):
384 node = cast(command.Pipeline, UP_node)
385 cmd_st.show_code = True # not sure about this
386 # TODO: We should show which element of the pipeline failed!
387
388 desc = command_str(node.tag())
389
390 # Override location if explicitly passed.
391 # Note: this produces better results for process sub
392 # echo <(sort x)
393 # and different results for some pipelines:
394 # { ls; false; } | wc -l; echo hi # Point to | or first { ?
395 if default_loc.tag() != loc_e.Missing:
396 blame_loc = default_loc # type: loc_t
397 else:
398 blame_loc = location.TokenForCommand(node)
399
400 msg = '%s failed with status %d' % (desc, status)
401 raise error.ErrExit(status,
402 msg,
403 blame_loc,
404 show_code=cmd_st.show_code)
405
406 def _EvalRedirect(self, r):
407 # type: (Redir) -> RedirValue
408
409 result = RedirValue(r.op.id, r.op, r.loc, None)
410
411 arg = r.arg
412 UP_arg = arg
413 with tagswitch(arg) as case:
414 if case(redir_param_e.Word):
415 arg_word = cast(CompoundWord, UP_arg)
416
417 # Note: needed for redirect like 'echo foo > x$LINENO'
418 self.mem.SetTokenForLine(r.op)
419
420 # Could be computed at parse time?
421 redir_type = consts.RedirArgType(r.op.id)
422
423 if redir_type == redir_arg_type_e.Path:
424 # Redirects with path arguments are evaluated in a special
425 # way. bash and zsh allow globbing a path, but
426 # dash/ash/mksh don't.
427 #
428 # If there are multiple files, zsh opens BOTH, but bash
429 # makes the command fail with status 1. We mostly follow
430 # bash behavior.
431
432 # These don't match bash/zsh behavior
433 # val = self.word_ev.EvalWordToString(arg_word)
434 # val, has_extglob = self.word_ev.EvalWordToPattern(arg_word)
435 # Short-circuit with word_.StaticEval() also doesn't work
436 # with globs
437
438 # mycpp needs this explicit declaration
439 b = braces.BraceDetect(
440 arg_word) # type: Optional[word.BracedTree]
441 if b is not None:
442 raise error.RedirectEval(
443 'Brace expansion not allowed (try adding quotes)',
444 arg_word)
445
446 # Needed for globbing behavior
447 files = self.word_ev.EvalWordSequence([arg_word])
448
449 n = len(files)
450 if n == 0:
451 # happens in OSH on empty elision
452 # in YSH because simple_word_eval globs to zero
453 raise error.RedirectEval(
454 "Can't redirect to zero files", arg_word)
455 if n > 1:
456 raise error.RedirectEval(
457 "Can't redirect to more than one file", arg_word)
458
459 result.arg = redirect_arg.Path(files[0])
460 return result
461
462 elif redir_type == redir_arg_type_e.Desc: # e.g. 1>&2, 1>&-, 1>&2-
463 val = self.word_ev.EvalWordToString(arg_word)
464 t = val.s
465 if len(t) == 0:
466 raise error.RedirectEval(
467 "Redirect descriptor can't be empty", arg_word)
468 return None
469
470 try:
471 if t == '-':
472 result.arg = redirect_arg.CloseFd
473 elif t[-1] == '-':
474 target_fd = int(t[:-1])
475 result.arg = redirect_arg.MoveFd(target_fd)
476 else:
477 result.arg = redirect_arg.CopyFd(int(t))
478 except ValueError:
479 raise error.RedirectEval(
480 'Invalid descriptor %r. Expected D, -, or D- where D is an '
481 'integer' % t, arg_word)
482 return None
483
484 return result
485
486 elif redir_type == redir_arg_type_e.Here: # here word
487 val = self.word_ev.EvalWordToString(arg_word)
488 assert val.tag() == value_e.Str, val
489 # NOTE: bash and mksh both add \n
490 result.arg = redirect_arg.HereDoc(val.s + '\n')
491 return result
492
493 else:
494 raise AssertionError('Unknown redirect op')
495
496 elif case(redir_param_e.HereDoc):
497 arg = cast(redir_param.HereDoc, UP_arg)
498 w = CompoundWord(
499 arg.stdin_parts) # HACK: Wrap it in a word to eval
500 val = self.word_ev.EvalWordToString(w)
501 assert val.tag() == value_e.Str, val
502 result.arg = redirect_arg.HereDoc(val.s)
503 return result
504
505 else:
506 raise AssertionError('Unknown redirect type')
507
508 raise AssertionError('for -Wreturn-type in C++')
509
510 def _RunSimpleCommand(self, cmd_val, cmd_st, run_flags):
511 # type: (cmd_value_t, CommandStatus, int) -> int
512 """Private interface to run a simple command (including assignment)."""
513 UP_cmd_val = cmd_val
514 with tagswitch(UP_cmd_val) as case:
515 if case(cmd_value_e.Argv):
516 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
517 self.tracer.OnSimpleCommand(cmd_val.argv)
518 return self.shell_ex.RunSimpleCommand(cmd_val, cmd_st,
519 run_flags)
520
521 elif case(cmd_value_e.Assign):
522 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
523 self.tracer.OnAssignBuiltin(cmd_val)
524 return self._RunAssignBuiltin(cmd_val)
525
526 else:
527 raise AssertionError()
528
529 def _EvalTempEnv(self, more_env, flags):
530 # type: (List[EnvPair], int) -> None
531 """For FOO=1 cmd."""
532 for e_pair in more_env:
533 val = self.word_ev.EvalRhsWord(e_pair.val)
534 # Set each var so the next one can reference it. Example:
535 # FOO=1 BAR=$FOO ls /
536 self.mem.SetNamed(location.LName(e_pair.name),
537 val,
538 scope_e.LocalOnly,
539 flags=flags)
540
541 def _StrictErrExit(self, node):
542 # type: (command_t) -> None
543 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
544 return
545
546 if _HasManyStatuses(node):
547 node_str = ui.CommandType(node)
548 e_die(
549 "strict_errexit only allows simple commands in conditionals (got %s). "
550 % node_str, loc.Command(node))
551
552 def _StrictErrExitList(self, node_list):
553 # type: (List[command_t]) -> None
554 """Not allowed, too confusing:
555
556 if grep foo eggs.txt; grep bar eggs.txt; then echo hi fi
557 """
558 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
559 return
560
561 if len(node_list) > 1:
562 e_die(
563 "strict_errexit only allows a single command. Hint: use 'try'.",
564 loc.Command(node_list[0]))
565
566 assert len(node_list) > 0
567 node = node_list[0]
568 if _HasManyStatuses(node):
569 # TODO: consolidate error message with above
570 node_str = ui.CommandType(node)
571 e_die(
572 "strict_errexit only allows simple commands in conditionals (got %s). "
573 % node_str, loc.Command(node))
574
575 def _EvalCondition(self, cond, blame_tok):
576 # type: (condition_t, Token) -> bool
577 """
578 Args:
579 spid: for OSH conditions, where errexit was disabled -- e.g. if
580 for YSH conditions, it would be nice to blame the ( instead
581 """
582 b = False
583 UP_cond = cond
584 with tagswitch(cond) as case:
585 if case(condition_e.Shell):
586 cond = cast(condition.Shell, UP_cond)
587 self._StrictErrExitList(cond.commands)
588 with state.ctx_ErrExit(self.mutable_opts, False, blame_tok):
589 cond_status = self._ExecuteList(cond.commands)
590
591 b = cond_status == 0
592
593 elif case(condition_e.YshExpr):
594 cond = cast(condition.YshExpr, UP_cond)
595 obj = self.expr_ev.EvalExpr(cond.e, blame_tok)
596 b = val_ops.ToBool(obj)
597
598 return b
599
600 def _EvalCaseArg(self, arg, blame):
601 # type: (case_arg_t, loc_t) -> value_t
602 """Evaluate a `case_arg` into a `value_t` which can be matched on in a case
603 command.
604 """
605 UP_arg = arg
606 with tagswitch(arg) as case:
607 if case(case_arg_e.Word):
608 arg = cast(case_arg.Word, UP_arg)
609 return self.word_ev.EvalWordToString(arg.w)
610
611 elif case(case_arg_e.YshExpr):
612 arg = cast(case_arg.YshExpr, UP_arg)
613 return self.expr_ev.EvalExpr(arg.e, blame)
614
615 else:
616 raise NotImplementedError()
617
618 def _DoVarDecl(self, node):
619 # type: (command.VarDecl) -> int
620 # x = 'foo' in Hay blocks
621 if node.keyword is None:
622 # Note: there's only one LHS
623 lhs0 = node.lhs[0]
624 lval = LeftName(lhs0.name, lhs0.left)
625 assert node.rhs is not None, node
626 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
627
628 self.mem.SetNamed(lval,
629 val,
630 scope_e.LocalOnly,
631 flags=state.SetReadOnly)
632
633 else: # var or const
634 flags = (state.SetReadOnly
635 if node.keyword.id == Id.KW_Const else 0)
636
637 # var x, y does null initialization
638 if node.rhs is None:
639 for i, lhs_val in enumerate(node.lhs):
640 lval = LeftName(lhs_val.name, lhs_val.left)
641 self.mem.SetNamed(lval,
642 value.Null,
643 scope_e.LocalOnly,
644 flags=flags)
645 return 0
646
647 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
648 lvals = None # type: List[LeftName]
649 rhs_vals = None # type: List[value_t]
650
651 num_lhs = len(node.lhs)
652 if num_lhs == 1:
653 lhs0 = node.lhs[0]
654 lvals = [LeftName(lhs0.name, lhs0.left)]
655 rhs_vals = [right_val]
656 else:
657 items = val_ops.ToList(
658 right_val, 'Destructuring assignment expected List',
659 node.keyword)
660
661 num_rhs = len(items)
662 if num_lhs != num_rhs:
663 raise error.Expr(
664 'Got %d places on the left, but %d values on right' %
665 (num_lhs, num_rhs), node.keyword)
666
667 lvals = []
668 rhs_vals = []
669 for i, lhs_val in enumerate(node.lhs):
670 lval = LeftName(lhs_val.name, lhs_val.left)
671 lvals.append(lval)
672 rhs_vals.append(items[i])
673
674 for i, lval in enumerate(lvals):
675 rval = rhs_vals[i]
676 self.mem.SetNamed(lval, rval, scope_e.LocalOnly, flags=flags)
677
678 return 0
679
680 def _DoMutation(self, node):
681 # type: (command.Mutation) -> None
682
683 with switch(node.keyword.id) as case2:
684 if case2(Id.KW_SetVar):
685 which_scopes = scope_e.LocalOnly
686 elif case2(Id.KW_SetGlobal):
687 which_scopes = scope_e.GlobalOnly
688 else:
689 raise AssertionError(node.keyword.id)
690
691 if node.op.id == Id.Arith_Equal:
692 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
693
694 lvals = None # type: List[y_lvalue_t]
695 rhs_vals = None # type: List[value_t]
696
697 num_lhs = len(node.lhs)
698 if num_lhs == 1:
699 lvals = [self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)]
700 rhs_vals = [right_val]
701 else:
702 items = val_ops.ToList(
703 right_val, 'Destructuring assignment expected List',
704 node.keyword)
705
706 num_rhs = len(items)
707 if num_lhs != num_rhs:
708 raise error.Expr(
709 'Got %d places on the left, but %d values on the right'
710 % (num_lhs, num_rhs), node.keyword)
711
712 lvals = []
713 rhs_vals = []
714 for i, lhs_val in enumerate(node.lhs):
715 lvals.append(
716 self.expr_ev.EvalLhsExpr(lhs_val, which_scopes))
717 rhs_vals.append(items[i])
718
719 for i, lval in enumerate(lvals):
720 rval = rhs_vals[i]
721
722 # setvar mylist[0] = 42
723 # setvar mydict['key'] = 42
724 UP_lval = lval
725
726 if lval.tag() == y_lvalue_e.Local:
727 lval = cast(LeftName, UP_lval)
728
729 self.mem.SetNamed(lval, rval, which_scopes)
730
731 elif lval.tag() == y_lvalue_e.Container:
732 lval = cast(y_lvalue.Container, UP_lval)
733
734 obj = lval.obj
735 UP_obj = obj
736 with tagswitch(obj) as case:
737 if case(value_e.List):
738 obj = cast(value.List, UP_obj)
739 index = val_ops.ToInt(lval.index,
740 'List index should be Int',
741 loc.Missing)
742 obj.items[index] = rval
743
744 elif case(value_e.Dict):
745 obj = cast(value.Dict, UP_obj)
746 key = val_ops.ToStr(lval.index,
747 'Dict index should be Str',
748 loc.Missing)
749 obj.d[key] = rval
750
751 else:
752 raise error.TypeErr(
753 obj, "obj[index] expected List or Dict",
754 loc.Missing)
755
756 else:
757 raise AssertionError()
758
759 else:
760 # Checked in the parser
761 assert len(node.lhs) == 1
762
763 aug_lval = self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)
764 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
765
766 self.expr_ev.EvalAugmented(aug_lval, val, node.op, which_scopes)
767
768 def _DoSimple(self, node, cmd_st):
769 # type: (command.Simple, CommandStatus) -> int
770 probe('cmd_eval', '_DoSimple_enter')
771
772 # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already
773 # redirected here, which screws up logging. For example, 'echo hi
774 # >/dev/null 2>&1'. We want to evaluate argv and log it BEFORE applying
775 # redirects.
776
777 # Another problem:
778 # - tracing can be called concurrently from multiple processes, leading
779 # to overlap. Maybe have a mode that creates a file per process.
780 # xtrace-proc
781 # - line numbers for every command would be very nice. But then you have
782 # to print the filename too.
783
784 words = braces.BraceExpandWords(node.words)
785
786 # Note: Individual WORDS can fail
787 # - $() and <() can have failures. This can happen in DBracket,
788 # DParen, etc. too
789 # - Tracing: this can start processes for proc sub and here docs!
790 cmd_val = self.word_ev.EvalWordSequence2(words,
791 node.is_last_cmd,
792 allow_assign=True)
793
794 UP_cmd_val = cmd_val
795 if UP_cmd_val.tag() == cmd_value_e.Argv:
796 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
797
798 if len(cmd_val.argv): # it can be empty in rare cases
799 self.mem.SetLastArgument(cmd_val.argv[-1])
800 else:
801 self.mem.SetLastArgument('')
802
803 if node.typed_args or node.block: # guard to avoid allocs
804 cmd_val.proc_args = ProcArgs(node.typed_args, None, None, None)
805 func_proc.EvalTypedArgsToProc(self.expr_ev, self.mutable_opts,
806 node, cmd_val.proc_args)
807 else:
808 if node.block:
809 e_die("ShAssignment builtins don't accept blocks",
810 node.block.brace_group.left)
811 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
812
813 # Could reset $_ after assignment, but then we'd have to do it for
814 # all YSH constructs too. It's easier to let it persist. Other
815 # shells aren't consistent.
816 # self.mem.SetLastArgument('')
817
818 run_flags = executor.IS_LAST_CMD if node.is_last_cmd else 0
819
820 # NOTE: RunSimpleCommand may never return
821 if len(node.more_env): # I think this guard is necessary?
822 is_other_special = False # TODO: There are other special builtins too!
823 if cmd_val.tag() == cmd_value_e.Assign or is_other_special:
824 # Special builtins have their temp env persisted.
825 self._EvalTempEnv(node.more_env, 0)
826 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
827 else:
828 with state.ctx_Temp(self.mem):
829 self._EvalTempEnv(node.more_env, state.SetExport)
830 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
831 else:
832 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
833
834 probe('cmd_eval', '_DoSimple_exit', status)
835 return status
836
837 def _DoExpandedAlias(self, node):
838 # type: (command.ExpandedAlias) -> int
839 # Expanded aliases need redirects and env bindings from the calling
840 # context, as well as redirects in the expansion!
841
842 # TODO: SetTokenForLine to OUTSIDE? Don't bother with stuff inside
843 # expansion, since aliases are discouraged.
844
845 if len(node.more_env):
846 with state.ctx_Temp(self.mem):
847 self._EvalTempEnv(node.more_env, state.SetExport)
848 return self._Execute(node.child)
849 else:
850 return self._Execute(node.child)
851
852 def _DoPipeline(self, node, cmd_st):
853 # type: (command.Pipeline, CommandStatus) -> int
854 cmd_st.check_errexit = True
855 for op in node.ops:
856 if op.id != Id.Op_Pipe:
857 e_die("|& isn't supported", op)
858
859 # Remove $_ before pipeline. This matches bash, and is important in
860 # pipelines than assignments because pipelines are non-deterministic.
861 self.mem.SetLastArgument('')
862
863 # Set status to INVALID value, because we MIGHT set cmd_st.pipe_status,
864 # which _Execute() boils down into a status for us.
865 status = -1
866
867 if node.negated is not None:
868 self._StrictErrExit(node)
869 with state.ctx_ErrExit(self.mutable_opts, False, node.negated):
870 # '! grep' is parsed as a pipeline, according to the grammar, but
871 # there's no pipe() call.
872 if len(node.children) == 1:
873 tmp_status = self._Execute(node.children[0])
874 status = 1 if tmp_status == 0 else 0
875 else:
876 self.shell_ex.RunPipeline(node, cmd_st)
877 cmd_st.pipe_negated = True
878
879 # errexit is disabled for !.
880 cmd_st.check_errexit = False
881 else:
882 self.shell_ex.RunPipeline(node, cmd_st)
883
884 return status
885
886 def _DoShAssignment(self, node, cmd_st):
887 # type: (command.ShAssignment, CommandStatus) -> int
888 assert len(node.pairs) >= 1, node
889
890 # x=y is 'neutered' inside 'proc'
891 which_scopes = self.mem.ScopesForWriting()
892
893 for pair in node.pairs:
894 if pair.op == assign_op_e.PlusEqual:
895 assert pair.rhs, pair.rhs # I don't think a+= is valid?
896 rhs = self.word_ev.EvalRhsWord(pair.rhs)
897
898 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
899 # do not respect set -u
900 old_val = sh_expr_eval.OldValue(lval, self.mem, None)
901
902 val = PlusEquals(old_val, rhs)
903
904 else: # plain assignment
905 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
906
907 # RHS can be a string or array.
908 if pair.rhs:
909 val = self.word_ev.EvalRhsWord(pair.rhs)
910 assert isinstance(val, value_t), val
911
912 else: # e.g. 'readonly x' or 'local x'
913 val = None
914
915 # NOTE: In bash and mksh, declare -a myarray makes an empty cell
916 # with Undef value, but the 'array' attribute.
917
918 flags = 0 # for tracing
919 self.mem.SetValue(lval, val, which_scopes, flags=flags)
920 self.tracer.OnShAssignment(lval, pair.op, val, flags, which_scopes)
921
922 # PATCH to be compatible with existing shells: If the assignment had a
923 # command sub like:
924 #
925 # s=$(echo one; false)
926 #
927 # then its status will be in mem.last_status, and we can check it here.
928 # If there was NOT a command sub in the assignment, then we don't want to
929 # check it.
930
931 # Only do this if there was a command sub? How? Look at node?
932 # Set a flag in mem? self.mem.last_status or
933 if self.check_command_sub_status:
934 last_status = self.mem.LastStatus()
935 self._CheckStatus(last_status, cmd_st, node, loc.Missing)
936 return last_status # A global assignment shouldn't clear $?.
937 else:
938 return 0
939
940 def _DoExpr(self, node):
941 # type: (command.Expr) -> int
942
943 # call f(x) or = f(x)
944 val = self.expr_ev.EvalExpr(node.e, loc.Missing)
945
946 if node.keyword.id == Id.Lit_Equals: # = f(x)
947 io_errors = [] # type: List[error.IOError_OSError]
948 with vm.ctx_FlushStdout(io_errors):
949 try:
950 ui.PrettyPrintValue('', val, mylib.Stdout())
951 except (IOError, OSError) as e:
952 self.errfmt.PrintMessage(
953 'I/O error during = keyword: %s' % pyutil.strerror(e),
954 node.keyword)
955 return 1
956
957 if len(io_errors): # e.g. disk full, ulimit
958 self.errfmt.PrintMessage(
959 'I/O error during = keyword: %s' %
960 pyutil.strerror(io_errors[0]), node.keyword)
961 return 1
962
963 return 0
964
965 def _DoControlFlow(self, node):
966 # type: (command.ControlFlow) -> int
967 keyword = node.keyword
968
969 if node.arg_word: # Evaluate the argument
970 str_val = self.word_ev.EvalWordToString(node.arg_word)
971
972 # Quirk: We need 'return $empty' to be valid for libtool. This is
973 # another meaning of strict_control_flow, which also has to do with
974 # break/continue at top level. It has the side effect of making
975 # 'return ""' valid, which shells other than zsh fail on.
976 if (len(str_val.s) == 0 and
977 not self.exec_opts.strict_control_flow()):
978 arg = 0
979 else:
980 try:
981 arg = int(str_val.s) # all control flow takes an integer
982 except ValueError:
983 # Either a bad argument, or integer overflow
984 e_die(
985 '%r expected a small integer, got %r' %
986 (lexer.TokenVal(keyword), str_val.s),
987 loc.Word(node.arg_word))
988
989 # C++ int() does range checking, but Python doesn't. So let's
990 # simulate it here for spec tests.
991 # TODO: could be mylib.ToMachineInt()? Problem: 'int' in C/C++
992 # could be more than 4 bytes. We are testing INT_MAX and
993 # INT_MIN in gc_builtins.cc - those could be hard-coded.
994 if mylib.PYTHON:
995 max_int = (1 << 31) - 1
996 min_int = -(1 << 31)
997 if not (min_int <= arg <= max_int):
998 e_die(
999 '%r expected a small integer, got %r' %
1000 (lexer.TokenVal(keyword), str_val.s),
1001 loc.Word(node.arg_word))
1002 else:
1003 if keyword.id in (Id.ControlFlow_Exit, Id.ControlFlow_Return):
1004 arg = self.mem.LastStatus()
1005 else:
1006 arg = 1 # break or continue 1 level by default
1007
1008 self.tracer.OnControlFlow(consts.ControlFlowName(keyword.id), arg)
1009
1010 # NOTE: A top-level 'return' is OK, unlike in bash. If you can return
1011 # from a sourced script, it makes sense to return from a main script.
1012 if (keyword.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and
1013 self.loop_level == 0):
1014 msg = 'Invalid control flow at top level'
1015 if self.exec_opts.strict_control_flow():
1016 e_die(msg, keyword)
1017 else:
1018 # Only print warnings, never fatal.
1019 # Bash oddly only exits 1 for 'return', but no other shell does.
1020 self.errfmt.PrefixPrint(msg, 'warning: ', keyword)
1021 return 0
1022
1023 if keyword.id == Id.ControlFlow_Exit:
1024 # handled differently than other control flow
1025 raise util.UserExit(arg)
1026 else:
1027 raise vm.IntControlFlow(keyword, arg)
1028
1029 def _DoAndOr(self, node, cmd_st):
1030 # type: (command.AndOr, CommandStatus) -> int
1031 # NOTE: && and || have EQUAL precedence in command mode. See case #13
1032 # in dbracket.test.sh.
1033
1034 left = node.children[0]
1035
1036 # Suppress failure for every child except the last one.
1037 self._StrictErrExit(left)
1038 with state.ctx_ErrExit(self.mutable_opts, False, node.ops[0]):
1039 status = self._Execute(left)
1040
1041 i = 1
1042 n = len(node.children)
1043 while i < n:
1044 #log('i %d status %d', i, status)
1045 child = node.children[i]
1046 op = node.ops[i - 1]
1047 op_id = op.id
1048
1049 #log('child %s op_id %s', child, op_id)
1050
1051 if op_id == Id.Op_DPipe and status == 0:
1052 i += 1
1053 continue # short circuit
1054
1055 elif op_id == Id.Op_DAmp and status != 0:
1056 i += 1
1057 continue # short circuit
1058
1059 if i == n - 1: # errexit handled differently for last child
1060 status = self._Execute(child)
1061 else:
1062 # blame the right && or ||
1063 self._StrictErrExit(child)
1064 with state.ctx_ErrExit(self.mutable_opts, False, op):
1065 status = self._Execute(child)
1066
1067 i += 1
1068
1069 return status
1070
1071 def _DoWhileUntil(self, node):
1072 # type: (command.WhileUntil) -> int
1073 status = 0
1074 with ctx_LoopLevel(self):
1075 while True:
1076 try:
1077 # blame while/until spid
1078 b = self._EvalCondition(node.cond, node.keyword)
1079 if node.keyword.id == Id.KW_Until:
1080 b = not b
1081 if not b:
1082 break
1083 status = self._Execute(node.body) # last one wins
1084
1085 except vm.IntControlFlow as e:
1086 status = 0
1087 action = e.HandleLoop()
1088 if action == flow_e.Break:
1089 break
1090 elif action == flow_e.Raise:
1091 raise
1092
1093 return status
1094
1095 def _DoForEach(self, node):
1096 # type: (command.ForEach) -> int
1097
1098 # for the 2 kinds of shell loop
1099 iter_list = None # type: List[str]
1100
1101 # for YSH loop
1102 iter_expr = None # type: expr_t
1103 expr_blame = None # type: loc_t
1104
1105 iterable = node.iterable
1106 UP_iterable = iterable
1107
1108 with tagswitch(node.iterable) as case:
1109 if case(for_iter_e.Args):
1110 iter_list = self.mem.GetArgv()
1111
1112 elif case(for_iter_e.Words):
1113 iterable = cast(for_iter.Words, UP_iterable)
1114 words = braces.BraceExpandWords(iterable.words)
1115 iter_list = self.word_ev.EvalWordSequence(words)
1116
1117 elif case(for_iter_e.YshExpr):
1118 iterable = cast(for_iter.YshExpr, UP_iterable)
1119 iter_expr = iterable.e
1120 expr_blame = iterable.blame
1121
1122 else:
1123 raise AssertionError()
1124
1125 n = len(node.iter_names)
1126 assert n > 0
1127
1128 i_name = None # type: Optional[LeftName]
1129 # required
1130 name1 = None # type: LeftName
1131 name2 = None # type: Optional[LeftName]
1132
1133 it2 = None # type: val_ops.Iterator
1134 if iter_expr: # for_expr.YshExpr
1135 val = self.expr_ev.EvalExpr(iter_expr, expr_blame)
1136
1137 UP_val = val
1138 with tagswitch(val) as case:
1139 if case(value_e.List):
1140 val = cast(value.List, UP_val)
1141 it2 = val_ops.ListIterator(val)
1142
1143 if n == 1:
1144 name1 = location.LName(node.iter_names[0])
1145 elif n == 2:
1146 i_name = location.LName(node.iter_names[0])
1147 name1 = location.LName(node.iter_names[1])
1148 else:
1149 # This is similar to a parse error
1150 e_die_status(
1151 2,
1152 'List iteration expects at most 2 loop variables',
1153 node.keyword)
1154
1155 elif case(value_e.Dict):
1156 val = cast(value.Dict, UP_val)
1157 it2 = val_ops.DictIterator(val)
1158
1159 if n == 1:
1160 name1 = location.LName(node.iter_names[0])
1161 elif n == 2:
1162 name1 = location.LName(node.iter_names[0])
1163 name2 = location.LName(node.iter_names[1])
1164 elif n == 3:
1165 i_name = location.LName(node.iter_names[0])
1166 name1 = location.LName(node.iter_names[1])
1167 name2 = location.LName(node.iter_names[2])
1168 else:
1169 raise AssertionError()
1170
1171 elif case(value_e.Range):
1172 val = cast(value.Range, UP_val)
1173 it2 = val_ops.RangeIterator(val)
1174
1175 if n == 1:
1176 name1 = location.LName(node.iter_names[0])
1177 elif n == 2:
1178 i_name = location.LName(node.iter_names[0])
1179 name1 = location.LName(node.iter_names[1])
1180 else:
1181 e_die_status(
1182 2,
1183 'Range iteration expects at most 2 loop variables',
1184 node.keyword)
1185
1186 elif case(value_e.Stdin):
1187 # TODO: This could changed to magic iterator?
1188 it2 = val_ops.StdinIterator(expr_blame)
1189 if n == 1:
1190 name1 = location.LName(node.iter_names[0])
1191 elif n == 2:
1192 i_name = location.LName(node.iter_names[0])
1193 name1 = location.LName(node.iter_names[1])
1194 else:
1195 e_die_status(
1196 2,
1197 'Stdin iteration expects at most 2 loop variables',
1198 node.keyword)
1199 else:
1200 raise error.TypeErr(
1201 val, 'for loop expected List, Dict, Range, or Stdin',
1202 node.keyword)
1203
1204 else:
1205 assert iter_list is not None, iter_list
1206
1207 #log('iter list %s', iter_list)
1208 it2 = val_ops.ArrayIter(iter_list)
1209
1210 if n == 1:
1211 name1 = location.LName(node.iter_names[0])
1212 elif n == 2:
1213 i_name = location.LName(node.iter_names[0])
1214 name1 = location.LName(node.iter_names[1])
1215 else:
1216 # This is similar to a parse error
1217 e_die_status(
1218 2, 'Argv iteration expects at most 2 loop variables',
1219 node.keyword)
1220
1221 status = 0 # in case we loop zero times
1222 with ctx_LoopLevel(self):
1223 while True:
1224 first = it2.FirstValue()
1225 #log('first %s', first)
1226 if first is None: # for StdinIterator
1227 #log('first is None')
1228 break
1229
1230 if first.tag() == value_e.Interrupted:
1231 self.RunPendingTraps()
1232 #log('Done running traps')
1233 continue
1234
1235 self.mem.SetLocalName(name1, first)
1236 if name2:
1237 self.mem.SetLocalName(name2, it2.SecondValue())
1238 if i_name:
1239 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1240
1241 # increment index before handling continue, etc.
1242 it2.Next()
1243
1244 try:
1245 status = self._Execute(node.body) # last one wins
1246 except vm.IntControlFlow as e:
1247 status = 0
1248 action = e.HandleLoop()
1249 if action == flow_e.Break:
1250 break
1251 elif action == flow_e.Raise:
1252 raise
1253
1254 return status
1255
1256 def _DoForExpr(self, node):
1257 # type: (command.ForExpr) -> int
1258
1259 status = 0
1260
1261 init = node.init
1262 for_cond = node.cond
1263 body = node.body
1264 update = node.update
1265
1266 self.arith_ev.Eval(init)
1267 with ctx_LoopLevel(self):
1268 while True:
1269 # We only accept integers as conditions
1270 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1271 if mops.Equal(cond_int, mops.ZERO): # false
1272 break
1273
1274 try:
1275 status = self._Execute(body)
1276 except vm.IntControlFlow as e:
1277 status = 0
1278 action = e.HandleLoop()
1279 if action == flow_e.Break:
1280 break
1281 elif action == flow_e.Raise:
1282 raise
1283
1284 self.arith_ev.Eval(update)
1285
1286 return status
1287
1288 def _DoShFunction(self, node):
1289 # type: (command.ShFunction) -> None
1290 if (self.procs.Get(node.name) and
1291 not self.exec_opts.redefine_proc_func()):
1292 e_die(
1293 "Function %s was already defined (redefine_proc_func)" %
1294 node.name, node.name_tok)
1295 sh_func = value.Proc(node.name, node.name_tok, proc_sig.Open,
1296 node.body, None, True)
1297 self.procs.SetShFunc(node.name, sh_func)
1298
1299 def _DoProc(self, node):
1300 # type: (Proc) -> None
1301 proc_name = lexer.TokenVal(node.name)
1302 if (self.procs.Get(proc_name) and
1303 not self.exec_opts.redefine_proc_func()):
1304 e_die(
1305 "Proc %s was already defined (redefine_proc_func)" % proc_name,
1306 node.name)
1307
1308 if node.sig.tag() == proc_sig_e.Closed:
1309 sig = cast(proc_sig.Closed, node.sig)
1310 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1311 else:
1312 proc_defaults = None
1313
1314 # no dynamic scope
1315 proc = value.Proc(proc_name, node.name, node.sig, node.body,
1316 proc_defaults, False)
1317 self.procs.SetProc(proc_name, proc)
1318
1319 def _DoFunc(self, node):
1320 # type: (Func) -> None
1321 name = lexer.TokenVal(node.name)
1322 lval = location.LName(name)
1323
1324 # Check that we haven't already defined a function
1325 cell = self.mem.GetCell(name, scope_e.LocalOnly)
1326 if cell and cell.val.tag() == value_e.Func:
1327 if self.exec_opts.redefine_proc_func():
1328 cell.readonly = False # Ensure we can unset the value
1329 did_unset = self.mem.Unset(lval, scope_e.LocalOnly)
1330 assert did_unset, name
1331 else:
1332 e_die(
1333 "Func %s was already defined (redefine_proc_func)" % name,
1334 node.name)
1335
1336 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1337 self.expr_ev, node)
1338 func_val = value.Func(name, node, pos_defaults, named_defaults, None)
1339
1340 self.mem.SetNamed(lval,
1341 func_val,
1342 scope_e.LocalOnly,
1343 flags=state.SetReadOnly)
1344
1345 def _DoIf(self, node):
1346 # type: (command.If) -> int
1347 status = -1
1348
1349 done = False
1350 for if_arm in node.arms:
1351 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1352 if b:
1353 status = self._ExecuteList(if_arm.action)
1354 done = True
1355 break
1356
1357 if not done and node.else_action is not None:
1358 status = self._ExecuteList(node.else_action)
1359
1360 assert status != -1, 'Should have been initialized'
1361 return status
1362
1363 def _DoCase(self, node):
1364 # type: (command.Case) -> int
1365
1366 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1367 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1368
1369 status = 0 # If there are no arms, it should be zero?
1370
1371 done = False # Should we try the next arm?
1372
1373 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1374 ignore_next_cond = False
1375
1376 for case_arm in node.arms:
1377 with tagswitch(case_arm.pattern) as case:
1378 if case(pat_e.Words):
1379 if to_match.tag() != value_e.Str:
1380 continue # A non-string `to_match` will never match a pat.Words
1381 to_match_str = cast(value.Str, to_match)
1382
1383 pat_words = cast(pat.Words, case_arm.pattern)
1384
1385 this_arm_matches = False
1386 if ignore_next_cond: # Special handling for ;&
1387 this_arm_matches = True
1388 ignore_next_cond = False
1389 else:
1390 for pat_word in pat_words.words:
1391 word_val = self.word_ev.EvalWordToString(
1392 pat_word, word_eval.QUOTE_FNMATCH)
1393
1394 if libc.fnmatch(word_val.s, to_match_str.s,
1395 fnmatch_flags):
1396 this_arm_matches = True
1397 break # Stop at first pattern
1398
1399 if this_arm_matches:
1400 status = self._ExecuteList(case_arm.action)
1401 done = True
1402
1403 # ;& and ;;& only apply to shell-style case
1404 if case_arm.right:
1405 id_ = case_arm.right.id
1406 if id_ == Id.Op_SemiAmp:
1407 # very weird semantic
1408 ignore_next_cond = True
1409 done = False
1410 elif id_ == Id.Op_DSemiAmp:
1411 # Keep going until next pattern
1412 done = False
1413
1414 elif case(pat_e.YshExprs):
1415 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1416
1417 for pat_expr in pat_exprs.exprs:
1418 expr_val = self.expr_ev.EvalExpr(
1419 pat_expr, case_arm.left)
1420
1421 if val_ops.ExactlyEqual(expr_val, to_match,
1422 case_arm.left):
1423 status = self._ExecuteList(case_arm.action)
1424 done = True
1425 break
1426
1427 elif case(pat_e.Eggex):
1428 eggex = cast(Eggex, case_arm.pattern)
1429 eggex_val = self.expr_ev.EvalEggex(eggex)
1430
1431 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1432 status = self._ExecuteList(case_arm.action)
1433 done = True
1434 break
1435
1436 elif case(pat_e.Else):
1437 status = self._ExecuteList(case_arm.action)
1438 done = True
1439 break
1440
1441 else:
1442 raise AssertionError()
1443
1444 if done: # first match wins
1445 break
1446
1447 return status
1448
1449 def _DoTimeBlock(self, node):
1450 # type: (command.TimeBlock) -> int
1451 # TODO:
1452 # - When do we need RUSAGE_CHILDREN?
1453 # - Respect TIMEFORMAT environment variable.
1454 # "If this variable is not set, Bash acts as if it had the value"
1455 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1456 # "A trailing newline is added when the format string is displayed."
1457
1458 s_real, s_user, s_sys = pyos.Time()
1459 status = self._Execute(node.pipeline)
1460 e_real, e_user, e_sys = pyos.Time()
1461 # note: mycpp doesn't support %.3f
1462 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1463
1464 return status
1465
1466 def _DoRedirect(self, node, cmd_st):
1467 # type: (command.Redirect, CommandStatus) -> int
1468
1469 status = 0
1470 redirects = [] # type: List[RedirValue]
1471
1472 try:
1473 for redir in node.redirects:
1474 redirects.append(self._EvalRedirect(redir))
1475 except error.RedirectEval as e:
1476 self.errfmt.PrettyPrintError(e)
1477 redirects = None
1478 except error.FailGlob as e: # e.g. echo hi > foo-*
1479 if not e.HasLocation():
1480 e.location = self.mem.GetFallbackLocation()
1481 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1482 redirects = None
1483
1484 if redirects is None:
1485 # Error evaluating redirect words
1486 status = 1
1487
1488 # Translation fix: redirect I/O errors may happen in a C++
1489 # destructor ~vm::ctx_Redirect, which means they must be signaled
1490 # by out params, not exceptions.
1491 io_errors = [] # type: List[error.IOError_OSError]
1492
1493 # If we evaluated redirects, apply/push them
1494 if status == 0:
1495 self.shell_ex.PushRedirects(redirects, io_errors)
1496 if len(io_errors):
1497 # core/process.py prints cryptic errors, so we repeat them
1498 # here. e.g. Bad File Descriptor
1499 self.errfmt.PrintMessage(
1500 'I/O error applying redirect: %s' %
1501 pyutil.strerror(io_errors[0]),
1502 self.mem.GetFallbackLocation())
1503 status = 1
1504
1505 # If we applied redirects successfully, run the command_t, and pop
1506 # them.
1507 if status == 0:
1508 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1509 status = self._Execute(node.child)
1510 if len(io_errors):
1511 # It would be better to point to the right redirect
1512 # operator, but we don't track it specifically
1513 e_die("Fatal error popping redirect: %s" %
1514 pyutil.strerror(io_errors[0]))
1515
1516 return status
1517
1518 def _Dispatch(self, node, cmd_st):
1519 # type: (command_t, CommandStatus) -> int
1520 """Switch on the command_t variants and execute them."""
1521
1522 # If we call RunCommandSub in a recursive call to the executor, this will
1523 # be set true (if strict_errexit is false). But it only lasts for one
1524 # command.
1525 probe('cmd_eval', '_Dispatch', node.tag())
1526 self.check_command_sub_status = False
1527
1528 UP_node = node
1529 with tagswitch(node) as case:
1530 if case(command_e.Simple): # LEAF command
1531 node = cast(command.Simple, UP_node)
1532
1533 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1534 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1535 # TODO: blame_tok should always be set.
1536 if node.blame_tok is not None:
1537 self.mem.SetTokenForLine(node.blame_tok)
1538
1539 self._MaybeRunDebugTrap()
1540 cmd_st.check_errexit = True
1541 status = self._DoSimple(node, cmd_st)
1542
1543 elif case(command_e.ExpandedAlias):
1544 node = cast(command.ExpandedAlias, UP_node)
1545 status = self._DoExpandedAlias(node)
1546
1547 elif case(command_e.Sentence):
1548 node = cast(command.Sentence, UP_node)
1549
1550 # Don't check_errexit since this isn't a leaf command
1551 if node.terminator.id == Id.Op_Semi:
1552 status = self._Execute(node.child)
1553 else:
1554 status = self.shell_ex.RunBackgroundJob(node.child)
1555
1556 elif case(command_e.Redirect):
1557 node = cast(command.Redirect, UP_node)
1558
1559 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1560 # dash/ash
1561 cmd_st.check_errexit = True
1562 status = self._DoRedirect(node, cmd_st)
1563
1564 elif case(command_e.Pipeline):
1565 node = cast(command.Pipeline, UP_node)
1566 status = self._DoPipeline(node, cmd_st)
1567
1568 elif case(command_e.Subshell):
1569 node = cast(command.Subshell, UP_node)
1570
1571 # This is a leaf from the parent process POV
1572 cmd_st.check_errexit = True
1573 status = self.shell_ex.RunSubshell(node.child)
1574
1575 elif case(command_e.DBracket): # LEAF command
1576 node = cast(command.DBracket, UP_node)
1577
1578 self.mem.SetTokenForLine(node.left)
1579 self._MaybeRunDebugTrap()
1580
1581 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1582
1583 cmd_st.check_errexit = True
1584 cmd_st.show_code = True # this is a "leaf" for errors
1585 result = self.bool_ev.EvalB(node.expr)
1586 status = 0 if result else 1
1587
1588 elif case(command_e.DParen): # LEAF command
1589 node = cast(command.DParen, UP_node)
1590
1591 self.mem.SetTokenForLine(node.left)
1592 self._MaybeRunDebugTrap()
1593
1594 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1595
1596 cmd_st.check_errexit = True
1597 cmd_st.show_code = True # this is a "leaf" for errors
1598 i = self.arith_ev.EvalToBigInt(node.child)
1599 status = 1 if mops.Equal(i, mops.ZERO) else 0
1600
1601 elif case(command_e.ControlFlow): # LEAF command
1602 node = cast(command.ControlFlow, UP_node)
1603
1604 self.mem.SetTokenForLine(node.keyword)
1605 self._MaybeRunDebugTrap()
1606
1607 status = self._DoControlFlow(node)
1608
1609 elif case(command_e.VarDecl): # LEAF command
1610 node = cast(command.VarDecl, UP_node)
1611
1612 # Point to var name (bare assignment has no keyword)
1613 self.mem.SetTokenForLine(node.lhs[0].left)
1614 status = self._DoVarDecl(node)
1615
1616 elif case(command_e.Mutation): # LEAF command
1617 node = cast(command.Mutation, UP_node)
1618
1619 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1620 self._DoMutation(node)
1621 status = 0 # if no exception is thrown, it succeeds
1622
1623 elif case(command_e.ShAssignment): # LEAF command
1624 node = cast(command.ShAssignment, UP_node)
1625
1626 self.mem.SetTokenForLine(node.pairs[0].left)
1627 self._MaybeRunDebugTrap()
1628
1629 # Only unqualified assignment a=b
1630 status = self._DoShAssignment(node, cmd_st)
1631
1632 elif case(command_e.Expr): # YSH LEAF command
1633 node = cast(command.Expr, UP_node)
1634
1635 self.mem.SetTokenForLine(node.keyword)
1636 # YSH debug trap?
1637
1638 status = self._DoExpr(node)
1639
1640 elif case(command_e.Retval): # YSH LEAF command
1641 node = cast(command.Retval, UP_node)
1642
1643 self.mem.SetTokenForLine(node.keyword)
1644 # YSH debug trap? I think we don't want the debug trap in func
1645 # dialect, for speed?
1646
1647 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1648 raise vm.ValueControlFlow(node.keyword, val)
1649
1650 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1651 # DoGroup has 'do' and 'done' spids for translation.
1652 elif case(command_e.CommandList):
1653 node = cast(command.CommandList, UP_node)
1654 status = self._ExecuteList(node.children)
1655
1656 elif case(command_e.DoGroup):
1657 node = cast(command.DoGroup, UP_node)
1658 status = self._ExecuteList(node.children)
1659
1660 elif case(command_e.BraceGroup):
1661 node = cast(BraceGroup, UP_node)
1662 status = self._ExecuteList(node.children)
1663
1664 elif case(command_e.AndOr):
1665 node = cast(command.AndOr, UP_node)
1666 status = self._DoAndOr(node, cmd_st)
1667
1668 elif case(command_e.WhileUntil):
1669 node = cast(command.WhileUntil, UP_node)
1670
1671 self.mem.SetTokenForLine(node.keyword)
1672 status = self._DoWhileUntil(node)
1673
1674 elif case(command_e.ForEach):
1675 node = cast(command.ForEach, UP_node)
1676
1677 self.mem.SetTokenForLine(node.keyword)
1678 status = self._DoForEach(node)
1679
1680 elif case(command_e.ForExpr):
1681 node = cast(command.ForExpr, UP_node)
1682
1683 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1684 status = self._DoForExpr(node)
1685
1686 elif case(command_e.ShFunction):
1687 node = cast(command.ShFunction, UP_node)
1688 self._DoShFunction(node)
1689 status = 0
1690
1691 elif case(command_e.Proc):
1692 node = cast(Proc, UP_node)
1693 self._DoProc(node)
1694 status = 0
1695
1696 elif case(command_e.Func):
1697 node = cast(Func, UP_node)
1698
1699 # Needed for error, when the func is an existing variable name
1700 self.mem.SetTokenForLine(node.name)
1701
1702 self._DoFunc(node)
1703 status = 0
1704
1705 elif case(command_e.If):
1706 node = cast(command.If, UP_node)
1707
1708 # No SetTokenForLine() because
1709 # - $LINENO can't appear directly in 'if'
1710 # - 'if' doesn't directly cause errors
1711 # It will be taken care of by command.Simple, condition, etc.
1712 status = self._DoIf(node)
1713
1714 elif case(command_e.NoOp):
1715 status = 0 # make it true
1716
1717 elif case(command_e.Case):
1718 node = cast(command.Case, UP_node)
1719
1720 # Must set location for 'case $LINENO'
1721 self.mem.SetTokenForLine(node.case_kw)
1722 self._MaybeRunDebugTrap()
1723 status = self._DoCase(node)
1724
1725 elif case(command_e.TimeBlock):
1726 node = cast(command.TimeBlock, UP_node)
1727 status = self._DoTimeBlock(node)
1728
1729 else:
1730 raise NotImplementedError(node.tag())
1731
1732 # Return to caller. Note the only case that didn't set it was Pipeline,
1733 # which set cmd_st.pipe_status.
1734 return status
1735
1736 def RunPendingTraps(self):
1737 # type: () -> None
1738
1739 trap_nodes = self.trap_state.GetPendingTraps()
1740 if trap_nodes is not None:
1741 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1742 True):
1743 for trap_node in trap_nodes:
1744 # Isolate the exit status.
1745 with state.ctx_Registers(self.mem):
1746 # Trace it. TODO: Show the trap kind too
1747 with dev.ctx_Tracer(self.tracer, 'trap', None):
1748 self._Execute(trap_node)
1749
1750 def _Execute(self, node):
1751 # type: (command_t) -> int
1752 """Call _Dispatch(), and performs the errexit check.
1753
1754 Also runs trap handlers.
1755 """
1756 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1757 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1758 # and maybe throw an exception.
1759 self.RunPendingTraps()
1760
1761 # We only need this somewhat hacky check in osh-cpp since python's runtime
1762 # handles SIGINT for us in osh.
1763 if mylib.CPP:
1764 if self.signal_safe.PollSigInt():
1765 raise KeyboardInterrupt()
1766
1767 # Manual GC point before every statement
1768 mylib.MaybeCollect()
1769
1770 # Optimization: These 2 records have rarely-used lists, so we don't pass
1771 # alloc_lists=True. We create them on demand.
1772 cmd_st = CommandStatus.CreateNull()
1773 if len(self.status_array_pool):
1774 # Optimized to avoid allocs
1775 process_sub_st = self.status_array_pool.pop()
1776 else:
1777 process_sub_st = StatusArray.CreateNull()
1778
1779 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1780 try:
1781 status = self._Dispatch(node, cmd_st)
1782 except error.FailGlob as e:
1783 if not e.HasLocation(): # Last resort!
1784 e.location = self.mem.GetFallbackLocation()
1785 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1786 status = 1 # another redirect word eval error
1787 cmd_st.check_errexit = True # failglob + errexit
1788
1789 # Now we've waited for process subs
1790
1791 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1792 # @_pipeline_status
1793 pipe_status = cmd_st.pipe_status
1794 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1795 # makes it annoying to check both _process_sub_status and
1796 # _pipeline_status
1797
1798 errexit_loc = loc.Missing # type: loc_t
1799 if pipe_status is not None:
1800 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1801 # for a REAL pipeline (but not singleton pipelines)
1802 assert status == -1, (
1803 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1804 status)
1805
1806 self.mem.SetPipeStatus(pipe_status)
1807
1808 if self.exec_opts.pipefail():
1809 # The status is that of the last command that is non-zero.
1810 status = 0
1811 for i, st in enumerate(pipe_status):
1812 if st != 0:
1813 status = st
1814 errexit_loc = cmd_st.pipe_locs[i]
1815 else:
1816 # The status is that of last command, period.
1817 status = pipe_status[-1]
1818
1819 if cmd_st.pipe_negated:
1820 status = 1 if status == 0 else 0
1821
1822 # Compute status from _process_sub_status
1823 if process_sub_st.codes is None:
1824 # Optimized to avoid allocs
1825 self.status_array_pool.append(process_sub_st)
1826 else:
1827 codes = process_sub_st.codes
1828 self.mem.SetProcessSubStatus(codes)
1829 if status == 0 and self.exec_opts.process_sub_fail():
1830 # Choose the LAST non-zero status, consistent with pipefail above.
1831 for i, st in enumerate(codes):
1832 if st != 0:
1833 status = st
1834 errexit_loc = process_sub_st.locs[i]
1835
1836 self.mem.SetLastStatus(status)
1837
1838 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1839 # However, any bash construct can appear in a pipeline. So it's easier
1840 # just to put it at the end, instead of after every node.
1841 #
1842 # Possible exceptions:
1843 # - function def (however this always exits 0 anyway)
1844 # - assignment - its result should be the result of the RHS?
1845 # - e.g. arith sub, command sub? I don't want arith sub.
1846 # - ControlFlow: always raises, it has no status.
1847 if cmd_st.check_errexit:
1848 #log('cmd_st %s', cmd_st)
1849 self._CheckStatus(status, cmd_st, node, errexit_loc)
1850
1851 return status
1852
1853 def _ExecuteList(self, children):
1854 # type: (List[command_t]) -> int
1855 status = 0 # for empty list
1856 for child in children:
1857 # last status wins
1858 status = self._Execute(child)
1859 return status
1860
1861 def LastStatus(self):
1862 # type: () -> int
1863 """For main_loop.py to determine the exit code of the shell itself."""
1864 return self.mem.LastStatus()
1865
1866 def _NoForkLast(self, node):
1867 # type: (command_t) -> None
1868
1869 if 0:
1870 log('optimizing')
1871 node.PrettyPrint(sys.stderr)
1872 log('')
1873
1874 UP_node = node
1875 with tagswitch(node) as case:
1876 if case(command_e.Simple):
1877 node = cast(command.Simple, UP_node)
1878 node.is_last_cmd = True
1879 if 0:
1880 log('Simple optimized')
1881
1882 elif case(command_e.Pipeline):
1883 node = cast(command.Pipeline, UP_node)
1884 # Bug fix: if we change the status, we can't exec the last
1885 # element!
1886 if node.negated is None and not self.exec_opts.pipefail():
1887 self._NoForkLast(node.children[-1])
1888
1889 elif case(command_e.Sentence):
1890 node = cast(command.Sentence, UP_node)
1891 self._NoForkLast(node.child)
1892
1893 elif case(command_e.CommandList):
1894 # Subshells often have a CommandList child
1895 node = cast(command.CommandList, UP_node)
1896 self._NoForkLast(node.children[-1])
1897
1898 elif case(command_e.BraceGroup):
1899 # TODO: What about redirects?
1900 node = cast(BraceGroup, UP_node)
1901 self._NoForkLast(node.children[-1])
1902
1903 def _NoForkSentence(self, node):
1904 # type: (command_t) -> None
1905
1906 if 0:
1907 log('optimizing')
1908 node.PrettyPrint(sys.stderr)
1909 log('')
1910
1911 UP_node = node
1912 with tagswitch(node) as case:
1913 if case(command_e.Simple):
1914 node = cast(command.Simple, UP_node)
1915 node.is_last_cmd = False
1916 if 0:
1917 log('Simple optimized')
1918
1919 #elif case(command_e.Pipeline):
1920 # node = cast(command.Pipeline, UP_node)
1921 # if node.negated is None:
1922 # #log ('pipe')
1923 # self._NoForkLast(node.children[-1])
1924
1925 elif case(command_e.Sentence):
1926 node = cast(command.Sentence, UP_node)
1927 if node.terminator.id == Id.Op_Amp:
1928 self._NoForkSentence(node.child)
1929
1930 def _RemoveSubshells(self, node):
1931 # type: (command_t) -> command_t
1932 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1933
1934 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1935 be correct otherwise.
1936 """
1937 UP_node = node
1938 with tagswitch(node) as case:
1939 if case(command_e.Subshell):
1940 node = cast(command.Subshell, UP_node)
1941 # Optimize ( ( date ) ) etc.
1942 return self._RemoveSubshells(node.child)
1943 return node
1944
1945 def ExecuteAndCatch(self, node, cmd_flags=0):
1946 # type: (command_t, int) -> Tuple[bool, bool]
1947 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1948
1949 Args:
1950 node: LST subtree
1951 optimize: Whether to exec the last process rather than fork/exec
1952
1953 Returns:
1954 TODO: use enum 'why' instead of the 2 booleans
1955
1956 Used by
1957 - main_loop.py.
1958 - SubProgramThunk for pipelines, subshell, command sub, process sub
1959 - TODO: Signals besides EXIT trap
1960
1961 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
1962 finally_exit boolean. We use a different algorithm.
1963 """
1964 if cmd_flags & Optimize:
1965 node = self._RemoveSubshells(node)
1966 #if self.exec_opts.no_fork_last():
1967
1968 # Bug: analysis happens too early:
1969 #
1970 # sh -c 'trap "echo trap" EXIT; date'
1971 #if not self.trap_state.ThisProcessHasTraps():
1972
1973 self._NoForkLast(node) # turn the last ones into exec
1974
1975 # TODO: this makes a difference in job control test
1976 #self._NoForkSentence(node)
1977
1978 if 0:
1979 log('after opt:')
1980 node.PrettyPrint()
1981 log('')
1982
1983 is_return = False
1984 is_fatal = False
1985 is_errexit = False
1986
1987 err = None # type: error.FatalRuntime
1988 status = -1 # uninitialized
1989
1990 try:
1991 options = [] # type: List[int]
1992 if cmd_flags & NoDebugTrap:
1993 options.append(option_i._no_debug_trap)
1994 if cmd_flags & NoErrTrap:
1995 options.append(option_i._no_err_trap)
1996 with state.ctx_Option(self.mutable_opts, options, True):
1997 status = self._Execute(node)
1998 except vm.IntControlFlow as e:
1999 if cmd_flags & RaiseControlFlow:
2000 raise # 'eval break' and 'source return.sh', etc.
2001 else:
2002 # Return at top level is OK, unlike in bash.
2003 if e.IsReturn():
2004 is_return = True
2005 status = e.StatusCode()
2006 else:
2007 # TODO: This error message is invalid. Can also happen in eval.
2008 # We need a flag.
2009
2010 # Invalid control flow
2011 self.errfmt.Print_(
2012 "Loop and control flow can't be in different processes",
2013 blame_loc=e.token)
2014 is_fatal = True
2015 # All shells exit 0 here. It could be hidden behind
2016 # strict_control_flow if the incompatibility causes problems.
2017 status = 1
2018 except error.Parse as e:
2019 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2020 raise
2021 except error.ErrExit as e:
2022 err = e
2023 is_errexit = True
2024 except error.FatalRuntime as e:
2025 err = e
2026
2027 if err:
2028 status = err.ExitStatus()
2029
2030 is_fatal = True
2031 # Do this before unwinding stack
2032 self.dumper.MaybeRecord(self, err)
2033
2034 if not err.HasLocation(): # Last resort!
2035 #log('Missing location')
2036 err.location = self.mem.GetFallbackLocation()
2037 #log('%s', err.location)
2038
2039 if is_errexit:
2040 if self.exec_opts.verbose_errexit():
2041 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2042 posix.getpid())
2043 else:
2044 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2045
2046 assert status >= 0, 'Should have been initialized'
2047
2048 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2049 # created a crash dump. So we get 2 or more of them.
2050 self.dumper.MaybeDump(status)
2051
2052 self.mem.SetLastStatus(status)
2053 return is_return, is_fatal
2054
2055 def EvalCommand(self, block):
2056 # type: (command_t) -> int
2057 """For builtins to evaluate command args.
2058
2059 Many exceptions are raised.
2060
2061 Examples:
2062
2063 cd /tmp (; ; mycmd)
2064
2065 And:
2066 eval (mycmd)
2067 call _io->eval(mycmd)
2068
2069 (Should those be more like eval 'mystring'?)
2070 """
2071 status = 0
2072 try:
2073 status = self._Execute(block) # can raise FatalRuntimeError, etc.
2074 except vm.IntControlFlow as e: # A block is more like a function.
2075 # return in a block
2076 if e.IsReturn():
2077 status = e.StatusCode()
2078 else:
2079 e_die('Unexpected control flow in block', e.token)
2080
2081 return status
2082
2083 def MaybeRunExitTrap(self, mut_status):
2084 # type: (IntParamBox) -> None
2085 """If an EXIT trap handler exists, run it.
2086
2087 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2088 all bash/dash/mksh seem to agree on it. See cases in
2089 builtin-trap.test.sh.
2090
2091 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2092 of this awkward interface. But that's true in Python and not C!
2093
2094 Could use i & (n-1) == i & 255 because we have a power of 2.
2095 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2096 """
2097 node = self.trap_state.GetHook('EXIT') # type: command_t
2098 if node:
2099 # NOTE: Don't set option_i._running_trap, because that's for
2100 # RunPendingTraps() in the MAIN LOOP
2101 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2102 try:
2103 is_return, is_fatal = self.ExecuteAndCatch(node)
2104 except util.UserExit as e: # explicit exit
2105 mut_status.i = e.status
2106 return
2107 if is_return: # explicit 'return' in the trap handler!
2108 mut_status.i = self.LastStatus()
2109
2110 def _MaybeRunDebugTrap(self):
2111 # type: () -> None
2112 """Run user-specified DEBUG code before certain commands."""
2113 node = self.trap_state.GetHook('DEBUG') # type: command_t
2114 if node is None:
2115 return
2116
2117 # Fix lastpipe / job control / DEBUG trap interaction
2118 if self.exec_opts._no_debug_trap():
2119 return
2120
2121 # Don't run recursively run traps, etc.
2122 if not self.mem.ShouldRunDebugTrap():
2123 return
2124
2125 # NOTE: Don't set option_i._running_trap, because that's for
2126 # RunPendingTraps() in the MAIN LOOP
2127
2128 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2129 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2130 # for SetTokenForLine $LINENO
2131 with state.ctx_DebugTrap(self.mem):
2132 # Don't catch util.UserExit, etc.
2133 self._Execute(node)
2134
2135 def _MaybeRunErrTrap(self):
2136 # type: () -> None
2137 """
2138 Run user-specified ERR code after checking the status of certain
2139 commands (pipelines)
2140 """
2141 node = self.trap_state.GetHook('ERR') # type: command_t
2142 if node is None:
2143 return
2144
2145 # ERR trap is only run for a whole pipeline, not its parts
2146 if self.exec_opts._no_err_trap():
2147 return
2148
2149 # Prevent infinite recursion
2150 if self.mem.running_err_trap:
2151 return
2152
2153 # "disabled errexit" rule
2154 if self.mutable_opts.ErrExitIsDisabled():
2155 return
2156
2157 # bash rule - affected by set -o errtrace
2158 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2159 return
2160
2161 # NOTE: Don't set option_i._running_trap, because that's for
2162 # RunPendingTraps() in the MAIN LOOP
2163
2164 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2165 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2166 with state.ctx_ErrTrap(self.mem):
2167 self._Execute(node)
2168
2169 def RunProc(self, proc, cmd_val):
2170 # type: (value.Proc, cmd_value.Argv) -> int
2171 """Run procs aka "shell functions".
2172
2173 For SimpleCommand and registered completion hooks.
2174 """
2175 sig = proc.sig
2176 if sig.tag() == proc_sig_e.Closed:
2177 # We're binding named params. User should use @rest. No 'shift'.
2178 proc_argv = [] # type: List[str]
2179 else:
2180 proc_argv = cmd_val.argv[1:]
2181
2182 # Hm this sets "$@". TODO: Set ARGV only
2183 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2184 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2185
2186 # Redirects still valid for functions.
2187 # Here doc causes a pipe and Process(SubProgramThunk).
2188 try:
2189 status = self._Execute(proc.body)
2190 except vm.IntControlFlow as e:
2191 if e.IsReturn():
2192 status = e.StatusCode()
2193 else:
2194 # break/continue used in the wrong place.
2195 e_die(
2196 'Unexpected %r (in proc call)' %
2197 lexer.TokenVal(e.token), e.token)
2198 except error.FatalRuntime as e:
2199 # Dump the stack before unwinding it
2200 self.dumper.MaybeRecord(self, e)
2201 raise
2202
2203 return status
2204
2205 def RunFuncForCompletion(self, proc, argv):
2206 # type: (value.Proc, List[str]) -> int
2207 """
2208 Args:
2209 argv: $1 $2 $3 ... not including $0
2210 """
2211 cmd_val = MakeBuiltinArgv(argv)
2212
2213 # TODO: Change this to run YSH procs and funcs too
2214 try:
2215 status = self.RunProc(proc, cmd_val)
2216 except error.FatalRuntime as e:
2217 self.errfmt.PrettyPrintError(e)
2218 status = e.ExitStatus()
2219 except vm.IntControlFlow as e:
2220 # shouldn't be able to exit the shell from a completion hook!
2221 # TODO: Avoid overwriting the prompt!
2222 self.errfmt.Print_('Attempted to exit from completion hook.',
2223 blame_loc=e.token)
2224
2225 status = 1
2226 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2227 return status
2228
2229
2230# vim: sw=4