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

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