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

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