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

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