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

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