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

2184 lines, 1352 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 run_flags = executor.DO_FORK if node.do_fork else 0
815 # NOTE: RunSimpleCommand never returns when do_fork=False!
816 if len(node.more_env): # I think this guard is necessary?
817 is_other_special = False # TODO: There are other special builtins too!
818 if cmd_val.tag() == cmd_value_e.Assign or is_other_special:
819 # Special builtins have their temp env persisted.
820 self._EvalTempEnv(node.more_env, 0)
821 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
822 else:
823 with state.ctx_Temp(self.mem):
824 self._EvalTempEnv(node.more_env, state.SetExport)
825 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
826 else:
827 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
828
829 probe('cmd_eval', '_DoSimple_exit', status)
830 return status
831
832 def _DoExpandedAlias(self, node):
833 # type: (command.ExpandedAlias) -> int
834 # Expanded aliases need redirects and env bindings from the calling
835 # context, as well as redirects in the expansion!
836
837 # TODO: SetTokenForLine to OUTSIDE? Don't bother with stuff inside
838 # expansion, since aliases are discouraged.
839
840 if len(node.more_env):
841 with state.ctx_Temp(self.mem):
842 self._EvalTempEnv(node.more_env, state.SetExport)
843 return self._Execute(node.child)
844 else:
845 return self._Execute(node.child)
846
847 def _DoPipeline(self, node, cmd_st):
848 # type: (command.Pipeline, CommandStatus) -> int
849 cmd_st.check_errexit = True
850 for op in node.ops:
851 if op.id != Id.Op_Pipe:
852 e_die("|& isn't supported", op)
853
854 # Remove $_ before pipeline. This matches bash, and is important in
855 # pipelines than assignments because pipelines are non-deterministic.
856 self.mem.SetLastArgument('')
857
858 # Set status to INVALID value, because we MIGHT set cmd_st.pipe_status,
859 # which _Execute() boils down into a status for us.
860 status = -1
861
862 if node.negated is not None:
863 self._StrictErrExit(node)
864 with state.ctx_ErrExit(self.mutable_opts, False, node.negated):
865 # '! grep' is parsed as a pipeline, according to the grammar, but
866 # there's no pipe() call.
867 if len(node.children) == 1:
868 tmp_status = self._Execute(node.children[0])
869 status = 1 if tmp_status == 0 else 0
870 else:
871 self.shell_ex.RunPipeline(node, cmd_st)
872 cmd_st.pipe_negated = True
873
874 # errexit is disabled for !.
875 cmd_st.check_errexit = False
876 else:
877 self.shell_ex.RunPipeline(node, cmd_st)
878
879 return status
880
881 def _DoShAssignment(self, node, cmd_st):
882 # type: (command.ShAssignment, CommandStatus) -> int
883 assert len(node.pairs) >= 1, node
884
885 # x=y is 'neutered' inside 'proc'
886 which_scopes = self.mem.ScopesForWriting()
887
888 for pair in node.pairs:
889 if pair.op == assign_op_e.PlusEqual:
890 assert pair.rhs, pair.rhs # I don't think a+= is valid?
891 rhs = self.word_ev.EvalRhsWord(pair.rhs)
892
893 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
894 # do not respect set -u
895 old_val = sh_expr_eval.OldValue(lval, self.mem, None)
896
897 val = PlusEquals(old_val, rhs)
898
899 else: # plain assignment
900 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
901
902 # RHS can be a string or array.
903 if pair.rhs:
904 val = self.word_ev.EvalRhsWord(pair.rhs)
905 assert isinstance(val, value_t), val
906
907 else: # e.g. 'readonly x' or 'local x'
908 val = None
909
910 # NOTE: In bash and mksh, declare -a myarray makes an empty cell
911 # with Undef value, but the 'array' attribute.
912
913 flags = 0 # for tracing
914 self.mem.SetValue(lval, val, which_scopes, flags=flags)
915 self.tracer.OnShAssignment(lval, pair.op, val, flags, which_scopes)
916
917 # PATCH to be compatible with existing shells: If the assignment had a
918 # command sub like:
919 #
920 # s=$(echo one; false)
921 #
922 # then its status will be in mem.last_status, and we can check it here.
923 # If there was NOT a command sub in the assignment, then we don't want to
924 # check it.
925
926 # Only do this if there was a command sub? How? Look at node?
927 # Set a flag in mem? self.mem.last_status or
928 if self.check_command_sub_status:
929 last_status = self.mem.LastStatus()
930 self._CheckStatus(last_status, cmd_st, node, loc.Missing)
931 return last_status # A global assignment shouldn't clear $?.
932 else:
933 return 0
934
935 def _DoExpr(self, node):
936 # type: (command.Expr) -> int
937
938 # call f(x) or = f(x)
939 val = self.expr_ev.EvalExpr(node.e, loc.Missing)
940
941 if node.keyword.id == Id.Lit_Equals: # = f(x)
942 io_errors = [] # type: List[error.IOError_OSError]
943 with vm.ctx_FlushStdout(io_errors):
944 try:
945 ui.PrettyPrintValue(val, mylib.Stdout())
946 except (IOError, OSError) as e:
947 self.errfmt.PrintMessage(
948 'I/O error during = keyword: %s' % pyutil.strerror(e),
949 node.keyword)
950 return 1
951
952 if len(io_errors): # e.g. disk full, ulimit
953 self.errfmt.PrintMessage(
954 'I/O error during = keyword: %s' %
955 pyutil.strerror(io_errors[0]), node.keyword)
956 return 1
957
958 return 0
959
960 def _DoControlFlow(self, node):
961 # type: (command.ControlFlow) -> int
962 keyword = node.keyword
963
964 if node.arg_word: # Evaluate the argument
965 str_val = self.word_ev.EvalWordToString(node.arg_word)
966
967 # Quirk: We need 'return $empty' to be valid for libtool. This is
968 # another meaning of strict_control_flow, which also has to do with
969 # break/continue at top level. It has the side effect of making
970 # 'return ""' valid, which shells other than zsh fail on.
971 if (len(str_val.s) == 0 and
972 not self.exec_opts.strict_control_flow()):
973 arg = 0
974 else:
975 try:
976 arg = int(str_val.s) # all control flow takes an integer
977 except ValueError:
978 # Either a bad argument, or integer overflow
979 e_die(
980 '%r expected a small integer, got %r' %
981 (lexer.TokenVal(keyword), str_val.s),
982 loc.Word(node.arg_word))
983
984 # C++ int() does range checking, but Python doesn't. So let's
985 # simulate it here for spec tests.
986 # TODO: could be mylib.ToMachineInt()? Problem: 'int' in C/C++
987 # could be more than 4 bytes. We are testing INT_MAX and
988 # INT_MIN in gc_builtins.cc - those could be hard-coded.
989 if mylib.PYTHON:
990 max_int = (1 << 31) - 1
991 min_int = -(1 << 31)
992 if not (min_int <= arg <= max_int):
993 e_die(
994 '%r expected a small integer, got %r' %
995 (lexer.TokenVal(keyword), str_val.s),
996 loc.Word(node.arg_word))
997 else:
998 if keyword.id in (Id.ControlFlow_Exit, Id.ControlFlow_Return):
999 arg = self.mem.LastStatus()
1000 else:
1001 arg = 1 # break or continue 1 level by default
1002
1003 self.tracer.OnControlFlow(consts.ControlFlowName(keyword.id), arg)
1004
1005 # NOTE: A top-level 'return' is OK, unlike in bash. If you can return
1006 # from a sourced script, it makes sense to return from a main script.
1007 if (keyword.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and
1008 self.loop_level == 0):
1009 msg = 'Invalid control flow at top level'
1010 if self.exec_opts.strict_control_flow():
1011 e_die(msg, keyword)
1012 else:
1013 # Only print warnings, never fatal.
1014 # Bash oddly only exits 1 for 'return', but no other shell does.
1015 self.errfmt.PrefixPrint(msg, 'warning: ', keyword)
1016 return 0
1017
1018 if keyword.id == Id.ControlFlow_Exit:
1019 # handled differently than other control flow
1020 raise util.UserExit(arg)
1021 else:
1022 raise vm.IntControlFlow(keyword, arg)
1023
1024 def _DoAndOr(self, node, cmd_st):
1025 # type: (command.AndOr, CommandStatus) -> int
1026 # NOTE: && and || have EQUAL precedence in command mode. See case #13
1027 # in dbracket.test.sh.
1028
1029 left = node.children[0]
1030
1031 # Suppress failure for every child except the last one.
1032 self._StrictErrExit(left)
1033 with state.ctx_ErrExit(self.mutable_opts, False, node.ops[0]):
1034 status = self._Execute(left)
1035
1036 i = 1
1037 n = len(node.children)
1038 while i < n:
1039 #log('i %d status %d', i, status)
1040 child = node.children[i]
1041 op = node.ops[i - 1]
1042 op_id = op.id
1043
1044 #log('child %s op_id %s', child, op_id)
1045
1046 if op_id == Id.Op_DPipe and status == 0:
1047 i += 1
1048 continue # short circuit
1049
1050 elif op_id == Id.Op_DAmp and status != 0:
1051 i += 1
1052 continue # short circuit
1053
1054 if i == n - 1: # errexit handled differently for last child
1055 status = self._Execute(child)
1056 else:
1057 # blame the right && or ||
1058 self._StrictErrExit(child)
1059 with state.ctx_ErrExit(self.mutable_opts, False, op):
1060 status = self._Execute(child)
1061
1062 i += 1
1063
1064 return status
1065
1066 def _DoWhileUntil(self, node):
1067 # type: (command.WhileUntil) -> int
1068 status = 0
1069 with ctx_LoopLevel(self):
1070 while True:
1071 try:
1072 # blame while/until spid
1073 b = self._EvalCondition(node.cond, node.keyword)
1074 if node.keyword.id == Id.KW_Until:
1075 b = not b
1076 if not b:
1077 break
1078 status = self._Execute(node.body) # last one wins
1079
1080 except vm.IntControlFlow as e:
1081 status = 0
1082 action = e.HandleLoop()
1083 if action == flow_e.Break:
1084 break
1085 elif action == flow_e.Raise:
1086 raise
1087
1088 return status
1089
1090 def _DoForEach(self, node):
1091 # type: (command.ForEach) -> int
1092
1093 # for the 2 kinds of shell loop
1094 iter_list = None # type: List[str]
1095
1096 # for YSH loop
1097 iter_expr = None # type: expr_t
1098 expr_blame = None # type: loc_t
1099 iter_stdin = False
1100
1101 iterable = node.iterable
1102 UP_iterable = iterable
1103
1104 with tagswitch(node.iterable) as case:
1105 if case(for_iter_e.Args):
1106 iter_list = self.mem.GetArgv()
1107
1108 elif case(for_iter_e.Words):
1109 iterable = cast(for_iter.Words, UP_iterable)
1110 words = braces.BraceExpandWords(iterable.words)
1111 iter_list = self.word_ev.EvalWordSequence(words)
1112
1113 elif case(for_iter_e.YshExpr):
1114 iterable = cast(for_iter.YshExpr, UP_iterable)
1115 iter_expr = iterable.e
1116 expr_blame = iterable.blame
1117
1118 elif case(for_iter_e.Files):
1119 iterable = cast(for_iter.Files, UP_iterable)
1120
1121 # For now we only handle <>
1122 assert len(iterable.words) == 0, iterable.words
1123
1124 if 0:
1125 words = braces.BraceExpandWords(iterable.words)
1126 iter_list = self.word_ev.EvalWordSequence(words)
1127
1128 expr_blame = iterable.left
1129 iter_stdin = True
1130
1131 else:
1132 raise AssertionError()
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.Iterator
1143 if iter_expr: # 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
1199 elif iter_stdin:
1200 it2 = val_ops.StdinIterator()
1201 if n == 1:
1202 name1 = location.LName(node.iter_names[0])
1203 elif n == 2:
1204 i_name = location.LName(node.iter_names[0])
1205 name1 = location.LName(node.iter_names[1])
1206 else:
1207 e_die_status(
1208 2, 'Files iteration expects at most 2 loop variables',
1209 node.keyword)
1210
1211 else:
1212 assert iter_list is not None, iter_list
1213
1214 #log('iter list %s', iter_list)
1215 it2 = val_ops.ArrayIter(iter_list)
1216
1217 if n == 1:
1218 name1 = location.LName(node.iter_names[0])
1219 elif n == 2:
1220 i_name = location.LName(node.iter_names[0])
1221 name1 = location.LName(node.iter_names[1])
1222 else:
1223 # This is similar to a parse error
1224 e_die_status(
1225 2, 'Argv iteration expects at most 2 loop variables',
1226 node.keyword)
1227
1228 status = 0 # in case we loop zero times
1229 with ctx_LoopLevel(self):
1230 while True:
1231 first = it2.FirstValue()
1232 if first is None: # for StdinIterator
1233 break
1234 self.mem.SetLocalName(name1, first)
1235 if name2:
1236 self.mem.SetLocalName(name2, it2.SecondValue())
1237 if i_name:
1238 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1239
1240 # increment index before handling continue, etc.
1241 it2.Next()
1242
1243 try:
1244 status = self._Execute(node.body) # last one wins
1245 except vm.IntControlFlow as e:
1246 status = 0
1247 action = e.HandleLoop()
1248 if action == flow_e.Break:
1249 break
1250 elif action == flow_e.Raise:
1251 raise
1252
1253 return status
1254
1255 def _DoForExpr(self, node):
1256 # type: (command.ForExpr) -> int
1257
1258 status = 0
1259
1260 init = node.init
1261 for_cond = node.cond
1262 body = node.body
1263 update = node.update
1264
1265 self.arith_ev.Eval(init)
1266 with ctx_LoopLevel(self):
1267 while True:
1268 # We only accept integers as conditions
1269 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1270 if mops.Equal(cond_int, mops.ZERO): # false
1271 break
1272
1273 try:
1274 status = self._Execute(body)
1275 except vm.IntControlFlow as e:
1276 status = 0
1277 action = e.HandleLoop()
1278 if action == flow_e.Break:
1279 break
1280 elif action == flow_e.Raise:
1281 raise
1282
1283 self.arith_ev.Eval(update)
1284
1285 return status
1286
1287 def _DoShFunction(self, node):
1288 # type: (command.ShFunction) -> None
1289 if node.name in self.procs and not self.exec_opts.redefine_proc_func():
1290 e_die(
1291 "Function %s was already defined (redefine_proc_func)" %
1292 node.name, node.name_tok)
1293 self.procs[node.name] = value.Proc(node.name, node.name_tok,
1294 proc_sig.Open, node.body, None,
1295 True)
1296
1297 def _DoProc(self, node):
1298 # type: (Proc) -> None
1299 proc_name = lexer.TokenVal(node.name)
1300 if proc_name in self.procs and not self.exec_opts.redefine_proc_func():
1301 e_die(
1302 "Proc %s was already defined (redefine_proc_func)" % proc_name,
1303 node.name)
1304
1305 if node.sig.tag() == proc_sig_e.Closed:
1306 sig = cast(proc_sig.Closed, node.sig)
1307 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1308 else:
1309 proc_defaults = None
1310
1311 # no dynamic scope
1312 self.procs[proc_name] = value.Proc(proc_name, node.name, node.sig,
1313 node.body, proc_defaults, False)
1314
1315 def _DoFunc(self, node):
1316 # type: (Func) -> None
1317 name = lexer.TokenVal(node.name)
1318 lval = location.LName(name)
1319
1320 # Check that we haven't already defined a function
1321 cell = self.mem.GetCell(name, scope_e.LocalOnly)
1322 if cell and cell.val.tag() == value_e.Func:
1323 if self.exec_opts.redefine_proc_func():
1324 cell.readonly = False # Ensure we can unset the value
1325 did_unset = self.mem.Unset(lval, scope_e.LocalOnly)
1326 assert did_unset, name
1327 else:
1328 e_die(
1329 "Func %s was already defined (redefine_proc_func)" % name,
1330 node.name)
1331
1332 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1333 self.expr_ev, node)
1334 func_val = value.Func(name, node, pos_defaults, named_defaults, None)
1335
1336 self.mem.SetNamed(lval,
1337 func_val,
1338 scope_e.LocalOnly,
1339 flags=state.SetReadOnly)
1340
1341 def _DoIf(self, node):
1342 # type: (command.If) -> int
1343 status = -1
1344
1345 done = False
1346 for if_arm in node.arms:
1347 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1348 if b:
1349 status = self._ExecuteList(if_arm.action)
1350 done = True
1351 break
1352
1353 if not done and node.else_action is not None:
1354 status = self._ExecuteList(node.else_action)
1355
1356 assert status != -1, 'Should have been initialized'
1357 return status
1358
1359 def _DoCase(self, node):
1360 # type: (command.Case) -> int
1361
1362 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1363 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1364
1365 status = 0 # If there are no arms, it should be zero?
1366
1367 done = False # Should we try the next arm?
1368
1369 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1370 ignore_next_cond = False
1371
1372 for case_arm in node.arms:
1373 with tagswitch(case_arm.pattern) as case:
1374 if case(pat_e.Words):
1375 if to_match.tag() != value_e.Str:
1376 continue # A non-string `to_match` will never match a pat.Words
1377 to_match_str = cast(value.Str, to_match)
1378
1379 pat_words = cast(pat.Words, case_arm.pattern)
1380
1381 this_arm_matches = False
1382 if ignore_next_cond: # Special handling for ;&
1383 this_arm_matches = True
1384 ignore_next_cond = False
1385 else:
1386 for pat_word in pat_words.words:
1387 word_val = self.word_ev.EvalWordToString(
1388 pat_word, word_eval.QUOTE_FNMATCH)
1389
1390 if libc.fnmatch(word_val.s, to_match_str.s,
1391 fnmatch_flags):
1392 this_arm_matches = True
1393 break # Stop at first pattern
1394
1395 if this_arm_matches:
1396 status = self._ExecuteList(case_arm.action)
1397 done = True
1398
1399 # ;& and ;;& only apply to shell-style case
1400 if case_arm.right:
1401 id_ = case_arm.right.id
1402 if id_ == Id.Op_SemiAmp:
1403 # very weird semantic
1404 ignore_next_cond = True
1405 done = False
1406 elif id_ == Id.Op_DSemiAmp:
1407 # Keep going until next pattern
1408 done = False
1409
1410 elif case(pat_e.YshExprs):
1411 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1412
1413 for pat_expr in pat_exprs.exprs:
1414 expr_val = self.expr_ev.EvalExpr(
1415 pat_expr, case_arm.left)
1416
1417 if val_ops.ExactlyEqual(expr_val, to_match,
1418 case_arm.left):
1419 status = self._ExecuteList(case_arm.action)
1420 done = True
1421 break
1422
1423 elif case(pat_e.Eggex):
1424 eggex = cast(Eggex, case_arm.pattern)
1425 eggex_val = self.expr_ev.EvalEggex(eggex)
1426
1427 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1428 status = self._ExecuteList(case_arm.action)
1429 done = True
1430 break
1431
1432 elif case(pat_e.Else):
1433 status = self._ExecuteList(case_arm.action)
1434 done = True
1435 break
1436
1437 else:
1438 raise AssertionError()
1439
1440 if done: # first match wins
1441 break
1442
1443 return status
1444
1445 def _DoTimeBlock(self, node):
1446 # type: (command.TimeBlock) -> int
1447 # TODO:
1448 # - When do we need RUSAGE_CHILDREN?
1449 # - Respect TIMEFORMAT environment variable.
1450 # "If this variable is not set, Bash acts as if it had the value"
1451 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1452 # "A trailing newline is added when the format string is displayed."
1453
1454 s_real, s_user, s_sys = pyos.Time()
1455 status = self._Execute(node.pipeline)
1456 e_real, e_user, e_sys = pyos.Time()
1457 # note: mycpp doesn't support %.3f
1458 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1459
1460 return status
1461
1462 def _DoRedirect(self, node, cmd_st):
1463 # type: (command.Redirect, CommandStatus) -> int
1464
1465 status = 0
1466 redirects = [] # type: List[RedirValue]
1467
1468 try:
1469 for redir in node.redirects:
1470 redirects.append(self._EvalRedirect(redir))
1471 except error.RedirectEval as e:
1472 self.errfmt.PrettyPrintError(e)
1473 redirects = None
1474 except error.FailGlob as e: # e.g. echo hi > foo-*
1475 if not e.HasLocation():
1476 e.location = self.mem.GetFallbackLocation()
1477 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1478 redirects = None
1479
1480 if redirects is None:
1481 # Error evaluating redirect words
1482 status = 1
1483
1484 # Translation fix: redirect I/O errors may happen in a C++
1485 # destructor ~vm::ctx_Redirect, which means they must be signaled
1486 # by out params, not exceptions.
1487 io_errors = [] # type: List[error.IOError_OSError]
1488
1489 # If we evaluated redirects, apply/push them
1490 if status == 0:
1491 self.shell_ex.PushRedirects(redirects, io_errors)
1492 if len(io_errors):
1493 # core/process.py prints cryptic errors, so we repeat them
1494 # here. e.g. Bad File Descriptor
1495 self.errfmt.PrintMessage(
1496 'I/O error applying redirect: %s' %
1497 pyutil.strerror(io_errors[0]),
1498 self.mem.GetFallbackLocation())
1499 status = 1
1500
1501 # If we applied redirects successfully, run the command_t, and pop
1502 # them.
1503 if status == 0:
1504 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1505 status = self._Execute(node.child)
1506 if len(io_errors):
1507 # It would be better to point to the right redirect
1508 # operator, but we don't track it specifically
1509 e_die("Fatal error popping redirect: %s" %
1510 pyutil.strerror(io_errors[0]))
1511
1512 return status
1513
1514 def _Dispatch(self, node, cmd_st):
1515 # type: (command_t, CommandStatus) -> int
1516 """Switch on the command_t variants and execute them."""
1517
1518 # If we call RunCommandSub in a recursive call to the executor, this will
1519 # be set true (if strict_errexit is false). But it only lasts for one
1520 # command.
1521 probe('cmd_eval', '_Dispatch', node.tag())
1522 self.check_command_sub_status = False
1523
1524 UP_node = node
1525 with tagswitch(node) as case:
1526 if case(command_e.Simple): # LEAF command
1527 node = cast(command.Simple, UP_node)
1528
1529 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1530 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1531 # TODO: blame_tok should always be set.
1532 if node.blame_tok is not None:
1533 self.mem.SetTokenForLine(node.blame_tok)
1534
1535 self._MaybeRunDebugTrap()
1536 cmd_st.check_errexit = True
1537 status = self._DoSimple(node, cmd_st)
1538
1539 elif case(command_e.ExpandedAlias):
1540 node = cast(command.ExpandedAlias, UP_node)
1541 status = self._DoExpandedAlias(node)
1542
1543 elif case(command_e.Sentence):
1544 node = cast(command.Sentence, UP_node)
1545
1546 # Don't check_errexit since this isn't a leaf command
1547 if node.terminator.id == Id.Op_Semi:
1548 status = self._Execute(node.child)
1549 else:
1550 status = self.shell_ex.RunBackgroundJob(node.child)
1551
1552 elif case(command_e.Redirect):
1553 node = cast(command.Redirect, UP_node)
1554
1555 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1556 # dash/ash
1557 cmd_st.check_errexit = True
1558 status = self._DoRedirect(node, cmd_st)
1559
1560 elif case(command_e.Pipeline):
1561 node = cast(command.Pipeline, UP_node)
1562 status = self._DoPipeline(node, cmd_st)
1563
1564 elif case(command_e.Subshell):
1565 node = cast(command.Subshell, UP_node)
1566
1567 # This is a leaf from the parent process POV
1568 cmd_st.check_errexit = True
1569 status = self.shell_ex.RunSubshell(node.child)
1570
1571 elif case(command_e.DBracket): # LEAF command
1572 node = cast(command.DBracket, UP_node)
1573
1574 self.mem.SetTokenForLine(node.left)
1575 self._MaybeRunDebugTrap()
1576
1577 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1578
1579 cmd_st.check_errexit = True
1580 cmd_st.show_code = True # this is a "leaf" for errors
1581 result = self.bool_ev.EvalB(node.expr)
1582 status = 0 if result else 1
1583
1584 elif case(command_e.DParen): # LEAF command
1585 node = cast(command.DParen, UP_node)
1586
1587 self.mem.SetTokenForLine(node.left)
1588 self._MaybeRunDebugTrap()
1589
1590 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1591
1592 cmd_st.check_errexit = True
1593 cmd_st.show_code = True # this is a "leaf" for errors
1594 i = self.arith_ev.EvalToBigInt(node.child)
1595 status = 1 if mops.Equal(i, mops.ZERO) else 0
1596
1597 elif case(command_e.ControlFlow): # LEAF command
1598 node = cast(command.ControlFlow, UP_node)
1599
1600 self.mem.SetTokenForLine(node.keyword)
1601 self._MaybeRunDebugTrap()
1602
1603 status = self._DoControlFlow(node)
1604
1605 elif case(command_e.VarDecl): # LEAF command
1606 node = cast(command.VarDecl, UP_node)
1607
1608 # Point to var name (bare assignment has no keyword)
1609 self.mem.SetTokenForLine(node.lhs[0].left)
1610 status = self._DoVarDecl(node)
1611
1612 elif case(command_e.Mutation): # LEAF command
1613 node = cast(command.Mutation, UP_node)
1614
1615 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1616 self._DoMutation(node)
1617 status = 0 # if no exception is thrown, it succeeds
1618
1619 elif case(command_e.ShAssignment): # LEAF command
1620 node = cast(command.ShAssignment, UP_node)
1621
1622 self.mem.SetTokenForLine(node.pairs[0].left)
1623 self._MaybeRunDebugTrap()
1624
1625 # Only unqualified assignment a=b
1626 status = self._DoShAssignment(node, cmd_st)
1627
1628 elif case(command_e.Expr): # YSH LEAF command
1629 node = cast(command.Expr, UP_node)
1630
1631 self.mem.SetTokenForLine(node.keyword)
1632 # YSH debug trap?
1633
1634 status = self._DoExpr(node)
1635
1636 elif case(command_e.Retval): # YSH LEAF command
1637 node = cast(command.Retval, UP_node)
1638
1639 self.mem.SetTokenForLine(node.keyword)
1640 # YSH debug trap? I think we don't want the debug trap in func
1641 # dialect, for speed?
1642
1643 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1644 raise vm.ValueControlFlow(node.keyword, val)
1645
1646 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1647 # DoGroup has 'do' and 'done' spids for translation.
1648 elif case(command_e.CommandList):
1649 node = cast(command.CommandList, UP_node)
1650 status = self._ExecuteList(node.children)
1651
1652 elif case(command_e.DoGroup):
1653 node = cast(command.DoGroup, UP_node)
1654 status = self._ExecuteList(node.children)
1655
1656 elif case(command_e.BraceGroup):
1657 node = cast(BraceGroup, UP_node)
1658 status = self._ExecuteList(node.children)
1659
1660 elif case(command_e.AndOr):
1661 node = cast(command.AndOr, UP_node)
1662 status = self._DoAndOr(node, cmd_st)
1663
1664 elif case(command_e.WhileUntil):
1665 node = cast(command.WhileUntil, UP_node)
1666
1667 self.mem.SetTokenForLine(node.keyword)
1668 status = self._DoWhileUntil(node)
1669
1670 elif case(command_e.ForEach):
1671 node = cast(command.ForEach, UP_node)
1672
1673 self.mem.SetTokenForLine(node.keyword)
1674 status = self._DoForEach(node)
1675
1676 elif case(command_e.ForExpr):
1677 node = cast(command.ForExpr, UP_node)
1678
1679 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1680 status = self._DoForExpr(node)
1681
1682 elif case(command_e.ShFunction):
1683 node = cast(command.ShFunction, UP_node)
1684 self._DoShFunction(node)
1685 status = 0
1686
1687 elif case(command_e.Proc):
1688 node = cast(Proc, UP_node)
1689 self._DoProc(node)
1690 status = 0
1691
1692 elif case(command_e.Func):
1693 node = cast(Func, UP_node)
1694
1695 # Needed for error, when the func is an existing variable name
1696 self.mem.SetTokenForLine(node.name)
1697
1698 self._DoFunc(node)
1699 status = 0
1700
1701 elif case(command_e.If):
1702 node = cast(command.If, UP_node)
1703
1704 # No SetTokenForLine() because
1705 # - $LINENO can't appear directly in 'if'
1706 # - 'if' doesn't directly cause errors
1707 # It will be taken care of by command.Simple, condition, etc.
1708 status = self._DoIf(node)
1709
1710 elif case(command_e.NoOp):
1711 status = 0 # make it true
1712
1713 elif case(command_e.Case):
1714 node = cast(command.Case, UP_node)
1715
1716 # Must set location for 'case $LINENO'
1717 self.mem.SetTokenForLine(node.case_kw)
1718 self._MaybeRunDebugTrap()
1719 status = self._DoCase(node)
1720
1721 elif case(command_e.TimeBlock):
1722 node = cast(command.TimeBlock, UP_node)
1723 status = self._DoTimeBlock(node)
1724
1725 else:
1726 raise NotImplementedError(node.tag())
1727
1728 # Return to caller. Note the only case that didn't set it was Pipeline,
1729 # which set cmd_st.pipe_status.
1730 return status
1731
1732 def RunPendingTraps(self):
1733 # type: () -> None
1734
1735 trap_nodes = self.trap_state.GetPendingTraps()
1736 if trap_nodes is not None:
1737 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1738 True):
1739 for trap_node in trap_nodes:
1740 # Isolate the exit status.
1741 with state.ctx_Registers(self.mem):
1742 # Trace it. TODO: Show the trap kind too
1743 with dev.ctx_Tracer(self.tracer, 'trap', None):
1744 self._Execute(trap_node)
1745
1746 def _Execute(self, node):
1747 # type: (command_t) -> int
1748 """Call _Dispatch(), and performs the errexit check.
1749
1750 Also runs trap handlers.
1751 """
1752 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1753 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1754 # and maybe throw an exception.
1755 self.RunPendingTraps()
1756
1757 # We only need this somewhat hacky check in osh-cpp since python's runtime
1758 # handles SIGINT for us in osh.
1759 if mylib.CPP:
1760 if self.signal_safe.PollSigInt():
1761 raise KeyboardInterrupt()
1762
1763 # Manual GC point before every statement
1764 mylib.MaybeCollect()
1765
1766 # Optimization: These 2 records have rarely-used lists, so we don't pass
1767 # alloc_lists=True. We create them on demand.
1768 cmd_st = CommandStatus.CreateNull()
1769 if len(self.status_array_pool):
1770 # Optimized to avoid allocs
1771 process_sub_st = self.status_array_pool.pop()
1772 else:
1773 process_sub_st = StatusArray.CreateNull()
1774
1775 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1776 try:
1777 status = self._Dispatch(node, cmd_st)
1778 except error.FailGlob as e:
1779 if not e.HasLocation(): # Last resort!
1780 e.location = self.mem.GetFallbackLocation()
1781 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1782 status = 1 # another redirect word eval error
1783 cmd_st.check_errexit = True # failglob + errexit
1784
1785 # Now we've waited for process subs
1786
1787 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1788 # @_pipeline_status
1789 pipe_status = cmd_st.pipe_status
1790 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1791 # makes it annoying to check both _process_sub_status and
1792 # _pipeline_status
1793
1794 errexit_loc = loc.Missing # type: loc_t
1795 if pipe_status is not None:
1796 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1797 # for a REAL pipeline (but not singleton pipelines)
1798 assert status == -1, (
1799 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1800 status)
1801
1802 self.mem.SetPipeStatus(pipe_status)
1803
1804 if self.exec_opts.pipefail():
1805 # The status is that of the last command that is non-zero.
1806 status = 0
1807 for i, st in enumerate(pipe_status):
1808 if st != 0:
1809 status = st
1810 errexit_loc = cmd_st.pipe_locs[i]
1811 else:
1812 # The status is that of last command, period.
1813 status = pipe_status[-1]
1814
1815 if cmd_st.pipe_negated:
1816 status = 1 if status == 0 else 0
1817
1818 # Compute status from _process_sub_status
1819 if process_sub_st.codes is None:
1820 # Optimized to avoid allocs
1821 self.status_array_pool.append(process_sub_st)
1822 else:
1823 codes = process_sub_st.codes
1824 self.mem.SetProcessSubStatus(codes)
1825 if status == 0 and self.exec_opts.process_sub_fail():
1826 # Choose the LAST non-zero status, consistent with pipefail above.
1827 for i, st in enumerate(codes):
1828 if st != 0:
1829 status = st
1830 errexit_loc = process_sub_st.locs[i]
1831
1832 self.mem.SetLastStatus(status)
1833
1834 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1835 # However, any bash construct can appear in a pipeline. So it's easier
1836 # just to put it at the end, instead of after every node.
1837 #
1838 # Possible exceptions:
1839 # - function def (however this always exits 0 anyway)
1840 # - assignment - its result should be the result of the RHS?
1841 # - e.g. arith sub, command sub? I don't want arith sub.
1842 # - ControlFlow: always raises, it has no status.
1843 if cmd_st.check_errexit:
1844 #log('cmd_st %s', cmd_st)
1845 self._CheckStatus(status, cmd_st, node, errexit_loc)
1846
1847 return status
1848
1849 def _ExecuteList(self, children):
1850 # type: (List[command_t]) -> int
1851 status = 0 # for empty list
1852 for child in children:
1853 # last status wins
1854 status = self._Execute(child)
1855 return status
1856
1857 def LastStatus(self):
1858 # type: () -> int
1859 """For main_loop.py to determine the exit code of the shell itself."""
1860 return self.mem.LastStatus()
1861
1862 def _NoForkLast(self, node):
1863 # type: (command_t) -> None
1864
1865 if 0:
1866 log('optimizing')
1867 node.PrettyPrint(sys.stderr)
1868 log('')
1869
1870 UP_node = node
1871 with tagswitch(node) as case:
1872 if case(command_e.Simple):
1873 node = cast(command.Simple, UP_node)
1874 node.do_fork = False
1875 if 0:
1876 log('Simple optimized')
1877
1878 elif case(command_e.Pipeline):
1879 node = cast(command.Pipeline, UP_node)
1880 if node.negated is None:
1881 #log ('pipe')
1882 self._NoForkLast(node.children[-1])
1883
1884 elif case(command_e.Sentence):
1885 node = cast(command.Sentence, UP_node)
1886 self._NoForkLast(node.child)
1887
1888 elif case(command_e.CommandList):
1889 # Subshells start with CommandList, even if there's only one.
1890 node = cast(command.CommandList, UP_node)
1891 self._NoForkLast(node.children[-1])
1892
1893 elif case(command_e.BraceGroup):
1894 # TODO: What about redirects?
1895 node = cast(BraceGroup, UP_node)
1896 self._NoForkLast(node.children[-1])
1897
1898 def _RemoveSubshells(self, node):
1899 # type: (command_t) -> command_t
1900 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1901
1902 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1903 be correct otherwise.
1904 """
1905 UP_node = node
1906 with tagswitch(node) as case:
1907 if case(command_e.Subshell):
1908 node = cast(command.Subshell, UP_node)
1909 # Optimize ( ( date ) ) etc.
1910 return self._RemoveSubshells(node.child)
1911 return node
1912
1913 def ExecuteAndCatch(self, node, cmd_flags=0):
1914 # type: (command_t, int) -> Tuple[bool, bool]
1915 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1916
1917 Args:
1918 node: LST subtree
1919 optimize: Whether to exec the last process rather than fork/exec
1920
1921 Returns:
1922 TODO: use enum 'why' instead of the 2 booleans
1923
1924 Used by
1925 - main_loop.py.
1926 - SubProgramThunk for pipelines, subshell, command sub, process sub
1927 - TODO: Signals besides EXIT trap
1928
1929 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
1930 finally_exit boolean. We use a different algorithm.
1931 """
1932 if cmd_flags & Optimize:
1933 node = self._RemoveSubshells(node)
1934 # Using _NoForkLast() hides exit code for ERR trap, but only in non-interactive mode!
1935 # Example:
1936 # - ./bin/osh -c 'trap "echo ERR" ERR ; date X' - does not show ERR
1937 # - ./bin/osh -c 'trap "echo ERR" ERR ; date X ; exit' - does show ERR
1938 # This is why it is only called interactive mode
1939 if self.mutable_opts.Get(option_i.interactive):
1940 self._NoForkLast(node) # turn the last ones into exec
1941
1942 if 0:
1943 log('after opt:')
1944 node.PrettyPrint()
1945 log('')
1946
1947 is_return = False
1948 is_fatal = False
1949 is_errexit = False
1950
1951 err = None # type: error.FatalRuntime
1952 status = -1 # uninitialized
1953
1954 try:
1955 options = [] # type: List[int]
1956 if cmd_flags & NoDebugTrap:
1957 options.append(option_i._no_debug_trap)
1958 if cmd_flags & NoErrTrap:
1959 options.append(option_i._no_err_trap)
1960 with state.ctx_Option(self.mutable_opts, options, True):
1961 status = self._Execute(node)
1962 except vm.IntControlFlow as e:
1963 if cmd_flags & RaiseControlFlow:
1964 raise # 'eval break' and 'source return.sh', etc.
1965 else:
1966 # Return at top level is OK, unlike in bash.
1967 if e.IsReturn():
1968 is_return = True
1969 status = e.StatusCode()
1970 else:
1971 # TODO: This error message is invalid. Can also happen in eval.
1972 # We need a flag.
1973
1974 # Invalid control flow
1975 self.errfmt.Print_(
1976 "Loop and control flow can't be in different processes",
1977 blame_loc=e.token)
1978 is_fatal = True
1979 # All shells exit 0 here. It could be hidden behind
1980 # strict_control_flow if the incompatibility causes problems.
1981 status = 1
1982 except error.Parse as e:
1983 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
1984 raise
1985 except error.ErrExit as e:
1986 err = e
1987 is_errexit = True
1988 except error.FatalRuntime as e:
1989 err = e
1990
1991 if err:
1992 status = err.ExitStatus()
1993
1994 is_fatal = True
1995 # Do this before unwinding stack
1996 self.dumper.MaybeRecord(self, err)
1997
1998 if not err.HasLocation(): # Last resort!
1999 #log('Missing location')
2000 err.location = self.mem.GetFallbackLocation()
2001 #log('%s', err.location)
2002
2003 if is_errexit:
2004 if self.exec_opts.verbose_errexit():
2005 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2006 posix.getpid())
2007 else:
2008 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2009
2010 assert status >= 0, 'Should have been initialized'
2011
2012 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2013 # created a crash dump. So we get 2 or more of them.
2014 self.dumper.MaybeDump(status)
2015
2016 self.mem.SetLastStatus(status)
2017 return is_return, is_fatal
2018
2019 def EvalCommand(self, block):
2020 # type: (command_t) -> int
2021 """For builtins to evaluate command args.
2022
2023 e.g. cd /tmp (x)
2024 """
2025 status = 0
2026 try:
2027 status = self._Execute(block) # can raise FatalRuntimeError, etc.
2028 except vm.IntControlFlow as e: # A block is more like a function.
2029 # return in a block
2030 if e.IsReturn():
2031 status = e.StatusCode()
2032 else:
2033 e_die('Unexpected control flow in block', e.token)
2034
2035 return status
2036
2037 def MaybeRunExitTrap(self, mut_status):
2038 # type: (IntParamBox) -> None
2039 """If an EXIT trap handler exists, run it.
2040
2041 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2042 all bash/dash/mksh seem to agree on it. See cases in
2043 builtin-trap.test.sh.
2044
2045 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2046 of this awkward interface. But that's true in Python and not C!
2047
2048 Could use i & (n-1) == i & 255 because we have a power of 2.
2049 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2050 """
2051 node = self.trap_state.GetHook('EXIT') # type: command_t
2052 if node:
2053 # NOTE: Don't set option_i._running_trap, because that's for
2054 # RunPendingTraps() in the MAIN LOOP
2055 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2056 try:
2057 is_return, is_fatal = self.ExecuteAndCatch(node)
2058 except util.UserExit as e: # explicit exit
2059 mut_status.i = e.status
2060 return
2061 if is_return: # explicit 'return' in the trap handler!
2062 mut_status.i = self.LastStatus()
2063
2064 def _MaybeRunDebugTrap(self):
2065 # type: () -> None
2066 """Run user-specified DEBUG code before certain commands."""
2067 node = self.trap_state.GetHook('DEBUG') # type: command_t
2068 if node is None:
2069 return
2070
2071 # Fix lastpipe / job control / DEBUG trap interaction
2072 if self.exec_opts._no_debug_trap():
2073 return
2074
2075 # Don't run recursively run traps, etc.
2076 if not self.mem.ShouldRunDebugTrap():
2077 return
2078
2079 # NOTE: Don't set option_i._running_trap, because that's for
2080 # RunPendingTraps() in the MAIN LOOP
2081
2082 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2083 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2084 # for SetTokenForLine $LINENO
2085 with state.ctx_DebugTrap(self.mem):
2086 # Don't catch util.UserExit, etc.
2087 self._Execute(node)
2088
2089 def _MaybeRunErrTrap(self):
2090 # type: () -> None
2091 """
2092 Run user-specified ERR code after checking the status of certain
2093 commands (pipelines)
2094 """
2095 node = self.trap_state.GetHook('ERR') # type: command_t
2096 if node is None:
2097 return
2098
2099 # ERR trap is only run for a whole pipeline, not its parts
2100 if self.exec_opts._no_err_trap():
2101 return
2102
2103 # Prevent infinite recursion
2104 if self.mem.running_err_trap:
2105 return
2106
2107 # "disabled errexit" rule
2108 if self.mutable_opts.ErrExitIsDisabled():
2109 return
2110
2111 # bash rule - affected by set -o errtrace
2112 # if self.mem.InsideFunction():
2113 # return
2114
2115 # NOTE: Don't set option_i._running_trap, because that's for
2116 # RunPendingTraps() in the MAIN LOOP
2117
2118 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2119 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2120 with state.ctx_ErrTrap(self.mem):
2121 self._Execute(node)
2122
2123 def RunProc(self, proc, cmd_val):
2124 # type: (value.Proc, cmd_value.Argv) -> int
2125 """Run procs aka "shell functions".
2126
2127 For SimpleCommand and registered completion hooks.
2128 """
2129 sig = proc.sig
2130 if sig.tag() == proc_sig_e.Closed:
2131 # We're binding named params. User should use @rest. No 'shift'.
2132 proc_argv = [] # type: List[str]
2133 else:
2134 proc_argv = cmd_val.argv[1:]
2135
2136 # Hm this sets "$@". TODO: Set ARGV only
2137 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2138 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2139
2140 # Redirects still valid for functions.
2141 # Here doc causes a pipe and Process(SubProgramThunk).
2142 try:
2143 status = self._Execute(proc.body)
2144 except vm.IntControlFlow as e:
2145 if e.IsReturn():
2146 status = e.StatusCode()
2147 else:
2148 # break/continue used in the wrong place.
2149 e_die(
2150 'Unexpected %r (in proc call)' %
2151 lexer.TokenVal(e.token), e.token)
2152 except error.FatalRuntime as e:
2153 # Dump the stack before unwinding it
2154 self.dumper.MaybeRecord(self, e)
2155 raise
2156
2157 return status
2158
2159 def RunFuncForCompletion(self, proc, argv):
2160 # type: (value.Proc, List[str]) -> int
2161 """
2162 Args:
2163 argv: $1 $2 $3 ... not including $0
2164 """
2165 cmd_val = MakeBuiltinArgv(argv)
2166
2167 # TODO: Change this to run YSH procs and funcs too
2168 try:
2169 status = self.RunProc(proc, cmd_val)
2170 except error.FatalRuntime as e:
2171 self.errfmt.PrettyPrintError(e)
2172 status = e.ExitStatus()
2173 except vm.IntControlFlow as e:
2174 # shouldn't be able to exit the shell from a completion hook!
2175 # TODO: Avoid overwriting the prompt!
2176 self.errfmt.Print_('Attempted to exit from completion hook.',
2177 blame_loc=e.token)
2178
2179 status = 1
2180 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2181 return status
2182
2183
2184# vim: sw=4