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

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