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

2129 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, but its exit code will always be 0 without command subs
168 # - Naively, (non-singleton) pipelines could be allowed because pipefail.
169 # BUT could be a proc executed inside a child process, which causes a
170 # problem: the strict_errexit check has to occur at runtime and there's
171 # no way to signal it ot the parent.
172
173 return True
174
175
176def PlusEquals(old_val, val):
177 # type: (value_t, value_t) -> value_t
178 """Implement s+=val, typeset s+=val, etc."""
179
180 UP_old_val = old_val
181 UP_val = val
182
183 tag = val.tag()
184
185 with tagswitch(old_val) as case:
186 if case(value_e.Undef):
187 pass # val is RHS
188
189 elif case(value_e.Str):
190 if tag == value_e.Str:
191 old_val = cast(value.Str, UP_old_val)
192 str_to_append = cast(value.Str, UP_val)
193 val = value.Str(old_val.s + str_to_append.s)
194
195 elif tag == value_e.BashArray:
196 e_die("Can't append array to string")
197
198 else:
199 raise AssertionError() # parsing should prevent this
200
201 elif case(value_e.BashArray):
202 if tag == value_e.Str:
203 e_die("Can't append string to array")
204
205 elif tag == value_e.BashArray:
206 old_val = cast(value.BashArray, UP_old_val)
207 to_append = cast(value.BashArray, UP_val)
208
209 # TODO: MUTATE the existing value for efficiency?
210 strs = [] # type: List[str]
211 strs.extend(old_val.strs)
212 strs.extend(to_append.strs)
213 val = value.BashArray(strs)
214
215 else:
216 raise AssertionError() # parsing should prevent this
217
218 elif case(value_e.BashAssoc):
219 # TODO: Could try to match bash, it will append to ${A[0]}
220 pass
221
222 else:
223 e_die("Can't append to value of type %s" % ui.ValType(old_val))
224
225 return val
226
227
228class ctx_LoopLevel(object):
229 """For checking for invalid control flow."""
230
231 def __init__(self, cmd_ev):
232 # type: (CommandEvaluator) -> None
233 cmd_ev.loop_level += 1
234 self.cmd_ev = cmd_ev
235
236 def __enter__(self):
237 # type: () -> None
238 pass
239
240 def __exit__(self, type, value, traceback):
241 # type: (Any, Any, Any) -> None
242 self.cmd_ev.loop_level -= 1
243
244
245class CommandEvaluator(object):
246 """Executes the program by tree-walking.
247
248 It also does some double-dispatch by passing itself into Eval() for
249 Compound/WordPart.
250 """
251
252 def __init__(
253 self,
254 mem, # type: state.Mem
255 exec_opts, # type: optview.Exec
256 errfmt, # type: ui.ErrorFormatter
257 procs, # type: Dict[str, value.Proc]
258 assign_builtins, # type: Dict[builtin_t, _AssignBuiltin]
259 arena, # type: Arena
260 cmd_deps, # type: Deps
261 trap_state, # type: trap_osh.TrapState
262 signal_safe, # type: pyos.SignalSafe
263 ):
264 # type: (...) -> None
265 """
266 Args:
267 mem: Mem instance for storing variables
268 procs: dict of SHELL functions or 'procs'
269 builtins: dict of builtin callables
270 TODO: This should only be for assignment builtins?
271 cmd_deps: A bundle of stateless code
272 """
273 self.shell_ex = None # type: _Executor
274 self.arith_ev = None # type: sh_expr_eval.ArithEvaluator
275 self.bool_ev = None # type: sh_expr_eval.BoolEvaluator
276 self.expr_ev = None # type: expr_eval.ExprEvaluator
277 self.word_ev = None # type: word_eval.AbstractWordEvaluator
278 self.tracer = None # type: dev.Tracer
279
280 self.mem = mem
281 # This is for shopt and set -o. They are initialized by flags.
282 self.exec_opts = exec_opts
283 self.errfmt = errfmt
284 self.procs = procs
285 self.assign_builtins = assign_builtins
286 self.arena = arena
287
288 self.mutable_opts = cmd_deps.mutable_opts
289 self.dumper = cmd_deps.dumper
290 self.debug_f = cmd_deps.debug_f # Used by ShellFuncAction too
291
292 self.trap_state = trap_state
293 self.signal_safe = signal_safe
294
295 self.loop_level = 0 # for detecting bad top-level break/continue
296 self.check_command_sub_status = False # a hack. Modified by ShellExecutor
297
298 self.status_array_pool = [] # type: List[StatusArray]
299
300 def CheckCircularDeps(self):
301 # type: () -> None
302 assert self.arith_ev is not None
303 assert self.bool_ev is not None
304 # Disabled for push OSH
305 #assert self.expr_ev is not None
306 assert self.word_ev is not None
307
308 def _RunAssignBuiltin(self, cmd_val):
309 # type: (cmd_value.Assign) -> int
310 """Run an assignment builtin.
311
312 Except blocks copied from RunBuiltin.
313 """
314 builtin_func = self.assign_builtins.get(cmd_val.builtin_id)
315 if builtin_func is None:
316 # This only happens with alternative Oils interpreters.
317 e_die("Assignment builtin %r not configured" % cmd_val.argv[0],
318 cmd_val.arg_locs[0])
319
320 io_errors = [] # type: List[error.IOError_OSError]
321 with vm.ctx_FlushStdout(io_errors):
322 with ui.ctx_Location(self.errfmt, cmd_val.arg_locs[0]):
323 try:
324 status = builtin_func.Run(cmd_val)
325 except (IOError, OSError) as e:
326 # e.g. declare -p > /dev/full
327 self.errfmt.PrintMessage(
328 '%s builtin I/O error: %s' %
329 (cmd_val.argv[0], pyutil.strerror(e)),
330 cmd_val.arg_locs[0])
331 return 1
332 except error.Usage as e: # Copied from RunBuiltin
333 arg0 = cmd_val.argv[0]
334 self.errfmt.PrefixPrint(e.msg, '%r ' % arg0, e.location)
335 return 2 # consistent error code for usage error
336
337 if len(io_errors): # e.g. declare -p > /dev/full
338 self.errfmt.PrintMessage(
339 '%s builtin I/O: %s' %
340 (cmd_val.argv[0], pyutil.strerror(io_errors[0])),
341 cmd_val.arg_locs[0])
342 return 1
343
344 return status
345
346 def _CheckStatus(self, status, cmd_st, node, default_loc):
347 # type: (int, CommandStatus, command_t, loc_t) -> None
348 """Raises error.ErrExit, maybe with location info attached."""
349
350 assert status >= 0, status
351
352 if status == 0:
353 return # Nothing to do
354
355 self._MaybeRunErrTrap()
356
357 if self.exec_opts.errexit():
358 # NOTE: Sometimes we print 2 errors
359 # - 'type -z' has a UsageError with location, then errexit
360 # - '> /nonexistent' has an I/O error, then errexit
361 # - Pipelines and subshells are compound. Commands within them fail.
362 # - however ( exit 33 ) only prints one message.
363 #
364 # But we will want something like 'false' to have location info.
365
366 UP_node = node
367 with tagswitch(node) as case:
368 if case(command_e.ShAssignment):
369 node = cast(command.ShAssignment, UP_node)
370 cmd_st.show_code = True # leaf
371 # Note: we show errors from assignments a=$(false) rarely: when
372 # errexit, inherit_errexit, verbose_errexit are on, but
373 # command_sub_errexit is off!
374
375 # Note: a subshell often doesn't fail on its own.
376 elif case(command_e.Subshell):
377 node = cast(command.Subshell, UP_node)
378 cmd_st.show_code = True # not sure about this, e.g. ( exit 42 )
379
380 elif case(command_e.Pipeline):
381 node = cast(command.Pipeline, UP_node)
382 cmd_st.show_code = True # not sure about this
383 # TODO: We should show which element of the pipeline failed!
384
385 desc = command_str(node.tag())
386
387 # Override location if explicitly passed.
388 # Note: this produces better results for process sub
389 # echo <(sort x)
390 # and different results for some pipelines:
391 # { ls; false; } | wc -l; echo hi # Point to | or first { ?
392 if default_loc.tag() != loc_e.Missing:
393 blame_loc = default_loc # type: loc_t
394 else:
395 blame_loc = location.TokenForCommand(node)
396
397 msg = '%s failed with status %d' % (desc, status)
398 raise error.ErrExit(status,
399 msg,
400 blame_loc,
401 show_code=cmd_st.show_code)
402
403 def _EvalRedirect(self, r):
404 # type: (Redir) -> RedirValue
405
406 result = RedirValue(r.op.id, r.op, r.loc, None)
407
408 arg = r.arg
409 UP_arg = arg
410 with tagswitch(arg) as case:
411 if case(redir_param_e.Word):
412 arg_word = cast(CompoundWord, UP_arg)
413
414 # Note: needed for redirect like 'echo foo > x$LINENO'
415 self.mem.SetTokenForLine(r.op)
416
417 # Could be computed at parse time?
418 redir_type = consts.RedirArgType(r.op.id)
419
420 if redir_type == redir_arg_type_e.Path:
421 # Redirects with path arguments are evaluated in a special
422 # way. bash and zsh allow globbing a path, but
423 # dash/ash/mksh don't.
424 #
425 # If there are multiple files, zsh opens BOTH, but bash
426 # makes the command fail with status 1. We mostly follow
427 # bash behavior.
428
429 # These don't match bash/zsh behavior
430 # val = self.word_ev.EvalWordToString(arg_word)
431 # val, has_extglob = self.word_ev.EvalWordToPattern(arg_word)
432 # Short-circuit with word_.StaticEval() also doesn't work
433 # with globs
434
435 # mycpp needs this explicit declaration
436 b = braces.BraceDetect(
437 arg_word) # type: Optional[word.BracedTree]
438 if b is not None:
439 raise error.RedirectEval(
440 'Brace expansion not allowed (try adding quotes)',
441 arg_word)
442
443 # Needed for globbing behavior
444 files = self.word_ev.EvalWordSequence([arg_word])
445
446 n = len(files)
447 if n == 0:
448 # happens in OSH on empty elision
449 # in YSH because simple_word_eval globs to zero
450 raise error.RedirectEval(
451 "Can't redirect to zero files", arg_word)
452 if n > 1:
453 raise error.RedirectEval(
454 "Can't redirect to more than one file", arg_word)
455
456 result.arg = redirect_arg.Path(files[0])
457 return result
458
459 elif redir_type == redir_arg_type_e.Desc: # e.g. 1>&2, 1>&-, 1>&2-
460 val = self.word_ev.EvalWordToString(arg_word)
461 t = val.s
462 if len(t) == 0:
463 raise error.RedirectEval(
464 "Redirect descriptor can't be empty", arg_word)
465 return None
466
467 try:
468 if t == '-':
469 result.arg = redirect_arg.CloseFd
470 elif t[-1] == '-':
471 target_fd = int(t[:-1])
472 result.arg = redirect_arg.MoveFd(target_fd)
473 else:
474 result.arg = redirect_arg.CopyFd(int(t))
475 except ValueError:
476 raise error.RedirectEval(
477 'Invalid descriptor %r. Expected D, -, or D- where D is an '
478 'integer' % t, arg_word)
479 return None
480
481 return result
482
483 elif redir_type == redir_arg_type_e.Here: # here word
484 val = self.word_ev.EvalWordToString(arg_word)
485 assert val.tag() == value_e.Str, val
486 # NOTE: bash and mksh both add \n
487 result.arg = redirect_arg.HereDoc(val.s + '\n')
488 return result
489
490 else:
491 raise AssertionError('Unknown redirect op')
492
493 elif case(redir_param_e.HereDoc):
494 arg = cast(redir_param.HereDoc, UP_arg)
495 w = CompoundWord(
496 arg.stdin_parts) # HACK: Wrap it in a word to eval
497 val = self.word_ev.EvalWordToString(w)
498 assert val.tag() == value_e.Str, val
499 result.arg = redirect_arg.HereDoc(val.s)
500 return result
501
502 else:
503 raise AssertionError('Unknown redirect type')
504
505 raise AssertionError('for -Wreturn-type in C++')
506
507 def _RunSimpleCommand(self, cmd_val, cmd_st, run_flags):
508 # type: (cmd_value_t, CommandStatus, int) -> int
509 """Private interface to run a simple command (including assignment)."""
510 UP_cmd_val = cmd_val
511 with tagswitch(UP_cmd_val) as case:
512 if case(cmd_value_e.Argv):
513 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
514 self.tracer.OnSimpleCommand(cmd_val.argv)
515 return self.shell_ex.RunSimpleCommand(cmd_val, cmd_st,
516 run_flags)
517
518 elif case(cmd_value_e.Assign):
519 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
520 self.tracer.OnAssignBuiltin(cmd_val)
521 return self._RunAssignBuiltin(cmd_val)
522
523 else:
524 raise AssertionError()
525
526 def _EvalTempEnv(self, more_env, flags):
527 # type: (List[EnvPair], int) -> None
528 """For FOO=1 cmd."""
529 for e_pair in more_env:
530 val = self.word_ev.EvalRhsWord(e_pair.val)
531 # Set each var so the next one can reference it. Example:
532 # FOO=1 BAR=$FOO ls /
533 self.mem.SetNamed(location.LName(e_pair.name),
534 val,
535 scope_e.LocalOnly,
536 flags=flags)
537
538 def _StrictErrExit(self, node):
539 # type: (command_t) -> None
540 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
541 return
542
543 if _HasManyStatuses(node):
544 node_str = ui.CommandType(node)
545 e_die(
546 "strict_errexit only allows simple commands in conditionals (got %s). "
547 % node_str, loc.Command(node))
548
549 def _StrictErrExitList(self, node_list):
550 # type: (List[command_t]) -> None
551 """Not allowed, too confusing:
552
553 if grep foo eggs.txt; grep bar eggs.txt; then echo hi fi
554 """
555 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
556 return
557
558 if len(node_list) > 1:
559 e_die(
560 "strict_errexit only allows a single command. Hint: use 'try'.",
561 loc.Command(node_list[0]))
562
563 assert len(node_list) > 0
564 node = node_list[0]
565 if _HasManyStatuses(node):
566 # TODO: consolidate error message with above
567 node_str = ui.CommandType(node)
568 e_die(
569 "strict_errexit only allows simple commands in conditionals (got %s). "
570 % node_str, loc.Command(node))
571
572 def _EvalCondition(self, cond, blame_tok):
573 # type: (condition_t, Token) -> bool
574 """
575 Args:
576 spid: for OSH conditions, where errexit was disabled -- e.g. if
577 for YSH conditions, it would be nice to blame the ( instead
578 """
579 b = False
580 UP_cond = cond
581 with tagswitch(cond) as case:
582 if case(condition_e.Shell):
583 cond = cast(condition.Shell, UP_cond)
584 self._StrictErrExitList(cond.commands)
585 with state.ctx_ErrExit(self.mutable_opts, False, blame_tok):
586 cond_status = self._ExecuteList(cond.commands)
587
588 b = cond_status == 0
589
590 elif case(condition_e.YshExpr):
591 cond = cast(condition.YshExpr, UP_cond)
592 obj = self.expr_ev.EvalExpr(cond.e, blame_tok)
593 b = val_ops.ToBool(obj)
594
595 return b
596
597 def _EvalCaseArg(self, arg, blame):
598 # type: (case_arg_t, loc_t) -> value_t
599 """Evaluate a `case_arg` into a `value_t` which can be matched on in a case
600 command.
601 """
602 UP_arg = arg
603 with tagswitch(arg) as case:
604 if case(case_arg_e.Word):
605 arg = cast(case_arg.Word, UP_arg)
606 return self.word_ev.EvalWordToString(arg.w)
607
608 elif case(case_arg_e.YshExpr):
609 arg = cast(case_arg.YshExpr, UP_arg)
610 return self.expr_ev.EvalExpr(arg.e, blame)
611
612 else:
613 raise NotImplementedError()
614
615 def _DoVarDecl(self, node):
616 # type: (command.VarDecl) -> int
617 # x = 'foo' in Hay blocks
618 if node.keyword is None:
619 # Note: there's only one LHS
620 lhs0 = node.lhs[0]
621 lval = LeftName(lhs0.name, lhs0.left)
622 assert node.rhs is not None, node
623 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
624
625 self.mem.SetNamed(lval,
626 val,
627 scope_e.LocalOnly,
628 flags=state.SetReadOnly)
629
630 else: # var or const
631 flags = (state.SetReadOnly
632 if node.keyword.id == Id.KW_Const else 0)
633
634 # var x, y does null initialization
635 if node.rhs is None:
636 for i, lhs_val in enumerate(node.lhs):
637 lval = LeftName(lhs_val.name, lhs_val.left)
638 self.mem.SetNamed(lval,
639 value.Null,
640 scope_e.LocalOnly,
641 flags=flags)
642 return 0
643
644 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
645 lvals = None # type: List[LeftName]
646 rhs_vals = None # type: List[value_t]
647
648 num_lhs = len(node.lhs)
649 if num_lhs == 1:
650 lhs0 = node.lhs[0]
651 lvals = [LeftName(lhs0.name, lhs0.left)]
652 rhs_vals = [right_val]
653 else:
654 items = val_ops.ToList(
655 right_val, 'Destructuring assignment expected List',
656 node.keyword)
657
658 num_rhs = len(items)
659 if num_lhs != num_rhs:
660 raise error.Expr(
661 'Got %d places on the left, but %d values on right' %
662 (num_lhs, num_rhs), node.keyword)
663
664 lvals = []
665 rhs_vals = []
666 for i, lhs_val in enumerate(node.lhs):
667 lval = LeftName(lhs_val.name, lhs_val.left)
668 lvals.append(lval)
669 rhs_vals.append(items[i])
670
671 for i, lval in enumerate(lvals):
672 rval = rhs_vals[i]
673 self.mem.SetNamed(lval, rval, scope_e.LocalOnly, flags=flags)
674
675 return 0
676
677 def _DoMutation(self, node):
678 # type: (command.Mutation) -> None
679
680 with switch(node.keyword.id) as case2:
681 if case2(Id.KW_SetVar):
682 which_scopes = scope_e.LocalOnly
683 elif case2(Id.KW_SetGlobal):
684 which_scopes = scope_e.GlobalOnly
685 else:
686 raise AssertionError(node.keyword.id)
687
688 if node.op.id == Id.Arith_Equal:
689 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
690
691 lvals = None # type: List[y_lvalue_t]
692 rhs_vals = None # type: List[value_t]
693
694 num_lhs = len(node.lhs)
695 if num_lhs == 1:
696 lvals = [self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)]
697 rhs_vals = [right_val]
698 else:
699 items = val_ops.ToList(
700 right_val, 'Destructuring assignment expected List',
701 node.keyword)
702
703 num_rhs = len(items)
704 if num_lhs != num_rhs:
705 raise error.Expr(
706 'Got %d places on the left, but %d values on the right'
707 % (num_lhs, num_rhs), node.keyword)
708
709 lvals = []
710 rhs_vals = []
711 for i, lhs_val in enumerate(node.lhs):
712 lvals.append(
713 self.expr_ev.EvalLhsExpr(lhs_val, which_scopes))
714 rhs_vals.append(items[i])
715
716 for i, lval in enumerate(lvals):
717 rval = rhs_vals[i]
718
719 # setvar mylist[0] = 42
720 # setvar mydict['key'] = 42
721 UP_lval = lval
722
723 if lval.tag() == y_lvalue_e.Local:
724 lval = cast(LeftName, UP_lval)
725
726 self.mem.SetNamed(lval, rval, which_scopes)
727
728 elif lval.tag() == y_lvalue_e.Container:
729 lval = cast(y_lvalue.Container, UP_lval)
730
731 obj = lval.obj
732 UP_obj = obj
733 with tagswitch(obj) as case:
734 if case(value_e.List):
735 obj = cast(value.List, UP_obj)
736 index = val_ops.ToInt(lval.index,
737 'List index should be Int',
738 loc.Missing)
739 obj.items[index] = rval
740
741 elif case(value_e.Dict):
742 obj = cast(value.Dict, UP_obj)
743 key = val_ops.ToStr(lval.index,
744 'Dict index should be Str',
745 loc.Missing)
746 obj.d[key] = rval
747
748 else:
749 raise error.TypeErr(
750 obj, "obj[index] expected List or Dict",
751 loc.Missing)
752
753 else:
754 raise AssertionError()
755
756 else:
757 # Checked in the parser
758 assert len(node.lhs) == 1
759
760 aug_lval = self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)
761 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
762
763 self.expr_ev.EvalAugmented(aug_lval, val, node.op, which_scopes)
764
765 def _DoSimple(self, node, cmd_st):
766 # type: (command.Simple, CommandStatus) -> int
767 probe('cmd_eval', '_DoSimple_enter')
768 cmd_st.check_errexit = True
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 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1431 # dash/ash
1432 cmd_st.check_errexit = True
1433
1434 status = 0
1435 redirects = [] # type: List[RedirValue]
1436
1437 try:
1438 for redir in node.redirects:
1439 redirects.append(self._EvalRedirect(redir))
1440 except error.RedirectEval as e:
1441 self.errfmt.PrettyPrintError(e)
1442 redirects = None
1443 except error.FailGlob as e: # e.g. echo hi > foo-*
1444 if not e.HasLocation():
1445 e.location = self.mem.GetFallbackLocation()
1446 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1447 redirects = None
1448
1449 if redirects is None:
1450 # Error evaluating redirect words
1451 status = 1
1452
1453 # Translation fix: redirect I/O errors may happen in a C++
1454 # destructor ~vm::ctx_Redirect, which means they must be signaled
1455 # by out params, not exceptions.
1456 io_errors = [] # type: List[error.IOError_OSError]
1457
1458 # If we evaluated redirects, apply/push them
1459 if status == 0:
1460 self.shell_ex.PushRedirects(redirects, io_errors)
1461 if len(io_errors):
1462 # core/process.py prints cryptic errors, so we repeat them
1463 # here. e.g. Bad File Descriptor
1464 self.errfmt.PrintMessage(
1465 'I/O error applying redirect: %s' %
1466 pyutil.strerror(io_errors[0]),
1467 self.mem.GetFallbackLocation())
1468 status = 1
1469
1470 # If we applied redirects successfully, run the command_t, and pop
1471 # them.
1472 if status == 0:
1473 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1474 status = self._Execute(node.child)
1475 if len(io_errors):
1476 # It would be better to point to the right redirect
1477 # operator, but we don't track it specifically
1478 e_die("Fatal error popping redirect: %s" %
1479 pyutil.strerror(io_errors[0]))
1480
1481 return status
1482
1483 def _Dispatch(self, node, cmd_st):
1484 # type: (command_t, CommandStatus) -> int
1485 """Switch on the command_t variants and execute them."""
1486
1487 # If we call RunCommandSub in a recursive call to the executor, this will
1488 # be set true (if strict_errexit is false). But it only lasts for one
1489 # command.
1490 probe('cmd_eval', '_Dispatch', node.tag())
1491 self.check_command_sub_status = False
1492
1493 UP_node = node
1494 with tagswitch(node) as case:
1495 if case(command_e.Simple): # LEAF command
1496 node = cast(command.Simple, UP_node)
1497
1498 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1499 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1500 # TODO: blame_tok should always be set.
1501 if node.blame_tok is not None:
1502 self.mem.SetTokenForLine(node.blame_tok)
1503
1504 self._MaybeRunDebugTrap()
1505 status = self._DoSimple(node, cmd_st)
1506
1507 elif case(command_e.ExpandedAlias):
1508 node = cast(command.ExpandedAlias, UP_node)
1509 status = self._DoExpandedAlias(node)
1510
1511 elif case(command_e.Sentence):
1512 node = cast(command.Sentence, UP_node)
1513
1514 # Don't check_errexit since this isn't a leaf command
1515 if node.terminator.id == Id.Op_Semi:
1516 status = self._Execute(node.child)
1517 else:
1518 status = self.shell_ex.RunBackgroundJob(node.child)
1519
1520 elif case(command_e.Redirect):
1521 node = cast(command.Redirect, UP_node)
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 cmd_st.check_errexit = True
1531 status = self.shell_ex.RunSubshell(node.child)
1532
1533 elif case(command_e.DBracket): # LEAF command
1534 node = cast(command.DBracket, UP_node)
1535
1536 self.mem.SetTokenForLine(node.left)
1537 self._MaybeRunDebugTrap()
1538
1539 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1540
1541 cmd_st.check_errexit = True
1542 cmd_st.show_code = True # this is a "leaf" for errors
1543 result = self.bool_ev.EvalB(node.expr)
1544 status = 0 if result else 1
1545
1546 elif case(command_e.DParen): # LEAF command
1547 node = cast(command.DParen, UP_node)
1548
1549 self.mem.SetTokenForLine(node.left)
1550 self._MaybeRunDebugTrap()
1551
1552 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1553
1554 cmd_st.check_errexit = True
1555 cmd_st.show_code = True # this is a "leaf" for errors
1556 i = self.arith_ev.EvalToBigInt(node.child)
1557 status = 1 if mops.Equal(i, mops.ZERO) else 0
1558
1559 elif case(command_e.ControlFlow): # LEAF command
1560 node = cast(command.ControlFlow, UP_node)
1561
1562 self.mem.SetTokenForLine(node.keyword)
1563 self._MaybeRunDebugTrap()
1564
1565 status = self._DoControlFlow(node)
1566
1567 elif case(command_e.VarDecl): # LEAF command
1568 node = cast(command.VarDecl, UP_node)
1569
1570 # Point to var name (bare assignment has no keyword)
1571 self.mem.SetTokenForLine(node.lhs[0].left)
1572 status = self._DoVarDecl(node)
1573
1574 elif case(command_e.Mutation): # LEAF command
1575 node = cast(command.Mutation, UP_node)
1576
1577 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1578 self._DoMutation(node)
1579 status = 0 # if no exception is thrown, it succeeds
1580
1581 elif case(command_e.ShAssignment): # LEAF command
1582 node = cast(command.ShAssignment, UP_node)
1583
1584 self.mem.SetTokenForLine(node.pairs[0].left)
1585 self._MaybeRunDebugTrap()
1586
1587 # Only unqualified assignment a=b
1588 status = self._DoShAssignment(node, cmd_st)
1589
1590 elif case(command_e.Expr): # YSH LEAF command
1591 node = cast(command.Expr, UP_node)
1592
1593 self.mem.SetTokenForLine(node.keyword)
1594 # YSH debug trap?
1595
1596 status = self._DoExpr(node)
1597
1598 elif case(command_e.Retval): # YSH LEAF command
1599 node = cast(command.Retval, UP_node)
1600
1601 self.mem.SetTokenForLine(node.keyword)
1602 # YSH debug trap? I think we don't want the debug trap in func
1603 # dialect, for speed?
1604
1605 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1606 raise vm.ValueControlFlow(node.keyword, val)
1607
1608 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1609 # DoGroup has 'do' and 'done' spids for translation.
1610 elif case(command_e.CommandList):
1611 node = cast(command.CommandList, UP_node)
1612 status = self._ExecuteList(node.children)
1613 cmd_st.check_errexit = False
1614
1615 elif case(command_e.DoGroup):
1616 node = cast(command.DoGroup, UP_node)
1617 status = self._ExecuteList(node.children)
1618 cmd_st.check_errexit = False # not real statements
1619
1620 elif case(command_e.BraceGroup):
1621 node = cast(BraceGroup, UP_node)
1622 status = self._ExecuteList(node.children)
1623 cmd_st.check_errexit = False
1624
1625 elif case(command_e.AndOr):
1626 node = cast(command.AndOr, UP_node)
1627 status = self._DoAndOr(node, cmd_st)
1628
1629 elif case(command_e.WhileUntil):
1630 node = cast(command.WhileUntil, UP_node)
1631
1632 self.mem.SetTokenForLine(node.keyword)
1633 status = self._DoWhileUntil(node)
1634
1635 elif case(command_e.ForEach):
1636 node = cast(command.ForEach, UP_node)
1637
1638 self.mem.SetTokenForLine(node.keyword)
1639 status = self._DoForEach(node)
1640
1641 elif case(command_e.ForExpr):
1642 node = cast(command.ForExpr, UP_node)
1643
1644 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1645 status = self._DoForExpr(node)
1646
1647 elif case(command_e.ShFunction):
1648 node = cast(command.ShFunction, UP_node)
1649 self._DoShFunction(node)
1650 status = 0
1651
1652 elif case(command_e.Proc):
1653 node = cast(Proc, UP_node)
1654 self._DoProc(node)
1655 status = 0
1656
1657 elif case(command_e.Func):
1658 node = cast(Func, UP_node)
1659
1660 # Needed for error, when the func is an existing variable name
1661 self.mem.SetTokenForLine(node.name)
1662
1663 self._DoFunc(node)
1664 status = 0
1665
1666 elif case(command_e.If):
1667 node = cast(command.If, UP_node)
1668
1669 # No SetTokenForLine() because
1670 # - $LINENO can't appear directly in 'if'
1671 # - 'if' doesn't directly cause errors
1672 # It will be taken care of by command.Simple, condition, etc.
1673 status = self._DoIf(node)
1674
1675 elif case(command_e.NoOp):
1676 status = 0 # make it true
1677
1678 elif case(command_e.Case):
1679 node = cast(command.Case, UP_node)
1680
1681 # Must set location for 'case $LINENO'
1682 self.mem.SetTokenForLine(node.case_kw)
1683 self._MaybeRunDebugTrap()
1684 status = self._DoCase(node)
1685
1686 elif case(command_e.TimeBlock):
1687 node = cast(command.TimeBlock, UP_node)
1688 status = self._DoTimeBlock(node)
1689
1690 else:
1691 raise NotImplementedError(node.tag())
1692
1693 # Return to caller. Note the only case that didn't set it was Pipeline,
1694 # which set cmd_st.pipe_status.
1695 return status
1696
1697 def RunPendingTraps(self):
1698 # type: () -> None
1699
1700 trap_nodes = self.trap_state.GetPendingTraps()
1701 if trap_nodes is not None:
1702 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1703 True):
1704 for trap_node in trap_nodes:
1705 # Isolate the exit status.
1706 with state.ctx_Registers(self.mem):
1707 # Trace it. TODO: Show the trap kind too
1708 with dev.ctx_Tracer(self.tracer, 'trap', None):
1709 self._Execute(trap_node)
1710
1711 def _Execute(self, node):
1712 # type: (command_t) -> int
1713 """Call _Dispatch(), and performs the errexit check.
1714
1715 Also runs trap handlers.
1716 """
1717 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1718 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1719 # and maybe throw an exception.
1720 self.RunPendingTraps()
1721
1722 # We only need this somewhat hacky check in osh-cpp since python's runtime
1723 # handles SIGINT for us in osh.
1724 if mylib.CPP:
1725 if self.signal_safe.PollSigInt():
1726 raise KeyboardInterrupt()
1727
1728 # Manual GC point before every statement
1729 mylib.MaybeCollect()
1730
1731 # This has to go around redirect handling because the process sub could be
1732 # in the redirect word:
1733 # { echo one; echo two; } > >(tac)
1734
1735 # Optimization: These 2 records have rarely-used lists, so we don't pass
1736 # alloc_lists=True. We create them on demand.
1737 cmd_st = CommandStatus.CreateNull()
1738 if len(self.status_array_pool):
1739 # Optimized to avoid allocs
1740 process_sub_st = self.status_array_pool.pop()
1741 else:
1742 process_sub_st = StatusArray.CreateNull()
1743
1744 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1745 try:
1746 status = self._Dispatch(node, cmd_st)
1747 except error.FailGlob as e:
1748 if not e.HasLocation(): # Last resort!
1749 e.location = self.mem.GetFallbackLocation()
1750 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1751 status = 1 # another redirect word eval error
1752 cmd_st.check_errexit = True # failglob + errexit
1753
1754 # Now we've waited for process subs
1755
1756 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1757 # @_pipeline_status
1758 pipe_status = cmd_st.pipe_status
1759 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1760 # makes it annoying to check both _process_sub_status and
1761 # _pipeline_status
1762
1763 errexit_loc = loc.Missing # type: loc_t
1764 if pipe_status is not None:
1765 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1766 # for a REAL pipeline (but not singleton pipelines)
1767 assert status == -1, (
1768 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1769 status)
1770
1771 self.mem.SetPipeStatus(pipe_status)
1772
1773 if self.exec_opts.pipefail():
1774 # The status is that of the last command that is non-zero.
1775 status = 0
1776 for i, st in enumerate(pipe_status):
1777 if st != 0:
1778 status = st
1779 errexit_loc = cmd_st.pipe_locs[i]
1780 else:
1781 # The status is that of last command, period.
1782 status = pipe_status[-1]
1783
1784 if cmd_st.pipe_negated:
1785 status = 1 if status == 0 else 0
1786
1787 # Compute status from _process_sub_status
1788 if process_sub_st.codes is None:
1789 # Optimized to avoid allocs
1790 self.status_array_pool.append(process_sub_st)
1791 else:
1792 codes = process_sub_st.codes
1793 self.mem.SetProcessSubStatus(codes)
1794 if status == 0 and self.exec_opts.process_sub_fail():
1795 # Choose the LAST non-zero status, consistent with pipefail above.
1796 for i, st in enumerate(codes):
1797 if st != 0:
1798 status = st
1799 errexit_loc = process_sub_st.locs[i]
1800
1801 self.mem.SetLastStatus(status)
1802
1803 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1804 # However, any bash construct can appear in a pipeline. So it's easier
1805 # just to put it at the end, instead of after every node.
1806 #
1807 # Possible exceptions:
1808 # - function def (however this always exits 0 anyway)
1809 # - assignment - its result should be the result of the RHS?
1810 # - e.g. arith sub, command sub? I don't want arith sub.
1811 # - ControlFlow: always raises, it has no status.
1812 if cmd_st.check_errexit:
1813 #log('cmd_st %s', cmd_st)
1814 self._CheckStatus(status, cmd_st, node, errexit_loc)
1815
1816 return status
1817
1818 def _ExecuteList(self, children):
1819 # type: (List[command_t]) -> int
1820 status = 0 # for empty list
1821 for child in children:
1822 # last status wins
1823 status = self._Execute(child)
1824 return status
1825
1826 def LastStatus(self):
1827 # type: () -> int
1828 """For main_loop.py to determine the exit code of the shell itself."""
1829 return self.mem.LastStatus()
1830
1831 def _NoForkLast(self, node):
1832 # type: (command_t) -> None
1833
1834 if 0:
1835 log('optimizing')
1836 node.PrettyPrint(sys.stderr)
1837 log('')
1838
1839 UP_node = node
1840 with tagswitch(node) as case:
1841 if case(command_e.Simple):
1842 node = cast(command.Simple, UP_node)
1843 node.do_fork = False
1844 if 0:
1845 log('Simple optimized')
1846
1847 elif case(command_e.Pipeline):
1848 node = cast(command.Pipeline, UP_node)
1849 if node.negated is None:
1850 #log ('pipe')
1851 self._NoForkLast(node.children[-1])
1852
1853 elif case(command_e.Sentence):
1854 node = cast(command.Sentence, UP_node)
1855 self._NoForkLast(node.child)
1856
1857 elif case(command_e.CommandList):
1858 # Subshells start with CommandList, even if there's only one.
1859 node = cast(command.CommandList, UP_node)
1860 self._NoForkLast(node.children[-1])
1861
1862 elif case(command_e.BraceGroup):
1863 # TODO: What about redirects?
1864 node = cast(BraceGroup, UP_node)
1865 self._NoForkLast(node.children[-1])
1866
1867 def _RemoveSubshells(self, node):
1868 # type: (command_t) -> command_t
1869 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1870
1871 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1872 be correct otherwise.
1873 """
1874 UP_node = node
1875 with tagswitch(node) as case:
1876 if case(command_e.Subshell):
1877 node = cast(command.Subshell, UP_node)
1878 # Optimize ( ( date ) ) etc.
1879 return self._RemoveSubshells(node.child)
1880 return node
1881
1882 def ExecuteAndCatch(self, node, cmd_flags=0):
1883 # type: (command_t, int) -> Tuple[bool, bool]
1884 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1885
1886 Args:
1887 node: LST subtree
1888 optimize: Whether to exec the last process rather than fork/exec
1889
1890 Returns:
1891 TODO: use enum 'why' instead of the 2 booleans
1892
1893 Used by
1894 - main_loop.py.
1895 - SubProgramThunk for pipelines, subshell, command sub, process sub
1896 - TODO: Signals besides EXIT trap
1897
1898 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
1899 finally_exit boolean. We use a different algorithm.
1900 """
1901 if cmd_flags & Optimize:
1902 node = self._RemoveSubshells(node)
1903 self._NoForkLast(node) # turn the last ones into exec
1904
1905 if 0:
1906 log('after opt:')
1907 node.PrettyPrint()
1908 log('')
1909
1910 is_return = False
1911 is_fatal = False
1912 is_errexit = False
1913
1914 err = None # type: error.FatalRuntime
1915 status = -1 # uninitialized
1916
1917 try:
1918 if cmd_flags & NoDebugTrap:
1919 with state.ctx_Option(self.mutable_opts,
1920 [option_i._no_debug_trap], True):
1921 status = self._Execute(node)
1922 else:
1923 status = self._Execute(node)
1924 except vm.IntControlFlow as e:
1925 if cmd_flags & RaiseControlFlow:
1926 raise # 'eval break' and 'source return.sh', etc.
1927 else:
1928 # Return at top level is OK, unlike in bash.
1929 if e.IsReturn():
1930 is_return = True
1931 status = e.StatusCode()
1932 else:
1933 # TODO: This error message is invalid. Can also happen in eval.
1934 # We need a flag.
1935
1936 # Invalid control flow
1937 self.errfmt.Print_(
1938 "Loop and control flow can't be in different processes",
1939 blame_loc=e.token)
1940 is_fatal = True
1941 # All shells exit 0 here. It could be hidden behind
1942 # strict_control_flow if the incompatibility causes problems.
1943 status = 1
1944 except error.Parse as e:
1945 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
1946 raise
1947 except error.ErrExit as e:
1948 err = e
1949 is_errexit = True
1950 except error.FatalRuntime as e:
1951 err = e
1952
1953 if err:
1954 status = err.ExitStatus()
1955
1956 is_fatal = True
1957 # Do this before unwinding stack
1958 self.dumper.MaybeRecord(self, err)
1959
1960 if not err.HasLocation(): # Last resort!
1961 #log('Missing location')
1962 err.location = self.mem.GetFallbackLocation()
1963 #log('%s', err.location)
1964
1965 if is_errexit:
1966 if self.exec_opts.verbose_errexit():
1967 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
1968 posix.getpid())
1969 else:
1970 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
1971
1972 assert status >= 0, 'Should have been initialized'
1973
1974 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
1975 # created a crash dump. So we get 2 or more of them.
1976 self.dumper.MaybeDump(status)
1977
1978 self.mem.SetLastStatus(status)
1979 return is_return, is_fatal
1980
1981 def EvalCommand(self, block):
1982 # type: (command_t) -> int
1983 """For builtins to evaluate command args.
1984
1985 e.g. cd /tmp (x)
1986 """
1987 status = 0
1988 try:
1989 status = self._Execute(block) # can raise FatalRuntimeError, etc.
1990 except vm.IntControlFlow as e: # A block is more like a function.
1991 # return in a block
1992 if e.IsReturn():
1993 status = e.StatusCode()
1994 else:
1995 e_die('Unexpected control flow in block', e.token)
1996
1997 return status
1998
1999 def MaybeRunExitTrap(self, mut_status):
2000 # type: (IntParamBox) -> None
2001 """If an EXIT trap handler exists, run it.
2002
2003 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2004 all bash/dash/mksh seem to agree on it. See cases in
2005 builtin-trap.test.sh.
2006
2007 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2008 of this awkward interface. But that's true in Python and not C!
2009
2010 Could use i & (n-1) == i & 255 because we have a power of 2.
2011 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2012 """
2013 node = self.trap_state.GetHook('EXIT') # type: command_t
2014 if node:
2015 # NOTE: Don't set option_i._running_trap, because that's for
2016 # RunPendingTraps() in the MAIN LOOP
2017 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2018 try:
2019 is_return, is_fatal = self.ExecuteAndCatch(node)
2020 except util.UserExit as e: # explicit exit
2021 mut_status.i = e.status
2022 return
2023 if is_return: # explicit 'return' in the trap handler!
2024 mut_status.i = self.LastStatus()
2025
2026 def _MaybeRunDebugTrap(self):
2027 # type: () -> None
2028 """If a DEBUG trap handler exists, run it."""
2029
2030 # Fix lastpipe / job control / DEBUG trap interaction
2031 if self.exec_opts._no_debug_trap():
2032 return
2033
2034 # Don't run recursively run traps, etc.
2035 if not self.mem.ShouldRunDebugTrap():
2036 return
2037
2038 node = self.trap_state.GetHook('DEBUG') # type: command_t
2039 if node:
2040 # NOTE: Don't set option_i._running_trap, because that's for
2041 # RunPendingTraps() in the MAIN LOOP
2042
2043 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2044 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2045 # for SetTokenForLine $LINENO
2046 with state.ctx_DebugTrap(self.mem):
2047 # Don't catch util.UserExit, etc.
2048 self._Execute(node)
2049
2050 def _MaybeRunErrTrap(self):
2051 # type: () -> None
2052 """If a ERR trap handler exists, run it."""
2053
2054 # Prevent infinite recursion
2055 if self.mem.running_err_trap:
2056 return
2057
2058 node = self.trap_state.GetHook('ERR') # type: command_t
2059 if node:
2060 # NOTE: Don't set option_i._running_trap, because that's for
2061 # RunPendingTraps() in the MAIN LOOP
2062
2063 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2064 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2065 with state.ctx_ErrTrap(self.mem):
2066 self._Execute(node)
2067
2068 def RunProc(self, proc, cmd_val):
2069 # type: (value.Proc, cmd_value.Argv) -> int
2070 """Run procs aka "shell functions".
2071
2072 For SimpleCommand and registered completion hooks.
2073 """
2074 sig = proc.sig
2075 if sig.tag() == proc_sig_e.Closed:
2076 # We're binding named params. User should use @rest. No 'shift'.
2077 proc_argv = [] # type: List[str]
2078 else:
2079 proc_argv = cmd_val.argv[1:]
2080
2081 # Hm this sets "$@". TODO: Set ARGV only
2082 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2083 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2084
2085 # Redirects still valid for functions.
2086 # Here doc causes a pipe and Process(SubProgramThunk).
2087 try:
2088 status = self._Execute(proc.body)
2089 except vm.IntControlFlow as e:
2090 if e.IsReturn():
2091 status = e.StatusCode()
2092 else:
2093 # break/continue used in the wrong place.
2094 e_die(
2095 'Unexpected %r (in proc call)' %
2096 lexer.TokenVal(e.token), e.token)
2097 except error.FatalRuntime as e:
2098 # Dump the stack before unwinding it
2099 self.dumper.MaybeRecord(self, e)
2100 raise
2101
2102 return status
2103
2104 def RunFuncForCompletion(self, proc, argv):
2105 # type: (value.Proc, List[str]) -> int
2106 """
2107 Args:
2108 argv: $1 $2 $3 ... not including $0
2109 """
2110 cmd_val = MakeBuiltinArgv(argv)
2111
2112 # TODO: Change this to run YSH procs and funcs too
2113 try:
2114 status = self.RunProc(proc, cmd_val)
2115 except error.FatalRuntime as e:
2116 self.errfmt.PrettyPrintError(e)
2117 status = e.ExitStatus()
2118 except vm.IntControlFlow as e:
2119 # shouldn't be able to exit the shell from a completion hook!
2120 # TODO: Avoid overwriting the prompt!
2121 self.errfmt.Print_('Attempted to exit from completion hook.',
2122 blame_loc=e.token)
2123
2124 status = 1
2125 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2126 return status
2127
2128
2129# vim: sw=4