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

2150 lines, 1332 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 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1452 # dash/ash
1453 cmd_st.check_errexit = True
1454
1455 status = 0
1456 redirects = [] # type: List[RedirValue]
1457
1458 try:
1459 for redir in node.redirects:
1460 redirects.append(self._EvalRedirect(redir))
1461 except error.RedirectEval as e:
1462 self.errfmt.PrettyPrintError(e)
1463 redirects = None
1464 except error.FailGlob as e: # e.g. echo hi > foo-*
1465 if not e.HasLocation():
1466 e.location = self.mem.GetFallbackLocation()
1467 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1468 redirects = None
1469
1470 if redirects is None:
1471 # Error evaluating redirect words
1472 status = 1
1473
1474 # Translation fix: redirect I/O errors may happen in a C++
1475 # destructor ~vm::ctx_Redirect, which means they must be signaled
1476 # by out params, not exceptions.
1477 io_errors = [] # type: List[error.IOError_OSError]
1478
1479 # If we evaluated redirects, apply/push them
1480 if status == 0:
1481 self.shell_ex.PushRedirects(redirects, io_errors)
1482 if len(io_errors):
1483 # core/process.py prints cryptic errors, so we repeat them
1484 # here. e.g. Bad File Descriptor
1485 self.errfmt.PrintMessage(
1486 'I/O error applying redirect: %s' %
1487 pyutil.strerror(io_errors[0]),
1488 self.mem.GetFallbackLocation())
1489 status = 1
1490
1491 # If we applied redirects successfully, run the command_t, and pop
1492 # them.
1493 if status == 0:
1494 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1495 status = self._Execute(node.child)
1496 if len(io_errors):
1497 # It would be better to point to the right redirect
1498 # operator, but we don't track it specifically
1499 e_die("Fatal error popping redirect: %s" %
1500 pyutil.strerror(io_errors[0]))
1501
1502 return status
1503
1504 def _Dispatch(self, node, cmd_st):
1505 # type: (command_t, CommandStatus) -> int
1506 """Switch on the command_t variants and execute them."""
1507
1508 # If we call RunCommandSub in a recursive call to the executor, this will
1509 # be set true (if strict_errexit is false). But it only lasts for one
1510 # command.
1511 probe('cmd_eval', '_Dispatch', node.tag())
1512 self.check_command_sub_status = False
1513
1514 UP_node = node
1515 with tagswitch(node) as case:
1516 if case(command_e.Simple): # LEAF command
1517 node = cast(command.Simple, UP_node)
1518
1519 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1520 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1521 # TODO: blame_tok should always be set.
1522 if node.blame_tok is not None:
1523 self.mem.SetTokenForLine(node.blame_tok)
1524
1525 self._MaybeRunDebugTrap()
1526 status = self._DoSimple(node, cmd_st)
1527
1528 elif case(command_e.ExpandedAlias):
1529 node = cast(command.ExpandedAlias, UP_node)
1530 status = self._DoExpandedAlias(node)
1531
1532 elif case(command_e.Sentence):
1533 node = cast(command.Sentence, UP_node)
1534
1535 # Don't check_errexit since this isn't a leaf command
1536 if node.terminator.id == Id.Op_Semi:
1537 status = self._Execute(node.child)
1538 else:
1539 status = self.shell_ex.RunBackgroundJob(node.child)
1540
1541 elif case(command_e.Redirect):
1542 node = cast(command.Redirect, UP_node)
1543 status = self._DoRedirect(node, cmd_st)
1544
1545 elif case(command_e.Pipeline):
1546 node = cast(command.Pipeline, UP_node)
1547 status = self._DoPipeline(node, cmd_st)
1548
1549 elif case(command_e.Subshell):
1550 node = cast(command.Subshell, UP_node)
1551 cmd_st.check_errexit = True
1552 status = self.shell_ex.RunSubshell(node.child)
1553
1554 elif case(command_e.DBracket): # LEAF command
1555 node = cast(command.DBracket, UP_node)
1556
1557 self.mem.SetTokenForLine(node.left)
1558 self._MaybeRunDebugTrap()
1559
1560 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1561
1562 cmd_st.check_errexit = True
1563 cmd_st.show_code = True # this is a "leaf" for errors
1564 result = self.bool_ev.EvalB(node.expr)
1565 status = 0 if result else 1
1566
1567 elif case(command_e.DParen): # LEAF command
1568 node = cast(command.DParen, UP_node)
1569
1570 self.mem.SetTokenForLine(node.left)
1571 self._MaybeRunDebugTrap()
1572
1573 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1574
1575 cmd_st.check_errexit = True
1576 cmd_st.show_code = True # this is a "leaf" for errors
1577 i = self.arith_ev.EvalToInt(node.child)
1578 status = 1 if i == 0 else 0
1579
1580 elif case(command_e.ControlFlow): # LEAF command
1581 node = cast(command.ControlFlow, UP_node)
1582
1583 self.mem.SetTokenForLine(node.keyword)
1584 self._MaybeRunDebugTrap()
1585
1586 status = self._DoControlFlow(node)
1587
1588 elif case(command_e.VarDecl): # LEAF command
1589 node = cast(command.VarDecl, UP_node)
1590
1591 # Point to var name (bare assignment has no keyword)
1592 self.mem.SetTokenForLine(node.lhs[0].left)
1593 status = self._DoVarDecl(node)
1594
1595 elif case(command_e.Mutation): # LEAF command
1596 node = cast(command.Mutation, UP_node)
1597
1598 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1599 self._DoMutation(node)
1600 status = 0 # if no exception is thrown, it succeeds
1601
1602 elif case(command_e.ShAssignment): # LEAF command
1603 node = cast(command.ShAssignment, UP_node)
1604
1605 self.mem.SetTokenForLine(node.pairs[0].left)
1606 self._MaybeRunDebugTrap()
1607
1608 # Only unqualified assignment a=b
1609 status = self._DoShAssignment(node, cmd_st)
1610
1611 elif case(command_e.Expr): # YSH LEAF command
1612 node = cast(command.Expr, UP_node)
1613
1614 self.mem.SetTokenForLine(node.keyword)
1615 # YSH debug trap?
1616
1617 status = self._DoExpr(node)
1618
1619 elif case(command_e.Retval): # YSH LEAF command
1620 node = cast(command.Retval, UP_node)
1621
1622 self.mem.SetTokenForLine(node.keyword)
1623 # YSH debug trap? I think we don't want the debug trap in func
1624 # dialect, for speed?
1625
1626 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1627 raise vm.ValueControlFlow(node.keyword, val)
1628
1629 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1630 # DoGroup has 'do' and 'done' spids for translation.
1631 elif case(command_e.CommandList):
1632 node = cast(command.CommandList, UP_node)
1633 status = self._ExecuteList(node.children)
1634 cmd_st.check_errexit = False
1635
1636 elif case(command_e.DoGroup):
1637 node = cast(command.DoGroup, UP_node)
1638 status = self._ExecuteList(node.children)
1639 cmd_st.check_errexit = False # not real statements
1640
1641 elif case(command_e.BraceGroup):
1642 node = cast(BraceGroup, UP_node)
1643 status = self._ExecuteList(node.children)
1644 cmd_st.check_errexit = False
1645
1646 elif case(command_e.AndOr):
1647 node = cast(command.AndOr, UP_node)
1648 status = self._DoAndOr(node, cmd_st)
1649
1650 elif case(command_e.WhileUntil):
1651 node = cast(command.WhileUntil, UP_node)
1652
1653 self.mem.SetTokenForLine(node.keyword)
1654 status = self._DoWhileUntil(node)
1655
1656 elif case(command_e.ForEach):
1657 node = cast(command.ForEach, UP_node)
1658
1659 self.mem.SetTokenForLine(node.keyword)
1660 status = self._DoForEach(node)
1661
1662 elif case(command_e.ForExpr):
1663 node = cast(command.ForExpr, UP_node)
1664
1665 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1666 status = self._DoForExpr(node)
1667
1668 elif case(command_e.ShFunction):
1669 node = cast(command.ShFunction, UP_node)
1670 self._DoShFunction(node)
1671 status = 0
1672
1673 elif case(command_e.Proc):
1674 node = cast(Proc, UP_node)
1675 self._DoProc(node)
1676 status = 0
1677
1678 elif case(command_e.Func):
1679 node = cast(Func, UP_node)
1680
1681 # Needed for error, when the func is an existing variable name
1682 self.mem.SetTokenForLine(node.name)
1683
1684 self._DoFunc(node)
1685 status = 0
1686
1687 elif case(command_e.If):
1688 node = cast(command.If, UP_node)
1689
1690 # No SetTokenForLine() because
1691 # - $LINENO can't appear directly in 'if'
1692 # - 'if' doesn't directly cause errors
1693 # It will be taken care of by command.Simple, condition, etc.
1694 status = self._DoIf(node)
1695
1696 elif case(command_e.NoOp):
1697 status = 0 # make it true
1698
1699 elif case(command_e.Case):
1700 node = cast(command.Case, UP_node)
1701
1702 # Must set location for 'case $LINENO'
1703 self.mem.SetTokenForLine(node.case_kw)
1704 self._MaybeRunDebugTrap()
1705 status = self._DoCase(node)
1706
1707 elif case(command_e.TimeBlock):
1708 node = cast(command.TimeBlock, UP_node)
1709 status = self._DoTimeBlock(node)
1710
1711 else:
1712 raise NotImplementedError(node.tag())
1713
1714 # Return to caller. Note the only case that didn't set it was Pipeline,
1715 # which set cmd_st.pipe_status.
1716 return status
1717
1718 def RunPendingTraps(self):
1719 # type: () -> None
1720
1721 trap_nodes = self.trap_state.GetPendingTraps()
1722 if trap_nodes is not None:
1723 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1724 True):
1725 for trap_node in trap_nodes:
1726 # Isolate the exit status.
1727 with state.ctx_Registers(self.mem):
1728 # Trace it. TODO: Show the trap kind too
1729 with dev.ctx_Tracer(self.tracer, 'trap', None):
1730 self._Execute(trap_node)
1731
1732 def _Execute(self, node):
1733 # type: (command_t) -> int
1734 """Call _Dispatch(), and performs the errexit check.
1735
1736 Also runs trap handlers.
1737 """
1738 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1739 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1740 # and maybe throw an exception.
1741 self.RunPendingTraps()
1742
1743 # We only need this somewhat hacky check in osh-cpp since python's runtime
1744 # handles SIGINT for us in osh.
1745 if mylib.CPP:
1746 if self.signal_safe.PollSigInt():
1747 raise KeyboardInterrupt()
1748
1749 # Manual GC point before every statement
1750 mylib.MaybeCollect()
1751
1752 # This has to go around redirect handling because the process sub could be
1753 # in the redirect word:
1754 # { echo one; echo two; } > >(tac)
1755
1756 # Optimization: These 2 records have rarely-used lists, so we don't pass
1757 # alloc_lists=True. We create them on demand.
1758 cmd_st = CommandStatus.CreateNull()
1759 if len(self.status_array_pool):
1760 # Optimized to avoid allocs
1761 process_sub_st = self.status_array_pool.pop()
1762 else:
1763 process_sub_st = StatusArray.CreateNull()
1764
1765 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1766 try:
1767 status = self._Dispatch(node, cmd_st)
1768 except error.FailGlob as e:
1769 if not e.HasLocation(): # Last resort!
1770 e.location = self.mem.GetFallbackLocation()
1771 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1772 status = 1 # another redirect word eval error
1773 cmd_st.check_errexit = True # failglob + errexit
1774
1775 # Now we've waited for process subs
1776
1777 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1778 # @_pipeline_status
1779 pipe_status = cmd_st.pipe_status
1780 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1781 # makes it annoying to check both _process_sub_status and
1782 # _pipeline_status
1783
1784 errexit_loc = loc.Missing # type: loc_t
1785 if pipe_status is not None:
1786 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1787 # for a REAL pipeline (but not singleton pipelines)
1788 assert status == -1, (
1789 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1790 status)
1791
1792 self.mem.SetPipeStatus(pipe_status)
1793
1794 if self.exec_opts.pipefail():
1795 # The status is that of the last command that is non-zero.
1796 status = 0
1797 for i, st in enumerate(pipe_status):
1798 if st != 0:
1799 status = st
1800 errexit_loc = cmd_st.pipe_locs[i]
1801 else:
1802 # The status is that of last command, period.
1803 status = pipe_status[-1]
1804
1805 if cmd_st.pipe_negated:
1806 status = 1 if status == 0 else 0
1807
1808 # Compute status from _process_sub_status
1809 if process_sub_st.codes is None:
1810 # Optimized to avoid allocs
1811 self.status_array_pool.append(process_sub_st)
1812 else:
1813 codes = process_sub_st.codes
1814 self.mem.SetProcessSubStatus(codes)
1815 if status == 0 and self.exec_opts.process_sub_fail():
1816 # Choose the LAST non-zero status, consistent with pipefail above.
1817 for i, st in enumerate(codes):
1818 if st != 0:
1819 status = st
1820 errexit_loc = process_sub_st.locs[i]
1821
1822 self.mem.SetLastStatus(status)
1823
1824 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1825 # However, any bash construct can appear in a pipeline. So it's easier
1826 # just to put it at the end, instead of after every node.
1827 #
1828 # Possible exceptions:
1829 # - function def (however this always exits 0 anyway)
1830 # - assignment - its result should be the result of the RHS?
1831 # - e.g. arith sub, command sub? I don't want arith sub.
1832 # - ControlFlow: always raises, it has no status.
1833 if cmd_st.check_errexit:
1834 #log('cmd_st %s', cmd_st)
1835 self._CheckStatus(status, cmd_st, node, errexit_loc)
1836
1837 return status
1838
1839 def _ExecuteList(self, children):
1840 # type: (List[command_t]) -> int
1841 status = 0 # for empty list
1842 for child in children:
1843 # last status wins
1844 status = self._Execute(child)
1845 return status
1846
1847 def LastStatus(self):
1848 # type: () -> int
1849 """For main_loop.py to determine the exit code of the shell itself."""
1850 return self.mem.LastStatus()
1851
1852 def _NoForkLast(self, node):
1853 # type: (command_t) -> None
1854
1855 if 0:
1856 log('optimizing')
1857 node.PrettyPrint(sys.stderr)
1858 log('')
1859
1860 UP_node = node
1861 with tagswitch(node) as case:
1862 if case(command_e.Simple):
1863 node = cast(command.Simple, UP_node)
1864 node.do_fork = False
1865 if 0:
1866 log('Simple optimized')
1867
1868 elif case(command_e.Pipeline):
1869 node = cast(command.Pipeline, UP_node)
1870 if node.negated is None:
1871 #log ('pipe')
1872 self._NoForkLast(node.children[-1])
1873
1874 elif case(command_e.Sentence):
1875 node = cast(command.Sentence, UP_node)
1876 self._NoForkLast(node.child)
1877
1878 elif case(command_e.CommandList):
1879 # Subshells start with CommandList, even if there's only one.
1880 node = cast(command.CommandList, UP_node)
1881 self._NoForkLast(node.children[-1])
1882
1883 elif case(command_e.BraceGroup):
1884 # TODO: What about redirects?
1885 node = cast(BraceGroup, UP_node)
1886 self._NoForkLast(node.children[-1])
1887
1888 def _RemoveSubshells(self, node):
1889 # type: (command_t) -> command_t
1890 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1891
1892 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1893 be correct otherwise.
1894 """
1895 UP_node = node
1896 with tagswitch(node) as case:
1897 if case(command_e.Subshell):
1898 node = cast(command.Subshell, UP_node)
1899 # Optimize ( ( date ) ) etc.
1900 return self._RemoveSubshells(node.child)
1901 return node
1902
1903 def ExecuteAndCatch(self, node, cmd_flags=0):
1904 # type: (command_t, int) -> Tuple[bool, bool]
1905 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1906
1907 Args:
1908 node: LST subtree
1909 optimize: Whether to exec the last process rather than fork/exec
1910
1911 Returns:
1912 TODO: use enum 'why' instead of the 2 booleans
1913
1914 Used by
1915 - main_loop.py.
1916 - SubProgramThunk for pipelines, subshell, command sub, process sub
1917 - TODO: Signals besides EXIT trap
1918
1919 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
1920 finally_exit boolean. We use a different algorithm.
1921 """
1922 if cmd_flags & Optimize:
1923 node = self._RemoveSubshells(node)
1924 self._NoForkLast(node) # turn the last ones into exec
1925
1926 if 0:
1927 log('after opt:')
1928 node.PrettyPrint()
1929 log('')
1930
1931 is_return = False
1932 is_fatal = False
1933 is_errexit = False
1934
1935 err = None # type: error.FatalRuntime
1936 status = -1 # uninitialized
1937
1938 try:
1939 if cmd_flags & NoDebugTrap:
1940 with state.ctx_Option(self.mutable_opts,
1941 [option_i._no_debug_trap], True):
1942 status = self._Execute(node)
1943 else:
1944 status = self._Execute(node)
1945 except vm.IntControlFlow as e:
1946 if cmd_flags & RaiseControlFlow:
1947 raise # 'eval break' and 'source return.sh', etc.
1948 else:
1949 # Return at top level is OK, unlike in bash.
1950 if e.IsReturn():
1951 is_return = True
1952 status = e.StatusCode()
1953 else:
1954 # TODO: This error message is invalid. Can also happen in eval.
1955 # We need a flag.
1956
1957 # Invalid control flow
1958 self.errfmt.Print_(
1959 "Loop and control flow can't be in different processes",
1960 blame_loc=e.token)
1961 is_fatal = True
1962 # All shells exit 0 here. It could be hidden behind
1963 # strict_control_flow if the incompatibility causes problems.
1964 status = 1
1965 except error.Parse as e:
1966 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
1967 raise
1968 except error.ErrExit as e:
1969 err = e
1970 is_errexit = True
1971 except error.FatalRuntime as e:
1972 err = e
1973
1974 if err:
1975 status = err.ExitStatus()
1976
1977 is_fatal = True
1978 # Do this before unwinding stack
1979 self.dumper.MaybeRecord(self, err)
1980
1981 if not err.HasLocation(): # Last resort!
1982 #log('Missing location')
1983 err.location = self.mem.GetFallbackLocation()
1984 #log('%s', err.location)
1985
1986 if is_errexit:
1987 if self.exec_opts.verbose_errexit():
1988 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
1989 posix.getpid())
1990 else:
1991 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
1992
1993 assert status >= 0, 'Should have been initialized'
1994
1995 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
1996 # created a crash dump. So we get 2 or more of them.
1997 self.dumper.MaybeDump(status)
1998
1999 self.mem.SetLastStatus(status)
2000 return is_return, is_fatal
2001
2002 def EvalCommand(self, block):
2003 # type: (command_t) -> int
2004 """For builtins to evaluate command args.
2005
2006 e.g. cd /tmp (x)
2007 """
2008 status = 0
2009 try:
2010 status = self._Execute(block) # can raise FatalRuntimeError, etc.
2011 except vm.IntControlFlow as e: # A block is more like a function.
2012 # return in a block
2013 if e.IsReturn():
2014 status = e.StatusCode()
2015 else:
2016 e_die('Unexpected control flow in block', e.token)
2017
2018 return status
2019
2020 def MaybeRunExitTrap(self, mut_status):
2021 # type: (IntParamBox) -> None
2022 """If an EXIT trap handler exists, run it.
2023
2024 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2025 all bash/dash/mksh seem to agree on it. See cases in
2026 builtin-trap.test.sh.
2027
2028 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2029 of this awkward interface. But that's true in Python and not C!
2030
2031 Could use i & (n-1) == i & 255 because we have a power of 2.
2032 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2033 """
2034 node = self.trap_state.GetHook('EXIT') # type: command_t
2035 if node:
2036 # NOTE: Don't set option_i._running_trap, because that's for
2037 # RunPendingTraps() in the MAIN LOOP
2038 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2039 try:
2040 is_return, is_fatal = self.ExecuteAndCatch(node)
2041 except util.UserExit as e: # explicit exit
2042 mut_status.i = e.status
2043 return
2044 if is_return: # explicit 'return' in the trap handler!
2045 mut_status.i = self.LastStatus()
2046
2047 def _MaybeRunDebugTrap(self):
2048 # type: () -> None
2049 """If a DEBUG trap handler exists, run it."""
2050
2051 # Fix lastpipe / job control / DEBUG trap interaction
2052 if self.exec_opts._no_debug_trap():
2053 return
2054
2055 # Don't run recursively run traps, etc.
2056 if not self.mem.ShouldRunDebugTrap():
2057 return
2058
2059 node = self.trap_state.GetHook('DEBUG') # type: command_t
2060 if node:
2061 # NOTE: Don't set option_i._running_trap, because that's for
2062 # RunPendingTraps() in the MAIN LOOP
2063
2064 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2065 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2066 # for SetTokenForLine $LINENO
2067 with state.ctx_DebugTrap(self.mem):
2068 # Don't catch util.UserExit, etc.
2069 self._Execute(node)
2070
2071 def _MaybeRunErrTrap(self):
2072 # type: () -> None
2073 """If a ERR trap handler exists, run it."""
2074
2075 # Prevent infinite recursion
2076 if self.running_err_trap:
2077 return
2078
2079 node = self.trap_state.GetHook('ERR') # type: command_t
2080 if node:
2081 # NOTE: Don't set option_i._running_trap, because that's for
2082 # RunPendingTraps() in the MAIN LOOP
2083
2084 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2085 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2086 with ctx_ErrTrap(self):
2087 self._Execute(node)
2088
2089 def RunProc(self, proc, cmd_val):
2090 # type: (value.Proc, cmd_value.Argv) -> int
2091 """Run procs aka "shell functions".
2092
2093 For SimpleCommand and registered completion hooks.
2094 """
2095 sig = proc.sig
2096 if sig.tag() == proc_sig_e.Closed:
2097 # We're binding named params. User should use @rest. No 'shift'.
2098 proc_argv = [] # type: List[str]
2099 else:
2100 proc_argv = cmd_val.argv[1:]
2101
2102 # Hm this sets "$@". TODO: Set ARGV only
2103 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2104 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2105
2106 # Redirects still valid for functions.
2107 # Here doc causes a pipe and Process(SubProgramThunk).
2108 try:
2109 status = self._Execute(proc.body)
2110 except vm.IntControlFlow as e:
2111 if e.IsReturn():
2112 status = e.StatusCode()
2113 else:
2114 # break/continue used in the wrong place.
2115 e_die(
2116 'Unexpected %r (in proc call)' %
2117 lexer.TokenVal(e.token), e.token)
2118 except error.FatalRuntime as e:
2119 # Dump the stack before unwinding it
2120 self.dumper.MaybeRecord(self, e)
2121 raise
2122
2123 return status
2124
2125 def RunFuncForCompletion(self, proc, argv):
2126 # type: (value.Proc, List[str]) -> int
2127 """
2128 Args:
2129 argv: $1 $2 $3 ... not including $0
2130 """
2131 cmd_val = MakeBuiltinArgv(argv)
2132
2133 # TODO: Change this to run YSH procs and funcs too
2134 try:
2135 status = self.RunProc(proc, cmd_val)
2136 except error.FatalRuntime as e:
2137 self.errfmt.PrettyPrintError(e)
2138 status = e.ExitStatus()
2139 except vm.IntControlFlow as e:
2140 # shouldn't be able to exit the shell from a completion hook!
2141 # TODO: Avoid overwriting the prompt!
2142 self.errfmt.Print_('Attempted to exit from completion hook.',
2143 blame_loc=e.token)
2144
2145 status = 1
2146 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2147 return status
2148
2149
2150# vim: sw=4