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

2130 lines, 1321 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 else:
1056 # blame the right && or ||
1057 self._StrictErrExit(child)
1058 with state.ctx_ErrExit(self.mutable_opts, False, op):
1059 status = self._Execute(child)
1060
1061 i += 1
1062
1063 return status
1064
1065 def _DoWhileUntil(self, node):
1066 # type: (command.WhileUntil) -> int
1067 status = 0
1068 with ctx_LoopLevel(self):
1069 while True:
1070 try:
1071 # blame while/until spid
1072 b = self._EvalCondition(node.cond, node.keyword)
1073 if node.keyword.id == Id.KW_Until:
1074 b = not b
1075 if not b:
1076 break
1077 status = self._Execute(node.body) # last one wins
1078
1079 except vm.IntControlFlow as e:
1080 status = 0
1081 action = e.HandleLoop()
1082 if action == flow_e.Break:
1083 break
1084 elif action == flow_e.Raise:
1085 raise
1086
1087 return status
1088
1089 def _DoForEach(self, node):
1090 # type: (command.ForEach) -> int
1091
1092 # for the 2 kinds of shell loop
1093 iter_list = None # type: List[str]
1094
1095 # for YSH loop
1096 iter_expr = None # type: expr_t
1097 expr_blame = None # type: loc_t
1098
1099 iterable = node.iterable
1100 UP_iterable = iterable
1101
1102 with tagswitch(node.iterable) as case:
1103 if case(for_iter_e.Args):
1104 iter_list = self.mem.GetArgv()
1105
1106 elif case(for_iter_e.Words):
1107 iterable = cast(for_iter.Words, UP_iterable)
1108 words = braces.BraceExpandWords(iterable.words)
1109 iter_list = self.word_ev.EvalWordSequence(words)
1110
1111 elif case(for_iter_e.YshExpr):
1112 iterable = cast(for_iter.YshExpr, UP_iterable)
1113 iter_expr = iterable.e
1114 expr_blame = iterable.blame
1115
1116 n = len(node.iter_names)
1117 assert n > 0
1118
1119 i_name = None # type: Optional[LeftName]
1120 # required
1121 name1 = None # type: LeftName
1122 name2 = None # type: Optional[LeftName]
1123
1124 it2 = None # type: val_ops._ContainerIter
1125 if iter_list is None: # for_expr.YshExpr
1126 val = self.expr_ev.EvalExpr(iter_expr, expr_blame)
1127
1128 UP_val = val
1129 with tagswitch(val) as case:
1130 if case(value_e.List):
1131 val = cast(value.List, UP_val)
1132 it2 = val_ops.ListIterator(val)
1133
1134 if n == 1:
1135 name1 = location.LName(node.iter_names[0])
1136 elif n == 2:
1137 i_name = location.LName(node.iter_names[0])
1138 name1 = location.LName(node.iter_names[1])
1139 else:
1140 # This is similar to a parse error
1141 e_die_status(
1142 2,
1143 'List iteration expects at most 2 loop variables',
1144 node.keyword)
1145
1146 elif case(value_e.Dict):
1147 val = cast(value.Dict, UP_val)
1148 it2 = val_ops.DictIterator(val)
1149
1150 if n == 1:
1151 name1 = location.LName(node.iter_names[0])
1152 elif n == 2:
1153 name1 = location.LName(node.iter_names[0])
1154 name2 = location.LName(node.iter_names[1])
1155 elif n == 3:
1156 i_name = location.LName(node.iter_names[0])
1157 name1 = location.LName(node.iter_names[1])
1158 name2 = location.LName(node.iter_names[2])
1159 else:
1160 raise AssertionError()
1161
1162 elif case(value_e.Range):
1163 val = cast(value.Range, UP_val)
1164 it2 = val_ops.RangeIterator(val)
1165
1166 if n == 1:
1167 name1 = location.LName(node.iter_names[0])
1168 elif n == 2:
1169 i_name = location.LName(node.iter_names[0])
1170 name1 = location.LName(node.iter_names[1])
1171 else:
1172 e_die_status(
1173 2,
1174 'Range iteration expects at most 2 loop variables',
1175 node.keyword)
1176
1177 else:
1178 raise error.TypeErr(val, 'for loop expected List or Dict',
1179 node.keyword)
1180 else:
1181 #log('iter list %s', iter_list)
1182 it2 = val_ops.ArrayIter(iter_list)
1183
1184 if n == 1:
1185 name1 = location.LName(node.iter_names[0])
1186 elif n == 2:
1187 i_name = location.LName(node.iter_names[0])
1188 name1 = location.LName(node.iter_names[1])
1189 else:
1190 # This is similar to a parse error
1191 e_die_status(
1192 2, 'Argv iteration expects at most 2 loop variables',
1193 node.keyword)
1194
1195 status = 0 # in case we loop zero times
1196 with ctx_LoopLevel(self):
1197 while not it2.Done():
1198 self.mem.SetLocalName(name1, it2.FirstValue())
1199 if name2:
1200 self.mem.SetLocalName(name2, it2.SecondValue())
1201 if i_name:
1202 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1203
1204 # increment index before handling continue, etc.
1205 it2.Next()
1206
1207 try:
1208 status = self._Execute(node.body) # last one wins
1209 except vm.IntControlFlow as e:
1210 status = 0
1211 action = e.HandleLoop()
1212 if action == flow_e.Break:
1213 break
1214 elif action == flow_e.Raise:
1215 raise
1216
1217 return status
1218
1219 def _DoForExpr(self, node):
1220 # type: (command.ForExpr) -> int
1221
1222 status = 0
1223
1224 init = node.init
1225 for_cond = node.cond
1226 body = node.body
1227 update = node.update
1228
1229 self.arith_ev.Eval(init)
1230 with ctx_LoopLevel(self):
1231 while True:
1232 # We only accept integers as conditions
1233 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1234 if mops.Equal(cond_int, mops.ZERO): # false
1235 break
1236
1237 try:
1238 status = self._Execute(body)
1239 except vm.IntControlFlow as e:
1240 status = 0
1241 action = e.HandleLoop()
1242 if action == flow_e.Break:
1243 break
1244 elif action == flow_e.Raise:
1245 raise
1246
1247 self.arith_ev.Eval(update)
1248
1249 return status
1250
1251 def _DoShFunction(self, node):
1252 # type: (command.ShFunction) -> None
1253 if node.name in self.procs and not self.exec_opts.redefine_proc_func():
1254 e_die(
1255 "Function %s was already defined (redefine_proc_func)" %
1256 node.name, node.name_tok)
1257 self.procs[node.name] = value.Proc(node.name, node.name_tok,
1258 proc_sig.Open, node.body, None,
1259 True)
1260
1261 def _DoProc(self, node):
1262 # type: (Proc) -> None
1263 proc_name = lexer.TokenVal(node.name)
1264 if proc_name in self.procs and not self.exec_opts.redefine_proc_func():
1265 e_die(
1266 "Proc %s was already defined (redefine_proc_func)" % proc_name,
1267 node.name)
1268
1269 if node.sig.tag() == proc_sig_e.Closed:
1270 sig = cast(proc_sig.Closed, node.sig)
1271 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1272 else:
1273 proc_defaults = None
1274
1275 # no dynamic scope
1276 self.procs[proc_name] = value.Proc(proc_name, node.name, node.sig,
1277 node.body, proc_defaults, False)
1278
1279 def _DoFunc(self, node):
1280 # type: (Func) -> None
1281 name = lexer.TokenVal(node.name)
1282 lval = location.LName(name)
1283
1284 # Check that we haven't already defined a function
1285 cell = self.mem.GetCell(name, scope_e.LocalOnly)
1286 if cell and cell.val.tag() == value_e.Func:
1287 if self.exec_opts.redefine_proc_func():
1288 cell.readonly = False # Ensure we can unset the value
1289 did_unset = self.mem.Unset(lval, scope_e.LocalOnly)
1290 assert did_unset, name
1291 else:
1292 e_die(
1293 "Func %s was already defined (redefine_proc_func)" % name,
1294 node.name)
1295
1296 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1297 self.expr_ev, node)
1298 func_val = value.Func(name, node, pos_defaults, named_defaults, None)
1299
1300 self.mem.SetNamed(lval,
1301 func_val,
1302 scope_e.LocalOnly,
1303 flags=state.SetReadOnly)
1304
1305 def _DoIf(self, node):
1306 # type: (command.If) -> int
1307 status = -1
1308
1309 done = False
1310 for if_arm in node.arms:
1311 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1312 if b:
1313 status = self._ExecuteList(if_arm.action)
1314 done = True
1315 break
1316
1317 if not done and node.else_action is not None:
1318 status = self._ExecuteList(node.else_action)
1319
1320 assert status != -1, 'Should have been initialized'
1321 return status
1322
1323 def _DoCase(self, node):
1324 # type: (command.Case) -> int
1325
1326 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1327 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1328
1329 status = 0 # If there are no arms, it should be zero?
1330
1331 done = False # Should we try the next arm?
1332
1333 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1334 ignore_next_cond = False
1335
1336 for case_arm in node.arms:
1337 with tagswitch(case_arm.pattern) as case:
1338 if case(pat_e.Words):
1339 if to_match.tag() != value_e.Str:
1340 continue # A non-string `to_match` will never match a pat.Words
1341 to_match_str = cast(value.Str, to_match)
1342
1343 pat_words = cast(pat.Words, case_arm.pattern)
1344
1345 this_arm_matches = False
1346 if ignore_next_cond: # Special handling for ;&
1347 this_arm_matches = True
1348 ignore_next_cond = False
1349 else:
1350 for pat_word in pat_words.words:
1351 word_val = self.word_ev.EvalWordToString(
1352 pat_word, word_eval.QUOTE_FNMATCH)
1353
1354 if libc.fnmatch(word_val.s, to_match_str.s,
1355 fnmatch_flags):
1356 this_arm_matches = True
1357 break # Stop at first pattern
1358
1359 if this_arm_matches:
1360 status = self._ExecuteList(case_arm.action)
1361 done = True
1362
1363 # ;& and ;;& only apply to shell-style case
1364 if case_arm.right:
1365 id_ = case_arm.right.id
1366 if id_ == Id.Op_SemiAmp:
1367 # very weird semantic
1368 ignore_next_cond = True
1369 done = False
1370 elif id_ == Id.Op_DSemiAmp:
1371 # Keep going until next pattern
1372 done = False
1373
1374 elif case(pat_e.YshExprs):
1375 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1376
1377 for pat_expr in pat_exprs.exprs:
1378 expr_val = self.expr_ev.EvalExpr(
1379 pat_expr, case_arm.left)
1380
1381 if val_ops.ExactlyEqual(expr_val, to_match,
1382 case_arm.left):
1383 status = self._ExecuteList(case_arm.action)
1384 done = True
1385 break
1386
1387 elif case(pat_e.Eggex):
1388 eggex = cast(Eggex, case_arm.pattern)
1389 eggex_val = self.expr_ev.EvalEggex(eggex)
1390
1391 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1392 status = self._ExecuteList(case_arm.action)
1393 done = True
1394 break
1395
1396 elif case(pat_e.Else):
1397 status = self._ExecuteList(case_arm.action)
1398 done = True
1399 break
1400
1401 else:
1402 raise AssertionError()
1403
1404 if done: # first match wins
1405 break
1406
1407 return status
1408
1409 def _DoTimeBlock(self, node):
1410 # type: (command.TimeBlock) -> int
1411 # TODO:
1412 # - When do we need RUSAGE_CHILDREN?
1413 # - Respect TIMEFORMAT environment variable.
1414 # "If this variable is not set, Bash acts as if it had the value"
1415 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1416 # "A trailing newline is added when the format string is displayed."
1417
1418 s_real, s_user, s_sys = pyos.Time()
1419 status = self._Execute(node.pipeline)
1420 e_real, e_user, e_sys = pyos.Time()
1421 # note: mycpp doesn't support %.3f
1422 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1423
1424 return status
1425
1426 def _DoRedirect(self, node, cmd_st):
1427 # type: (command.Redirect, CommandStatus) -> int
1428
1429 status = 0
1430 redirects = [] # type: List[RedirValue]
1431
1432 try:
1433 for redir in node.redirects:
1434 redirects.append(self._EvalRedirect(redir))
1435 except error.RedirectEval as e:
1436 self.errfmt.PrettyPrintError(e)
1437 redirects = None
1438 except error.FailGlob as e: # e.g. echo hi > foo-*
1439 if not e.HasLocation():
1440 e.location = self.mem.GetFallbackLocation()
1441 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1442 redirects = None
1443
1444 if redirects is None:
1445 # Error evaluating redirect words
1446 status = 1
1447
1448 # Translation fix: redirect I/O errors may happen in a C++
1449 # destructor ~vm::ctx_Redirect, which means they must be signaled
1450 # by out params, not exceptions.
1451 io_errors = [] # type: List[error.IOError_OSError]
1452
1453 # If we evaluated redirects, apply/push them
1454 if status == 0:
1455 self.shell_ex.PushRedirects(redirects, io_errors)
1456 if len(io_errors):
1457 # core/process.py prints cryptic errors, so we repeat them
1458 # here. e.g. Bad File Descriptor
1459 self.errfmt.PrintMessage(
1460 'I/O error applying redirect: %s' %
1461 pyutil.strerror(io_errors[0]),
1462 self.mem.GetFallbackLocation())
1463 status = 1
1464
1465 # If we applied redirects successfully, run the command_t, and pop
1466 # them.
1467 if status == 0:
1468 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1469 status = self._Execute(node.child)
1470 if len(io_errors):
1471 # It would be better to point to the right redirect
1472 # operator, but we don't track it specifically
1473 e_die("Fatal error popping redirect: %s" %
1474 pyutil.strerror(io_errors[0]))
1475
1476 return status
1477
1478 def _Dispatch(self, node, cmd_st):
1479 # type: (command_t, CommandStatus) -> int
1480 """Switch on the command_t variants and execute them."""
1481
1482 # If we call RunCommandSub in a recursive call to the executor, this will
1483 # be set true (if strict_errexit is false). But it only lasts for one
1484 # command.
1485 probe('cmd_eval', '_Dispatch', node.tag())
1486 self.check_command_sub_status = False
1487
1488 UP_node = node
1489 with tagswitch(node) as case:
1490 if case(command_e.Simple): # LEAF command
1491 node = cast(command.Simple, UP_node)
1492
1493 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1494 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1495 # TODO: blame_tok should always be set.
1496 if node.blame_tok is not None:
1497 self.mem.SetTokenForLine(node.blame_tok)
1498
1499 self._MaybeRunDebugTrap()
1500 cmd_st.check_errexit = True
1501 status = self._DoSimple(node, cmd_st)
1502
1503 elif case(command_e.ExpandedAlias):
1504 node = cast(command.ExpandedAlias, UP_node)
1505 status = self._DoExpandedAlias(node)
1506
1507 elif case(command_e.Sentence):
1508 node = cast(command.Sentence, UP_node)
1509
1510 # Don't check_errexit since this isn't a leaf command
1511 if node.terminator.id == Id.Op_Semi:
1512 status = self._Execute(node.child)
1513 else:
1514 status = self.shell_ex.RunBackgroundJob(node.child)
1515
1516 elif case(command_e.Redirect):
1517 node = cast(command.Redirect, UP_node)
1518
1519 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1520 # dash/ash
1521 cmd_st.check_errexit = True
1522 status = self._DoRedirect(node, cmd_st)
1523
1524 elif case(command_e.Pipeline):
1525 node = cast(command.Pipeline, UP_node)
1526 status = self._DoPipeline(node, cmd_st)
1527
1528 elif case(command_e.Subshell):
1529 node = cast(command.Subshell, UP_node)
1530
1531 # This is a leaf from the parent process POV
1532 cmd_st.check_errexit = True
1533 status = self.shell_ex.RunSubshell(node.child)
1534
1535 elif case(command_e.DBracket): # LEAF command
1536 node = cast(command.DBracket, UP_node)
1537
1538 self.mem.SetTokenForLine(node.left)
1539 self._MaybeRunDebugTrap()
1540
1541 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1542
1543 cmd_st.check_errexit = True
1544 cmd_st.show_code = True # this is a "leaf" for errors
1545 result = self.bool_ev.EvalB(node.expr)
1546 status = 0 if result else 1
1547
1548 elif case(command_e.DParen): # LEAF command
1549 node = cast(command.DParen, UP_node)
1550
1551 self.mem.SetTokenForLine(node.left)
1552 self._MaybeRunDebugTrap()
1553
1554 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1555
1556 cmd_st.check_errexit = True
1557 cmd_st.show_code = True # this is a "leaf" for errors
1558 i = self.arith_ev.EvalToBigInt(node.child)
1559 status = 1 if mops.Equal(i, mops.ZERO) else 0
1560
1561 elif case(command_e.ControlFlow): # LEAF command
1562 node = cast(command.ControlFlow, UP_node)
1563
1564 self.mem.SetTokenForLine(node.keyword)
1565 self._MaybeRunDebugTrap()
1566
1567 status = self._DoControlFlow(node)
1568
1569 elif case(command_e.VarDecl): # LEAF command
1570 node = cast(command.VarDecl, UP_node)
1571
1572 # Point to var name (bare assignment has no keyword)
1573 self.mem.SetTokenForLine(node.lhs[0].left)
1574 status = self._DoVarDecl(node)
1575
1576 elif case(command_e.Mutation): # LEAF command
1577 node = cast(command.Mutation, UP_node)
1578
1579 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1580 self._DoMutation(node)
1581 status = 0 # if no exception is thrown, it succeeds
1582
1583 elif case(command_e.ShAssignment): # LEAF command
1584 node = cast(command.ShAssignment, UP_node)
1585
1586 self.mem.SetTokenForLine(node.pairs[0].left)
1587 self._MaybeRunDebugTrap()
1588
1589 # Only unqualified assignment a=b
1590 status = self._DoShAssignment(node, cmd_st)
1591
1592 elif case(command_e.Expr): # YSH LEAF command
1593 node = cast(command.Expr, UP_node)
1594
1595 self.mem.SetTokenForLine(node.keyword)
1596 # YSH debug trap?
1597
1598 status = self._DoExpr(node)
1599
1600 elif case(command_e.Retval): # YSH LEAF command
1601 node = cast(command.Retval, UP_node)
1602
1603 self.mem.SetTokenForLine(node.keyword)
1604 # YSH debug trap? I think we don't want the debug trap in func
1605 # dialect, for speed?
1606
1607 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1608 raise vm.ValueControlFlow(node.keyword, val)
1609
1610 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1611 # DoGroup has 'do' and 'done' spids for translation.
1612 elif case(command_e.CommandList):
1613 node = cast(command.CommandList, UP_node)
1614 status = self._ExecuteList(node.children)
1615
1616 elif case(command_e.DoGroup):
1617 node = cast(command.DoGroup, UP_node)
1618 status = self._ExecuteList(node.children)
1619
1620 elif case(command_e.BraceGroup):
1621 node = cast(BraceGroup, UP_node)
1622 status = self._ExecuteList(node.children)
1623
1624 elif case(command_e.AndOr):
1625 node = cast(command.AndOr, UP_node)
1626 status = self._DoAndOr(node, cmd_st)
1627
1628 elif case(command_e.WhileUntil):
1629 node = cast(command.WhileUntil, UP_node)
1630
1631 self.mem.SetTokenForLine(node.keyword)
1632 status = self._DoWhileUntil(node)
1633
1634 elif case(command_e.ForEach):
1635 node = cast(command.ForEach, UP_node)
1636
1637 self.mem.SetTokenForLine(node.keyword)
1638 status = self._DoForEach(node)
1639
1640 elif case(command_e.ForExpr):
1641 node = cast(command.ForExpr, UP_node)
1642
1643 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1644 status = self._DoForExpr(node)
1645
1646 elif case(command_e.ShFunction):
1647 node = cast(command.ShFunction, UP_node)
1648 self._DoShFunction(node)
1649 status = 0
1650
1651 elif case(command_e.Proc):
1652 node = cast(Proc, UP_node)
1653 self._DoProc(node)
1654 status = 0
1655
1656 elif case(command_e.Func):
1657 node = cast(Func, UP_node)
1658
1659 # Needed for error, when the func is an existing variable name
1660 self.mem.SetTokenForLine(node.name)
1661
1662 self._DoFunc(node)
1663 status = 0
1664
1665 elif case(command_e.If):
1666 node = cast(command.If, UP_node)
1667
1668 # No SetTokenForLine() because
1669 # - $LINENO can't appear directly in 'if'
1670 # - 'if' doesn't directly cause errors
1671 # It will be taken care of by command.Simple, condition, etc.
1672 status = self._DoIf(node)
1673
1674 elif case(command_e.NoOp):
1675 status = 0 # make it true
1676
1677 elif case(command_e.Case):
1678 node = cast(command.Case, UP_node)
1679
1680 # Must set location for 'case $LINENO'
1681 self.mem.SetTokenForLine(node.case_kw)
1682 self._MaybeRunDebugTrap()
1683 status = self._DoCase(node)
1684
1685 elif case(command_e.TimeBlock):
1686 node = cast(command.TimeBlock, UP_node)
1687 status = self._DoTimeBlock(node)
1688
1689 else:
1690 raise NotImplementedError(node.tag())
1691
1692 # Return to caller. Note the only case that didn't set it was Pipeline,
1693 # which set cmd_st.pipe_status.
1694 return status
1695
1696 def RunPendingTraps(self):
1697 # type: () -> None
1698
1699 trap_nodes = self.trap_state.GetPendingTraps()
1700 if trap_nodes is not None:
1701 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1702 True):
1703 for trap_node in trap_nodes:
1704 # Isolate the exit status.
1705 with state.ctx_Registers(self.mem):
1706 # Trace it. TODO: Show the trap kind too
1707 with dev.ctx_Tracer(self.tracer, 'trap', None):
1708 self._Execute(trap_node)
1709
1710 def _Execute(self, node):
1711 # type: (command_t) -> int
1712 """Call _Dispatch(), and performs the errexit check.
1713
1714 Also runs trap handlers.
1715 """
1716 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1717 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1718 # and maybe throw an exception.
1719 self.RunPendingTraps()
1720
1721 # We only need this somewhat hacky check in osh-cpp since python's runtime
1722 # handles SIGINT for us in osh.
1723 if mylib.CPP:
1724 if self.signal_safe.PollSigInt():
1725 raise KeyboardInterrupt()
1726
1727 # Manual GC point before every statement
1728 mylib.MaybeCollect()
1729
1730 # Optimization: These 2 records have rarely-used lists, so we don't pass
1731 # alloc_lists=True. We create them on demand.
1732 cmd_st = CommandStatus.CreateNull()
1733 if len(self.status_array_pool):
1734 # Optimized to avoid allocs
1735 process_sub_st = self.status_array_pool.pop()
1736 else:
1737 process_sub_st = StatusArray.CreateNull()
1738
1739 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1740 try:
1741 status = self._Dispatch(node, cmd_st)
1742 except error.FailGlob as e:
1743 if not e.HasLocation(): # Last resort!
1744 e.location = self.mem.GetFallbackLocation()
1745 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1746 status = 1 # another redirect word eval error
1747 cmd_st.check_errexit = True # failglob + errexit
1748
1749 # Now we've waited for process subs
1750
1751 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1752 # @_pipeline_status
1753 pipe_status = cmd_st.pipe_status
1754 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1755 # makes it annoying to check both _process_sub_status and
1756 # _pipeline_status
1757
1758 errexit_loc = loc.Missing # type: loc_t
1759 if pipe_status is not None:
1760 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1761 # for a REAL pipeline (but not singleton pipelines)
1762 assert status == -1, (
1763 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1764 status)
1765
1766 self.mem.SetPipeStatus(pipe_status)
1767
1768 if self.exec_opts.pipefail():
1769 # The status is that of the last command that is non-zero.
1770 status = 0
1771 for i, st in enumerate(pipe_status):
1772 if st != 0:
1773 status = st
1774 errexit_loc = cmd_st.pipe_locs[i]
1775 else:
1776 # The status is that of last command, period.
1777 status = pipe_status[-1]
1778
1779 if cmd_st.pipe_negated:
1780 status = 1 if status == 0 else 0
1781
1782 # Compute status from _process_sub_status
1783 if process_sub_st.codes is None:
1784 # Optimized to avoid allocs
1785 self.status_array_pool.append(process_sub_st)
1786 else:
1787 codes = process_sub_st.codes
1788 self.mem.SetProcessSubStatus(codes)
1789 if status == 0 and self.exec_opts.process_sub_fail():
1790 # Choose the LAST non-zero status, consistent with pipefail above.
1791 for i, st in enumerate(codes):
1792 if st != 0:
1793 status = st
1794 errexit_loc = process_sub_st.locs[i]
1795
1796 self.mem.SetLastStatus(status)
1797
1798 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1799 # However, any bash construct can appear in a pipeline. So it's easier
1800 # just to put it at the end, instead of after every node.
1801 #
1802 # Possible exceptions:
1803 # - function def (however this always exits 0 anyway)
1804 # - assignment - its result should be the result of the RHS?
1805 # - e.g. arith sub, command sub? I don't want arith sub.
1806 # - ControlFlow: always raises, it has no status.
1807 if cmd_st.check_errexit:
1808 #log('cmd_st %s', cmd_st)
1809 self._CheckStatus(status, cmd_st, node, errexit_loc)
1810
1811 return status
1812
1813 def _ExecuteList(self, children):
1814 # type: (List[command_t]) -> int
1815 status = 0 # for empty list
1816 for child in children:
1817 # last status wins
1818 status = self._Execute(child)
1819 return status
1820
1821 def LastStatus(self):
1822 # type: () -> int
1823 """For main_loop.py to determine the exit code of the shell itself."""
1824 return self.mem.LastStatus()
1825
1826 def _NoForkLast(self, node):
1827 # type: (command_t) -> None
1828
1829 if 0:
1830 log('optimizing')
1831 node.PrettyPrint(sys.stderr)
1832 log('')
1833
1834 UP_node = node
1835 with tagswitch(node) as case:
1836 if case(command_e.Simple):
1837 node = cast(command.Simple, UP_node)
1838 node.do_fork = False
1839 if 0:
1840 log('Simple optimized')
1841
1842 elif case(command_e.Pipeline):
1843 node = cast(command.Pipeline, UP_node)
1844 if node.negated is None:
1845 #log ('pipe')
1846 self._NoForkLast(node.children[-1])
1847
1848 elif case(command_e.Sentence):
1849 node = cast(command.Sentence, UP_node)
1850 self._NoForkLast(node.child)
1851
1852 elif case(command_e.CommandList):
1853 # Subshells start with CommandList, even if there's only one.
1854 node = cast(command.CommandList, UP_node)
1855 self._NoForkLast(node.children[-1])
1856
1857 elif case(command_e.BraceGroup):
1858 # TODO: What about redirects?
1859 node = cast(BraceGroup, UP_node)
1860 self._NoForkLast(node.children[-1])
1861
1862 def _RemoveSubshells(self, node):
1863 # type: (command_t) -> command_t
1864 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1865
1866 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1867 be correct otherwise.
1868 """
1869 UP_node = node
1870 with tagswitch(node) as case:
1871 if case(command_e.Subshell):
1872 node = cast(command.Subshell, UP_node)
1873 # Optimize ( ( date ) ) etc.
1874 return self._RemoveSubshells(node.child)
1875 return node
1876
1877 def ExecuteAndCatch(self, node, cmd_flags=0):
1878 # type: (command_t, int) -> Tuple[bool, bool]
1879 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1880
1881 Args:
1882 node: LST subtree
1883 optimize: Whether to exec the last process rather than fork/exec
1884
1885 Returns:
1886 TODO: use enum 'why' instead of the 2 booleans
1887
1888 Used by
1889 - main_loop.py.
1890 - SubProgramThunk for pipelines, subshell, command sub, process sub
1891 - TODO: Signals besides EXIT trap
1892
1893 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
1894 finally_exit boolean. We use a different algorithm.
1895 """
1896 if cmd_flags & Optimize:
1897 node = self._RemoveSubshells(node)
1898 self._NoForkLast(node) # turn the last ones into exec
1899
1900 if 0:
1901 log('after opt:')
1902 node.PrettyPrint()
1903 log('')
1904
1905 is_return = False
1906 is_fatal = False
1907 is_errexit = False
1908
1909 err = None # type: error.FatalRuntime
1910 status = -1 # uninitialized
1911
1912 try:
1913 if cmd_flags & NoDebugTrap:
1914 with state.ctx_Option(self.mutable_opts,
1915 [option_i._no_debug_trap], True):
1916 status = self._Execute(node)
1917 else:
1918 status = self._Execute(node)
1919 except vm.IntControlFlow as e:
1920 if cmd_flags & RaiseControlFlow:
1921 raise # 'eval break' and 'source return.sh', etc.
1922 else:
1923 # Return at top level is OK, unlike in bash.
1924 if e.IsReturn():
1925 is_return = True
1926 status = e.StatusCode()
1927 else:
1928 # TODO: This error message is invalid. Can also happen in eval.
1929 # We need a flag.
1930
1931 # Invalid control flow
1932 self.errfmt.Print_(
1933 "Loop and control flow can't be in different processes",
1934 blame_loc=e.token)
1935 is_fatal = True
1936 # All shells exit 0 here. It could be hidden behind
1937 # strict_control_flow if the incompatibility causes problems.
1938 status = 1
1939 except error.Parse as e:
1940 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
1941 raise
1942 except error.ErrExit as e:
1943 err = e
1944 is_errexit = True
1945 except error.FatalRuntime as e:
1946 err = e
1947
1948 if err:
1949 status = err.ExitStatus()
1950
1951 is_fatal = True
1952 # Do this before unwinding stack
1953 self.dumper.MaybeRecord(self, err)
1954
1955 if not err.HasLocation(): # Last resort!
1956 #log('Missing location')
1957 err.location = self.mem.GetFallbackLocation()
1958 #log('%s', err.location)
1959
1960 if is_errexit:
1961 if self.exec_opts.verbose_errexit():
1962 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
1963 posix.getpid())
1964 else:
1965 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
1966
1967 assert status >= 0, 'Should have been initialized'
1968
1969 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
1970 # created a crash dump. So we get 2 or more of them.
1971 self.dumper.MaybeDump(status)
1972
1973 self.mem.SetLastStatus(status)
1974 return is_return, is_fatal
1975
1976 def EvalCommand(self, block):
1977 # type: (command_t) -> int
1978 """For builtins to evaluate command args.
1979
1980 e.g. cd /tmp (x)
1981 """
1982 status = 0
1983 try:
1984 status = self._Execute(block) # can raise FatalRuntimeError, etc.
1985 except vm.IntControlFlow as e: # A block is more like a function.
1986 # return in a block
1987 if e.IsReturn():
1988 status = e.StatusCode()
1989 else:
1990 e_die('Unexpected control flow in block', e.token)
1991
1992 return status
1993
1994 def MaybeRunExitTrap(self, mut_status):
1995 # type: (IntParamBox) -> None
1996 """If an EXIT trap handler exists, run it.
1997
1998 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
1999 all bash/dash/mksh seem to agree on it. See cases in
2000 builtin-trap.test.sh.
2001
2002 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2003 of this awkward interface. But that's true in Python and not C!
2004
2005 Could use i & (n-1) == i & 255 because we have a power of 2.
2006 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2007 """
2008 node = self.trap_state.GetHook('EXIT') # type: command_t
2009 if node:
2010 # NOTE: Don't set option_i._running_trap, because that's for
2011 # RunPendingTraps() in the MAIN LOOP
2012 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2013 try:
2014 is_return, is_fatal = self.ExecuteAndCatch(node)
2015 except util.UserExit as e: # explicit exit
2016 mut_status.i = e.status
2017 return
2018 if is_return: # explicit 'return' in the trap handler!
2019 mut_status.i = self.LastStatus()
2020
2021 def _MaybeRunDebugTrap(self):
2022 # type: () -> None
2023 """If a DEBUG trap handler exists, run it."""
2024
2025 # Fix lastpipe / job control / DEBUG trap interaction
2026 if self.exec_opts._no_debug_trap():
2027 return
2028
2029 # Don't run recursively run traps, etc.
2030 if not self.mem.ShouldRunDebugTrap():
2031 return
2032
2033 node = self.trap_state.GetHook('DEBUG') # type: command_t
2034 if node:
2035 # NOTE: Don't set option_i._running_trap, because that's for
2036 # RunPendingTraps() in the MAIN LOOP
2037
2038 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2039 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2040 # for SetTokenForLine $LINENO
2041 with state.ctx_DebugTrap(self.mem):
2042 # Don't catch util.UserExit, etc.
2043 self._Execute(node)
2044
2045 def _MaybeRunErrTrap(self):
2046 # type: () -> None
2047 """If a ERR trap handler exists, run it."""
2048
2049 # Prevent infinite recursion
2050 if self.mem.running_err_trap:
2051 return
2052
2053 if self.mutable_opts.ErrExitIsDisabled():
2054 return
2055
2056 if self.mem.InsideFunction():
2057 return
2058
2059 node = self.trap_state.GetHook('ERR') # type: command_t
2060 if node:
2061 # NOTE: Don't set option_i._running_trap, because that's for
2062 # RunPendingTraps() in the MAIN LOOP
2063
2064 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2065 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2066 with state.ctx_ErrTrap(self.mem):
2067 self._Execute(node)
2068
2069 def RunProc(self, proc, cmd_val):
2070 # type: (value.Proc, cmd_value.Argv) -> int
2071 """Run procs aka "shell functions".
2072
2073 For SimpleCommand and registered completion hooks.
2074 """
2075 sig = proc.sig
2076 if sig.tag() == proc_sig_e.Closed:
2077 # We're binding named params. User should use @rest. No 'shift'.
2078 proc_argv = [] # type: List[str]
2079 else:
2080 proc_argv = cmd_val.argv[1:]
2081
2082 # Hm this sets "$@". TODO: Set ARGV only
2083 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2084 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2085
2086 # Redirects still valid for functions.
2087 # Here doc causes a pipe and Process(SubProgramThunk).
2088 try:
2089 status = self._Execute(proc.body)
2090 except vm.IntControlFlow as e:
2091 if e.IsReturn():
2092 status = e.StatusCode()
2093 else:
2094 # break/continue used in the wrong place.
2095 e_die(
2096 'Unexpected %r (in proc call)' %
2097 lexer.TokenVal(e.token), e.token)
2098 except error.FatalRuntime as e:
2099 # Dump the stack before unwinding it
2100 self.dumper.MaybeRecord(self, e)
2101 raise
2102
2103 return status
2104
2105 def RunFuncForCompletion(self, proc, argv):
2106 # type: (value.Proc, List[str]) -> int
2107 """
2108 Args:
2109 argv: $1 $2 $3 ... not including $0
2110 """
2111 cmd_val = MakeBuiltinArgv(argv)
2112
2113 # TODO: Change this to run YSH procs and funcs too
2114 try:
2115 status = self.RunProc(proc, cmd_val)
2116 except error.FatalRuntime as e:
2117 self.errfmt.PrettyPrintError(e)
2118 status = e.ExitStatus()
2119 except vm.IntControlFlow as e:
2120 # shouldn't be able to exit the shell from a completion hook!
2121 # TODO: Avoid overwriting the prompt!
2122 self.errfmt.Print_('Attempted to exit from completion hook.',
2123 blame_loc=e.token)
2124
2125 status = 1
2126 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2127 return status
2128
2129
2130# vim: sw=4