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

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