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

2132 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
770 # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already
771 # redirected here, which screws up logging. For example, 'echo hi
772 # >/dev/null 2>&1'. We want to evaluate argv and log it BEFORE applying
773 # redirects.
774
775 # Another problem:
776 # - tracing can be called concurrently from multiple processes, leading
777 # to overlap. Maybe have a mode that creates a file per process.
778 # xtrace-proc
779 # - line numbers for every command would be very nice. But then you have
780 # to print the filename too.
781
782 words = braces.BraceExpandWords(node.words)
783
784 # Note: Individual WORDS can fail
785 # - $() and <() can have failures. This can happen in DBracket,
786 # DParen, etc. too
787 # - Tracing: this can start processes for proc sub and here docs!
788 cmd_val = self.word_ev.EvalWordSequence2(words, allow_assign=True)
789
790 UP_cmd_val = cmd_val
791 if UP_cmd_val.tag() == cmd_value_e.Argv:
792 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
793
794 if len(cmd_val.argv): # it can be empty in rare cases
795 self.mem.SetLastArgument(cmd_val.argv[-1])
796 else:
797 self.mem.SetLastArgument('')
798
799 if node.typed_args or node.block: # guard to avoid allocs
800 func_proc.EvalTypedArgsToProc(self.expr_ev, self.mutable_opts,
801 node, cmd_val)
802 else:
803 if node.block:
804 e_die("ShAssignment builtins don't accept blocks",
805 node.block.brace_group.left)
806 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
807
808 # Could reset $_ after assignment, but then we'd have to do it for
809 # all YSH constructs too. It's easier to let it persist. Other
810 # shells aren't consistent.
811 # self.mem.SetLastArgument('')
812
813 run_flags = executor.DO_FORK if node.do_fork else 0
814 # NOTE: RunSimpleCommand never returns when do_fork=False!
815 if len(node.more_env): # I think this guard is necessary?
816 is_other_special = False # TODO: There are other special builtins too!
817 if cmd_val.tag() == cmd_value_e.Assign or is_other_special:
818 # Special builtins have their temp env persisted.
819 self._EvalTempEnv(node.more_env, 0)
820 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
821 else:
822 with state.ctx_Temp(self.mem):
823 self._EvalTempEnv(node.more_env, state.SetExport)
824 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
825 else:
826 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
827
828 probe('cmd_eval', '_DoSimple_exit', status)
829 return status
830
831 def _DoExpandedAlias(self, node):
832 # type: (command.ExpandedAlias) -> int
833 # Expanded aliases need redirects and env bindings from the calling
834 # context, as well as redirects in the expansion!
835
836 # TODO: SetTokenForLine to OUTSIDE? Don't bother with stuff inside
837 # expansion, since aliases are discouraged.
838
839 if len(node.more_env):
840 with state.ctx_Temp(self.mem):
841 self._EvalTempEnv(node.more_env, state.SetExport)
842 return self._Execute(node.child)
843 else:
844 return self._Execute(node.child)
845
846 def _DoPipeline(self, node, cmd_st):
847 # type: (command.Pipeline, CommandStatus) -> int
848 cmd_st.check_errexit = True
849 for op in node.ops:
850 if op.id != Id.Op_Pipe:
851 e_die("|& isn't supported", op)
852
853 # Remove $_ before pipeline. This matches bash, and is important in
854 # pipelines than assignments because pipelines are non-deterministic.
855 self.mem.SetLastArgument('')
856
857 # Set status to INVALID value, because we MIGHT set cmd_st.pipe_status,
858 # which _Execute() boils down into a status for us.
859 status = -1
860
861 if node.negated is not None:
862 self._StrictErrExit(node)
863 with state.ctx_ErrExit(self.mutable_opts, False, node.negated):
864 # '! grep' is parsed as a pipeline, according to the grammar, but
865 # there's no pipe() call.
866 if len(node.children) == 1:
867 tmp_status = self._Execute(node.children[0])
868 status = 1 if tmp_status == 0 else 0
869 else:
870 self.shell_ex.RunPipeline(node, cmd_st)
871 cmd_st.pipe_negated = True
872
873 # errexit is disabled for !.
874 cmd_st.check_errexit = False
875 else:
876 self.shell_ex.RunPipeline(node, cmd_st)
877
878 return status
879
880 def _DoShAssignment(self, node, cmd_st):
881 # type: (command.ShAssignment, CommandStatus) -> int
882 assert len(node.pairs) >= 1, node
883
884 # x=y is 'neutered' inside 'proc'
885 which_scopes = self.mem.ScopesForWriting()
886
887 for pair in node.pairs:
888 if pair.op == assign_op_e.PlusEqual:
889 assert pair.rhs, pair.rhs # I don't think a+= is valid?
890 rhs = self.word_ev.EvalRhsWord(pair.rhs)
891
892 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
893 # do not respect set -u
894 old_val = sh_expr_eval.OldValue(lval, self.mem, None)
895
896 val = PlusEquals(old_val, rhs)
897
898 else: # plain assignment
899 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
900
901 # RHS can be a string or array.
902 if pair.rhs:
903 val = self.word_ev.EvalRhsWord(pair.rhs)
904 assert isinstance(val, value_t), val
905
906 else: # e.g. 'readonly x' or 'local x'
907 val = None
908
909 # NOTE: In bash and mksh, declare -a myarray makes an empty cell
910 # with Undef value, but the 'array' attribute.
911
912 flags = 0 # for tracing
913 self.mem.SetValue(lval, val, which_scopes, flags=flags)
914 self.tracer.OnShAssignment(lval, pair.op, val, flags, which_scopes)
915
916 # PATCH to be compatible with existing shells: If the assignment had a
917 # command sub like:
918 #
919 # s=$(echo one; false)
920 #
921 # then its status will be in mem.last_status, and we can check it here.
922 # If there was NOT a command sub in the assignment, then we don't want to
923 # check it.
924
925 # Only do this if there was a command sub? How? Look at node?
926 # Set a flag in mem? self.mem.last_status or
927 if self.check_command_sub_status:
928 last_status = self.mem.LastStatus()
929 self._CheckStatus(last_status, cmd_st, node, loc.Missing)
930 return last_status # A global assignment shouldn't clear $?.
931 else:
932 return 0
933
934 def _DoExpr(self, node):
935 # type: (command.Expr) -> int
936
937 # call f(x) or = f(x)
938 val = self.expr_ev.EvalExpr(node.e, loc.Missing)
939
940 if node.keyword.id == Id.Lit_Equals: # = f(x)
941 io_errors = [] # type: List[error.IOError_OSError]
942 with vm.ctx_FlushStdout(io_errors):
943 try:
944 ui.PrettyPrintValue(val, mylib.Stdout())
945 except (IOError, OSError) as e:
946 self.errfmt.PrintMessage(
947 'I/O error during = keyword: %s' % pyutil.strerror(e),
948 node.keyword)
949 return 1
950
951 if len(io_errors): # e.g. disk full, ulimit
952 self.errfmt.PrintMessage(
953 'I/O error during = keyword: %s' %
954 pyutil.strerror(io_errors[0]), node.keyword)
955 return 1
956
957 return 0
958
959 def _DoControlFlow(self, node):
960 # type: (command.ControlFlow) -> int
961 keyword = node.keyword
962
963 if node.arg_word: # Evaluate the argument
964 str_val = self.word_ev.EvalWordToString(node.arg_word)
965
966 # Quirk: We need 'return $empty' to be valid for libtool. This is
967 # another meaning of strict_control_flow, which also has to do with
968 # break/continue at top level. It has the side effect of making
969 # 'return ""' valid, which shells other than zsh fail on.
970 if (len(str_val.s) == 0 and
971 not self.exec_opts.strict_control_flow()):
972 arg = 0
973 else:
974 try:
975 arg = int(str_val.s) # all control flow takes an integer
976 except ValueError:
977 # Either a bad argument, or integer overflow
978 e_die(
979 '%r expected a small integer, got %r' %
980 (lexer.TokenVal(keyword), str_val.s),
981 loc.Word(node.arg_word))
982
983 # C++ int() does range checking, but Python doesn't. So let's
984 # simulate it here for spec tests.
985 # TODO: could be mylib.ToMachineInt()? Problem: 'int' in C/C++
986 # could be more than 4 bytes. We are testing INT_MAX and
987 # INT_MIN in gc_builtins.cc - those could be hard-coded.
988 if mylib.PYTHON:
989 max_int = (1 << 31) - 1
990 min_int = -(1 << 31)
991 if not (min_int <= arg <= max_int):
992 e_die(
993 '%r expected a small integer, got %r' %
994 (lexer.TokenVal(keyword), str_val.s),
995 loc.Word(node.arg_word))
996 else:
997 if keyword.id in (Id.ControlFlow_Exit, Id.ControlFlow_Return):
998 arg = self.mem.LastStatus()
999 else:
1000 arg = 1 # break or continue 1 level by default
1001
1002 self.tracer.OnControlFlow(consts.ControlFlowName(keyword.id), arg)
1003
1004 # NOTE: A top-level 'return' is OK, unlike in bash. If you can return
1005 # from a sourced script, it makes sense to return from a main script.
1006 if (keyword.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and
1007 self.loop_level == 0):
1008 msg = 'Invalid control flow at top level'
1009 if self.exec_opts.strict_control_flow():
1010 e_die(msg, keyword)
1011 else:
1012 # Only print warnings, never fatal.
1013 # Bash oddly only exits 1 for 'return', but no other shell does.
1014 self.errfmt.PrefixPrint(msg, 'warning: ', keyword)
1015 return 0
1016
1017 if keyword.id == Id.ControlFlow_Exit:
1018 # handled differently than other control flow
1019 raise util.UserExit(arg)
1020 else:
1021 raise vm.IntControlFlow(keyword, arg)
1022
1023 def _DoAndOr(self, node, cmd_st):
1024 # type: (command.AndOr, CommandStatus) -> int
1025 # NOTE: && and || have EQUAL precedence in command mode. See case #13
1026 # in dbracket.test.sh.
1027
1028 left = node.children[0]
1029
1030 # Suppress failure for every child except the last one.
1031 self._StrictErrExit(left)
1032 with state.ctx_ErrExit(self.mutable_opts, False, node.ops[0]):
1033 status = self._Execute(left)
1034
1035 i = 1
1036 n = len(node.children)
1037 while i < n:
1038 #log('i %d status %d', i, status)
1039 child = node.children[i]
1040 op = node.ops[i - 1]
1041 op_id = op.id
1042
1043 #log('child %s op_id %s', child, op_id)
1044
1045 if op_id == Id.Op_DPipe and status == 0:
1046 i += 1
1047 continue # short circuit
1048
1049 elif op_id == Id.Op_DAmp and status != 0:
1050 i += 1
1051 continue # short circuit
1052
1053 if i == n - 1: # errexit handled differently for last child
1054 status = self._Execute(child)
1055 cmd_st.check_errexit = True
1056 else:
1057 # blame the right && or ||
1058 self._StrictErrExit(child)
1059 with state.ctx_ErrExit(self.mutable_opts, False, op):
1060 status = self._Execute(child)
1061
1062 i += 1
1063
1064 return status
1065
1066 def _DoWhileUntil(self, node):
1067 # type: (command.WhileUntil) -> int
1068 status = 0
1069 with ctx_LoopLevel(self):
1070 while True:
1071 try:
1072 # blame while/until spid
1073 b = self._EvalCondition(node.cond, node.keyword)
1074 if node.keyword.id == Id.KW_Until:
1075 b = not b
1076 if not b:
1077 break
1078 status = self._Execute(node.body) # last one wins
1079
1080 except vm.IntControlFlow as e:
1081 status = 0
1082 action = e.HandleLoop()
1083 if action == flow_e.Break:
1084 break
1085 elif action == flow_e.Raise:
1086 raise
1087
1088 return status
1089
1090 def _DoForEach(self, node):
1091 # type: (command.ForEach) -> int
1092
1093 # for the 2 kinds of shell loop
1094 iter_list = None # type: List[str]
1095
1096 # for YSH loop
1097 iter_expr = None # type: expr_t
1098 expr_blame = None # type: loc_t
1099
1100 iterable = node.iterable
1101 UP_iterable = iterable
1102
1103 with tagswitch(node.iterable) as case:
1104 if case(for_iter_e.Args):
1105 iter_list = self.mem.GetArgv()
1106
1107 elif case(for_iter_e.Words):
1108 iterable = cast(for_iter.Words, UP_iterable)
1109 words = braces.BraceExpandWords(iterable.words)
1110 iter_list = self.word_ev.EvalWordSequence(words)
1111
1112 elif case(for_iter_e.YshExpr):
1113 iterable = cast(for_iter.YshExpr, UP_iterable)
1114 iter_expr = iterable.e
1115 expr_blame = iterable.blame
1116
1117 n = len(node.iter_names)
1118 assert n > 0
1119
1120 i_name = None # type: Optional[LeftName]
1121 # required
1122 name1 = None # type: LeftName
1123 name2 = None # type: Optional[LeftName]
1124
1125 it2 = None # type: val_ops._ContainerIter
1126 if iter_list is None: # for_expr.YshExpr
1127 val = self.expr_ev.EvalExpr(iter_expr, expr_blame)
1128
1129 UP_val = val
1130 with tagswitch(val) as case:
1131 if case(value_e.List):
1132 val = cast(value.List, UP_val)
1133 it2 = val_ops.ListIterator(val)
1134
1135 if n == 1:
1136 name1 = location.LName(node.iter_names[0])
1137 elif n == 2:
1138 i_name = location.LName(node.iter_names[0])
1139 name1 = location.LName(node.iter_names[1])
1140 else:
1141 # This is similar to a parse error
1142 e_die_status(
1143 2,
1144 'List iteration expects at most 2 loop variables',
1145 node.keyword)
1146
1147 elif case(value_e.Dict):
1148 val = cast(value.Dict, UP_val)
1149 it2 = val_ops.DictIterator(val)
1150
1151 if n == 1:
1152 name1 = location.LName(node.iter_names[0])
1153 elif n == 2:
1154 name1 = location.LName(node.iter_names[0])
1155 name2 = location.LName(node.iter_names[1])
1156 elif n == 3:
1157 i_name = location.LName(node.iter_names[0])
1158 name1 = location.LName(node.iter_names[1])
1159 name2 = location.LName(node.iter_names[2])
1160 else:
1161 raise AssertionError()
1162
1163 elif case(value_e.Range):
1164 val = cast(value.Range, UP_val)
1165 it2 = val_ops.RangeIterator(val)
1166
1167 if n == 1:
1168 name1 = location.LName(node.iter_names[0])
1169 elif n == 2:
1170 i_name = location.LName(node.iter_names[0])
1171 name1 = location.LName(node.iter_names[1])
1172 else:
1173 e_die_status(
1174 2,
1175 'Range iteration expects at most 2 loop variables',
1176 node.keyword)
1177
1178 else:
1179 raise error.TypeErr(val, 'for loop expected List or Dict',
1180 node.keyword)
1181 else:
1182 #log('iter list %s', iter_list)
1183 it2 = val_ops.ArrayIter(iter_list)
1184
1185 if n == 1:
1186 name1 = location.LName(node.iter_names[0])
1187 elif n == 2:
1188 i_name = location.LName(node.iter_names[0])
1189 name1 = location.LName(node.iter_names[1])
1190 else:
1191 # This is similar to a parse error
1192 e_die_status(
1193 2, 'Argv iteration expects at most 2 loop variables',
1194 node.keyword)
1195
1196 status = 0 # in case we loop zero times
1197 with ctx_LoopLevel(self):
1198 while not it2.Done():
1199 self.mem.SetLocalName(name1, it2.FirstValue())
1200 if name2:
1201 self.mem.SetLocalName(name2, it2.SecondValue())
1202 if i_name:
1203 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1204
1205 # increment index before handling continue, etc.
1206 it2.Next()
1207
1208 try:
1209 status = self._Execute(node.body) # last one wins
1210 except vm.IntControlFlow as e:
1211 status = 0
1212 action = e.HandleLoop()
1213 if action == flow_e.Break:
1214 break
1215 elif action == flow_e.Raise:
1216 raise
1217
1218 return status
1219
1220 def _DoForExpr(self, node):
1221 # type: (command.ForExpr) -> int
1222
1223 status = 0
1224
1225 init = node.init
1226 for_cond = node.cond
1227 body = node.body
1228 update = node.update
1229
1230 self.arith_ev.Eval(init)
1231 with ctx_LoopLevel(self):
1232 while True:
1233 # We only accept integers as conditions
1234 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1235 if mops.Equal(cond_int, mops.ZERO): # false
1236 break
1237
1238 try:
1239 status = self._Execute(body)
1240 except vm.IntControlFlow as e:
1241 status = 0
1242 action = e.HandleLoop()
1243 if action == flow_e.Break:
1244 break
1245 elif action == flow_e.Raise:
1246 raise
1247
1248 self.arith_ev.Eval(update)
1249
1250 return status
1251
1252 def _DoShFunction(self, node):
1253 # type: (command.ShFunction) -> None
1254 if node.name in self.procs and not self.exec_opts.redefine_proc_func():
1255 e_die(
1256 "Function %s was already defined (redefine_proc_func)" %
1257 node.name, node.name_tok)
1258 self.procs[node.name] = value.Proc(node.name, node.name_tok,
1259 proc_sig.Open, node.body, None,
1260 True)
1261
1262 def _DoProc(self, node):
1263 # type: (Proc) -> None
1264 proc_name = lexer.TokenVal(node.name)
1265 if proc_name in self.procs and not self.exec_opts.redefine_proc_func():
1266 e_die(
1267 "Proc %s was already defined (redefine_proc_func)" % proc_name,
1268 node.name)
1269
1270 if node.sig.tag() == proc_sig_e.Closed:
1271 sig = cast(proc_sig.Closed, node.sig)
1272 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1273 else:
1274 proc_defaults = None
1275
1276 # no dynamic scope
1277 self.procs[proc_name] = value.Proc(proc_name, node.name, node.sig,
1278 node.body, proc_defaults, False)
1279
1280 def _DoFunc(self, node):
1281 # type: (Func) -> None
1282 name = lexer.TokenVal(node.name)
1283 lval = location.LName(name)
1284
1285 # Check that we haven't already defined a function
1286 cell = self.mem.GetCell(name, scope_e.LocalOnly)
1287 if cell and cell.val.tag() == value_e.Func:
1288 if self.exec_opts.redefine_proc_func():
1289 cell.readonly = False # Ensure we can unset the value
1290 did_unset = self.mem.Unset(lval, scope_e.LocalOnly)
1291 assert did_unset, name
1292 else:
1293 e_die(
1294 "Func %s was already defined (redefine_proc_func)" % name,
1295 node.name)
1296
1297 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1298 self.expr_ev, node)
1299 func_val = value.Func(name, node, pos_defaults, named_defaults, None)
1300
1301 self.mem.SetNamed(lval,
1302 func_val,
1303 scope_e.LocalOnly,
1304 flags=state.SetReadOnly)
1305
1306 def _DoIf(self, node):
1307 # type: (command.If) -> int
1308 status = -1
1309
1310 done = False
1311 for if_arm in node.arms:
1312 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1313 if b:
1314 status = self._ExecuteList(if_arm.action)
1315 done = True
1316 break
1317
1318 if not done and node.else_action is not None:
1319 status = self._ExecuteList(node.else_action)
1320
1321 assert status != -1, 'Should have been initialized'
1322 return status
1323
1324 def _DoCase(self, node):
1325 # type: (command.Case) -> int
1326
1327 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1328 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1329
1330 status = 0 # If there are no arms, it should be zero?
1331
1332 done = False # Should we try the next arm?
1333
1334 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1335 ignore_next_cond = False
1336
1337 for case_arm in node.arms:
1338 with tagswitch(case_arm.pattern) as case:
1339 if case(pat_e.Words):
1340 if to_match.tag() != value_e.Str:
1341 continue # A non-string `to_match` will never match a pat.Words
1342 to_match_str = cast(value.Str, to_match)
1343
1344 pat_words = cast(pat.Words, case_arm.pattern)
1345
1346 this_arm_matches = False
1347 if ignore_next_cond: # Special handling for ;&
1348 this_arm_matches = True
1349 ignore_next_cond = False
1350 else:
1351 for pat_word in pat_words.words:
1352 word_val = self.word_ev.EvalWordToString(
1353 pat_word, word_eval.QUOTE_FNMATCH)
1354
1355 if libc.fnmatch(word_val.s, to_match_str.s,
1356 fnmatch_flags):
1357 this_arm_matches = True
1358 break # Stop at first pattern
1359
1360 if this_arm_matches:
1361 status = self._ExecuteList(case_arm.action)
1362 done = True
1363
1364 # ;& and ;;& only apply to shell-style case
1365 if case_arm.right:
1366 id_ = case_arm.right.id
1367 if id_ == Id.Op_SemiAmp:
1368 # very weird semantic
1369 ignore_next_cond = True
1370 done = False
1371 elif id_ == Id.Op_DSemiAmp:
1372 # Keep going until next pattern
1373 done = False
1374
1375 elif case(pat_e.YshExprs):
1376 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1377
1378 for pat_expr in pat_exprs.exprs:
1379 expr_val = self.expr_ev.EvalExpr(
1380 pat_expr, case_arm.left)
1381
1382 if val_ops.ExactlyEqual(expr_val, to_match,
1383 case_arm.left):
1384 status = self._ExecuteList(case_arm.action)
1385 done = True
1386 break
1387
1388 elif case(pat_e.Eggex):
1389 eggex = cast(Eggex, case_arm.pattern)
1390 eggex_val = self.expr_ev.EvalEggex(eggex)
1391
1392 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1393 status = self._ExecuteList(case_arm.action)
1394 done = True
1395 break
1396
1397 elif case(pat_e.Else):
1398 status = self._ExecuteList(case_arm.action)
1399 done = True
1400 break
1401
1402 else:
1403 raise AssertionError()
1404
1405 if done: # first match wins
1406 break
1407
1408 return status
1409
1410 def _DoTimeBlock(self, node):
1411 # type: (command.TimeBlock) -> int
1412 # TODO:
1413 # - When do we need RUSAGE_CHILDREN?
1414 # - Respect TIMEFORMAT environment variable.
1415 # "If this variable is not set, Bash acts as if it had the value"
1416 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1417 # "A trailing newline is added when the format string is displayed."
1418
1419 s_real, s_user, s_sys = pyos.Time()
1420 status = self._Execute(node.pipeline)
1421 e_real, e_user, e_sys = pyos.Time()
1422 # note: mycpp doesn't support %.3f
1423 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1424
1425 return status
1426
1427 def _DoRedirect(self, node, cmd_st):
1428 # type: (command.Redirect, CommandStatus) -> int
1429
1430 status = 0
1431 redirects = [] # type: List[RedirValue]
1432
1433 try:
1434 for redir in node.redirects:
1435 redirects.append(self._EvalRedirect(redir))
1436 except error.RedirectEval as e:
1437 self.errfmt.PrettyPrintError(e)
1438 redirects = None
1439 except error.FailGlob as e: # e.g. echo hi > foo-*
1440 if not e.HasLocation():
1441 e.location = self.mem.GetFallbackLocation()
1442 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1443 redirects = None
1444
1445 if redirects is None:
1446 # Error evaluating redirect words
1447 status = 1
1448
1449 # Translation fix: redirect I/O errors may happen in a C++
1450 # destructor ~vm::ctx_Redirect, which means they must be signaled
1451 # by out params, not exceptions.
1452 io_errors = [] # type: List[error.IOError_OSError]
1453
1454 # If we evaluated redirects, apply/push them
1455 if status == 0:
1456 self.shell_ex.PushRedirects(redirects, io_errors)
1457 if len(io_errors):
1458 # core/process.py prints cryptic errors, so we repeat them
1459 # here. e.g. Bad File Descriptor
1460 self.errfmt.PrintMessage(
1461 'I/O error applying redirect: %s' %
1462 pyutil.strerror(io_errors[0]),
1463 self.mem.GetFallbackLocation())
1464 status = 1
1465
1466 # If we applied redirects successfully, run the command_t, and pop
1467 # them.
1468 if status == 0:
1469 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1470 status = self._Execute(node.child)
1471 if len(io_errors):
1472 # It would be better to point to the right redirect
1473 # operator, but we don't track it specifically
1474 e_die("Fatal error popping redirect: %s" %
1475 pyutil.strerror(io_errors[0]))
1476
1477 return status
1478
1479 def _Dispatch(self, node, cmd_st):
1480 # type: (command_t, CommandStatus) -> int
1481 """Switch on the command_t variants and execute them."""
1482
1483 # If we call RunCommandSub in a recursive call to the executor, this will
1484 # be set true (if strict_errexit is false). But it only lasts for one
1485 # command.
1486 probe('cmd_eval', '_Dispatch', node.tag())
1487 self.check_command_sub_status = False
1488
1489 UP_node = node
1490 with tagswitch(node) as case:
1491 if case(command_e.Simple): # LEAF command
1492 node = cast(command.Simple, UP_node)
1493
1494 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1495 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1496 # TODO: blame_tok should always be set.
1497 if node.blame_tok is not None:
1498 self.mem.SetTokenForLine(node.blame_tok)
1499
1500 self._MaybeRunDebugTrap()
1501 cmd_st.check_errexit = True
1502 status = self._DoSimple(node, cmd_st)
1503
1504 elif case(command_e.ExpandedAlias):
1505 node = cast(command.ExpandedAlias, UP_node)
1506 status = self._DoExpandedAlias(node)
1507
1508 elif case(command_e.Sentence):
1509 node = cast(command.Sentence, UP_node)
1510
1511 # Don't check_errexit since this isn't a leaf command
1512 if node.terminator.id == Id.Op_Semi:
1513 status = self._Execute(node.child)
1514 else:
1515 status = self.shell_ex.RunBackgroundJob(node.child)
1516
1517 elif case(command_e.Redirect):
1518 node = cast(command.Redirect, UP_node)
1519
1520 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1521 # dash/ash
1522 cmd_st.check_errexit = True
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 # Optimization: These 2 records have rarely-used lists, so we don't pass
1733 # alloc_lists=True. We create them on demand.
1734 cmd_st = CommandStatus.CreateNull()
1735 if len(self.status_array_pool):
1736 # Optimized to avoid allocs
1737 process_sub_st = self.status_array_pool.pop()
1738 else:
1739 process_sub_st = StatusArray.CreateNull()
1740
1741 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1742 try:
1743 status = self._Dispatch(node, cmd_st)
1744 except error.FailGlob as e:
1745 if not e.HasLocation(): # Last resort!
1746 e.location = self.mem.GetFallbackLocation()
1747 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1748 status = 1 # another redirect word eval error
1749 cmd_st.check_errexit = True # failglob + errexit
1750
1751 # Now we've waited for process subs
1752
1753 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1754 # @_pipeline_status
1755 pipe_status = cmd_st.pipe_status
1756 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1757 # makes it annoying to check both _process_sub_status and
1758 # _pipeline_status
1759
1760 errexit_loc = loc.Missing # type: loc_t
1761 if pipe_status is not None:
1762 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1763 # for a REAL pipeline (but not singleton pipelines)
1764 assert status == -1, (
1765 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1766 status)
1767
1768 self.mem.SetPipeStatus(pipe_status)
1769
1770 if self.exec_opts.pipefail():
1771 # The status is that of the last command that is non-zero.
1772 status = 0
1773 for i, st in enumerate(pipe_status):
1774 if st != 0:
1775 status = st
1776 errexit_loc = cmd_st.pipe_locs[i]
1777 else:
1778 # The status is that of last command, period.
1779 status = pipe_status[-1]
1780
1781 if cmd_st.pipe_negated:
1782 status = 1 if status == 0 else 0
1783
1784 # Compute status from _process_sub_status
1785 if process_sub_st.codes is None:
1786 # Optimized to avoid allocs
1787 self.status_array_pool.append(process_sub_st)
1788 else:
1789 codes = process_sub_st.codes
1790 self.mem.SetProcessSubStatus(codes)
1791 if status == 0 and self.exec_opts.process_sub_fail():
1792 # Choose the LAST non-zero status, consistent with pipefail above.
1793 for i, st in enumerate(codes):
1794 if st != 0:
1795 status = st
1796 errexit_loc = process_sub_st.locs[i]
1797
1798 self.mem.SetLastStatus(status)
1799
1800 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1801 # However, any bash construct can appear in a pipeline. So it's easier
1802 # just to put it at the end, instead of after every node.
1803 #
1804 # Possible exceptions:
1805 # - function def (however this always exits 0 anyway)
1806 # - assignment - its result should be the result of the RHS?
1807 # - e.g. arith sub, command sub? I don't want arith sub.
1808 # - ControlFlow: always raises, it has no status.
1809 if cmd_st.check_errexit:
1810 #log('cmd_st %s', cmd_st)
1811 self._CheckStatus(status, cmd_st, node, errexit_loc)
1812
1813 return status
1814
1815 def _ExecuteList(self, children):
1816 # type: (List[command_t]) -> int
1817 status = 0 # for empty list
1818 for child in children:
1819 # last status wins
1820 status = self._Execute(child)
1821 return status
1822
1823 def LastStatus(self):
1824 # type: () -> int
1825 """For main_loop.py to determine the exit code of the shell itself."""
1826 return self.mem.LastStatus()
1827
1828 def _NoForkLast(self, node):
1829 # type: (command_t) -> None
1830
1831 if 0:
1832 log('optimizing')
1833 node.PrettyPrint(sys.stderr)
1834 log('')
1835
1836 UP_node = node
1837 with tagswitch(node) as case:
1838 if case(command_e.Simple):
1839 node = cast(command.Simple, UP_node)
1840 node.do_fork = False
1841 if 0:
1842 log('Simple optimized')
1843
1844 elif case(command_e.Pipeline):
1845 node = cast(command.Pipeline, UP_node)
1846 if node.negated is None:
1847 #log ('pipe')
1848 self._NoForkLast(node.children[-1])
1849
1850 elif case(command_e.Sentence):
1851 node = cast(command.Sentence, UP_node)
1852 self._NoForkLast(node.child)
1853
1854 elif case(command_e.CommandList):
1855 # Subshells start with CommandList, even if there's only one.
1856 node = cast(command.CommandList, UP_node)
1857 self._NoForkLast(node.children[-1])
1858
1859 elif case(command_e.BraceGroup):
1860 # TODO: What about redirects?
1861 node = cast(BraceGroup, UP_node)
1862 self._NoForkLast(node.children[-1])
1863
1864 def _RemoveSubshells(self, node):
1865 # type: (command_t) -> command_t
1866 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1867
1868 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1869 be correct otherwise.
1870 """
1871 UP_node = node
1872 with tagswitch(node) as case:
1873 if case(command_e.Subshell):
1874 node = cast(command.Subshell, UP_node)
1875 # Optimize ( ( date ) ) etc.
1876 return self._RemoveSubshells(node.child)
1877 return node
1878
1879 def ExecuteAndCatch(self, node, cmd_flags=0):
1880 # type: (command_t, int) -> Tuple[bool, bool]
1881 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1882
1883 Args:
1884 node: LST subtree
1885 optimize: Whether to exec the last process rather than fork/exec
1886
1887 Returns:
1888 TODO: use enum 'why' instead of the 2 booleans
1889
1890 Used by
1891 - main_loop.py.
1892 - SubProgramThunk for pipelines, subshell, command sub, process sub
1893 - TODO: Signals besides EXIT trap
1894
1895 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
1896 finally_exit boolean. We use a different algorithm.
1897 """
1898 if cmd_flags & Optimize:
1899 node = self._RemoveSubshells(node)
1900 self._NoForkLast(node) # turn the last ones into exec
1901
1902 if 0:
1903 log('after opt:')
1904 node.PrettyPrint()
1905 log('')
1906
1907 is_return = False
1908 is_fatal = False
1909 is_errexit = False
1910
1911 err = None # type: error.FatalRuntime
1912 status = -1 # uninitialized
1913
1914 try:
1915 if cmd_flags & NoDebugTrap:
1916 with state.ctx_Option(self.mutable_opts,
1917 [option_i._no_debug_trap], True):
1918 status = self._Execute(node)
1919 else:
1920 status = self._Execute(node)
1921 except vm.IntControlFlow as e:
1922 if cmd_flags & RaiseControlFlow:
1923 raise # 'eval break' and 'source return.sh', etc.
1924 else:
1925 # Return at top level is OK, unlike in bash.
1926 if e.IsReturn():
1927 is_return = True
1928 status = e.StatusCode()
1929 else:
1930 # TODO: This error message is invalid. Can also happen in eval.
1931 # We need a flag.
1932
1933 # Invalid control flow
1934 self.errfmt.Print_(
1935 "Loop and control flow can't be in different processes",
1936 blame_loc=e.token)
1937 is_fatal = True
1938 # All shells exit 0 here. It could be hidden behind
1939 # strict_control_flow if the incompatibility causes problems.
1940 status = 1
1941 except error.Parse as e:
1942 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
1943 raise
1944 except error.ErrExit as e:
1945 err = e
1946 is_errexit = True
1947 except error.FatalRuntime as e:
1948 err = e
1949
1950 if err:
1951 status = err.ExitStatus()
1952
1953 is_fatal = True
1954 # Do this before unwinding stack
1955 self.dumper.MaybeRecord(self, err)
1956
1957 if not err.HasLocation(): # Last resort!
1958 #log('Missing location')
1959 err.location = self.mem.GetFallbackLocation()
1960 #log('%s', err.location)
1961
1962 if is_errexit:
1963 if self.exec_opts.verbose_errexit():
1964 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
1965 posix.getpid())
1966 else:
1967 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
1968
1969 assert status >= 0, 'Should have been initialized'
1970
1971 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
1972 # created a crash dump. So we get 2 or more of them.
1973 self.dumper.MaybeDump(status)
1974
1975 self.mem.SetLastStatus(status)
1976 return is_return, is_fatal
1977
1978 def EvalCommand(self, block):
1979 # type: (command_t) -> int
1980 """For builtins to evaluate command args.
1981
1982 e.g. cd /tmp (x)
1983 """
1984 status = 0
1985 try:
1986 status = self._Execute(block) # can raise FatalRuntimeError, etc.
1987 except vm.IntControlFlow as e: # A block is more like a function.
1988 # return in a block
1989 if e.IsReturn():
1990 status = e.StatusCode()
1991 else:
1992 e_die('Unexpected control flow in block', e.token)
1993
1994 return status
1995
1996 def MaybeRunExitTrap(self, mut_status):
1997 # type: (IntParamBox) -> None
1998 """If an EXIT trap handler exists, run it.
1999
2000 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2001 all bash/dash/mksh seem to agree on it. See cases in
2002 builtin-trap.test.sh.
2003
2004 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2005 of this awkward interface. But that's true in Python and not C!
2006
2007 Could use i & (n-1) == i & 255 because we have a power of 2.
2008 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2009 """
2010 node = self.trap_state.GetHook('EXIT') # type: command_t
2011 if node:
2012 # NOTE: Don't set option_i._running_trap, because that's for
2013 # RunPendingTraps() in the MAIN LOOP
2014 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2015 try:
2016 is_return, is_fatal = self.ExecuteAndCatch(node)
2017 except util.UserExit as e: # explicit exit
2018 mut_status.i = e.status
2019 return
2020 if is_return: # explicit 'return' in the trap handler!
2021 mut_status.i = self.LastStatus()
2022
2023 def _MaybeRunDebugTrap(self):
2024 # type: () -> None
2025 """If a DEBUG trap handler exists, run it."""
2026
2027 # Fix lastpipe / job control / DEBUG trap interaction
2028 if self.exec_opts._no_debug_trap():
2029 return
2030
2031 # Don't run recursively run traps, etc.
2032 if not self.mem.ShouldRunDebugTrap():
2033 return
2034
2035 node = self.trap_state.GetHook('DEBUG') # type: command_t
2036 if node:
2037 # NOTE: Don't set option_i._running_trap, because that's for
2038 # RunPendingTraps() in the MAIN LOOP
2039
2040 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2041 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2042 # for SetTokenForLine $LINENO
2043 with state.ctx_DebugTrap(self.mem):
2044 # Don't catch util.UserExit, etc.
2045 self._Execute(node)
2046
2047 def _MaybeRunErrTrap(self):
2048 # type: () -> None
2049 """If a ERR trap handler exists, run it."""
2050
2051 # Prevent infinite recursion
2052 if self.mem.running_err_trap:
2053 return
2054
2055 if self.mutable_opts.ErrExitIsDisabled():
2056 return
2057
2058 if self.mem.InsideFunction():
2059 return
2060
2061 node = self.trap_state.GetHook('ERR') # type: command_t
2062 if node:
2063 # NOTE: Don't set option_i._running_trap, because that's for
2064 # RunPendingTraps() in the MAIN LOOP
2065
2066 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2067 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2068 with state.ctx_ErrTrap(self.mem):
2069 self._Execute(node)
2070
2071 def RunProc(self, proc, cmd_val):
2072 # type: (value.Proc, cmd_value.Argv) -> int
2073 """Run procs aka "shell functions".
2074
2075 For SimpleCommand and registered completion hooks.
2076 """
2077 sig = proc.sig
2078 if sig.tag() == proc_sig_e.Closed:
2079 # We're binding named params. User should use @rest. No 'shift'.
2080 proc_argv = [] # type: List[str]
2081 else:
2082 proc_argv = cmd_val.argv[1:]
2083
2084 # Hm this sets "$@". TODO: Set ARGV only
2085 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2086 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2087
2088 # Redirects still valid for functions.
2089 # Here doc causes a pipe and Process(SubProgramThunk).
2090 try:
2091 status = self._Execute(proc.body)
2092 except vm.IntControlFlow as e:
2093 if e.IsReturn():
2094 status = e.StatusCode()
2095 else:
2096 # break/continue used in the wrong place.
2097 e_die(
2098 'Unexpected %r (in proc call)' %
2099 lexer.TokenVal(e.token), e.token)
2100 except error.FatalRuntime as e:
2101 # Dump the stack before unwinding it
2102 self.dumper.MaybeRecord(self, e)
2103 raise
2104
2105 return status
2106
2107 def RunFuncForCompletion(self, proc, argv):
2108 # type: (value.Proc, List[str]) -> int
2109 """
2110 Args:
2111 argv: $1 $2 $3 ... not including $0
2112 """
2113 cmd_val = MakeBuiltinArgv(argv)
2114
2115 # TODO: Change this to run YSH procs and funcs too
2116 try:
2117 status = self.RunProc(proc, cmd_val)
2118 except error.FatalRuntime as e:
2119 self.errfmt.PrettyPrintError(e)
2120 status = e.ExitStatus()
2121 except vm.IntControlFlow as e:
2122 # shouldn't be able to exit the shell from a completion hook!
2123 # TODO: Avoid overwriting the prompt!
2124 self.errfmt.Print_('Attempted to exit from completion hook.',
2125 blame_loc=e.token)
2126
2127 status = 1
2128 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2129 return status
2130
2131
2132# vim: sw=4