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

2169 lines, 1341 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
1387 done = False # Should we try the next arm?
1388
1389 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1390 ignore_next_cond = False
1391
1392 for case_arm in node.arms:
1393 with tagswitch(case_arm.pattern) as case:
1394 if case(pat_e.Words):
1395 if to_match.tag() != value_e.Str:
1396 continue # A non-string `to_match` will never match a pat.Words
1397 to_match_str = cast(value.Str, to_match)
1398
1399 pat_words = cast(pat.Words, case_arm.pattern)
1400
1401 this_arm_matches = False
1402 if ignore_next_cond: # Special handling for ;&
1403 this_arm_matches = True
1404 ignore_next_cond = False
1405 else:
1406 for pat_word in pat_words.words:
1407 word_val = self.word_ev.EvalWordToString(
1408 pat_word, word_eval.QUOTE_FNMATCH)
1409
1410 if libc.fnmatch(word_val.s, to_match_str.s,
1411 fnmatch_flags):
1412 this_arm_matches = True
1413 break # Stop at first pattern
1414
1415 if this_arm_matches:
1416 status = self._ExecuteList(case_arm.action)
1417 done = True
1418
1419 # ;& and ;;& only apply to shell-style case
1420 if case_arm.right:
1421 id_ = case_arm.right.id
1422 if id_ == Id.Op_SemiAmp:
1423 # very weird semantic
1424 ignore_next_cond = True
1425 done = False
1426 elif id_ == Id.Op_DSemiAmp:
1427 # Keep going until next pattern
1428 done = False
1429
1430 elif case(pat_e.YshExprs):
1431 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1432
1433 for pat_expr in pat_exprs.exprs:
1434 expr_val = self.expr_ev.EvalExpr(
1435 pat_expr, case_arm.left)
1436
1437 if val_ops.ExactlyEqual(expr_val, to_match,
1438 case_arm.left):
1439 status = self._ExecuteList(case_arm.action)
1440 done = True
1441 break
1442
1443 elif case(pat_e.Eggex):
1444 eggex = cast(Eggex, case_arm.pattern)
1445 eggex_val = self.expr_ev.EvalEggex(eggex)
1446
1447 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1448 status = self._ExecuteList(case_arm.action)
1449 done = True
1450 break
1451
1452 elif case(pat_e.Else):
1453 status = self._ExecuteList(case_arm.action)
1454 done = True
1455 break
1456
1457 else:
1458 raise AssertionError()
1459
1460 if done: # first match wins
1461 break
1462
1463 return status
1464
1465 def _DoTimeBlock(self, node):
1466 # type: (command.TimeBlock) -> int
1467 # TODO:
1468 # - When do we need RUSAGE_CHILDREN?
1469 # - Respect TIMEFORMAT environment variable.
1470 # "If this variable is not set, Bash acts as if it had the value"
1471 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1472 # "A trailing newline is added when the format string is displayed."
1473
1474 s_real, s_user, s_sys = pyos.Time()
1475 status = self._Execute(node.pipeline)
1476 e_real, e_user, e_sys = pyos.Time()
1477 # note: mycpp doesn't support %.3f
1478 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1479
1480 return status
1481
1482 def _Dispatch(self, node, cmd_st):
1483 # type: (command_t, CommandStatus) -> int
1484 """Switch on the command_t variants and execute them."""
1485
1486 # If we call RunCommandSub in a recursive call to the executor, this will
1487 # be set true (if strict_errexit is false). But it only lasts for one
1488 # command.
1489 self.check_command_sub_status = False
1490
1491 UP_node = node
1492 with tagswitch(node) as case:
1493 if case(command_e.Simple): # LEAF command
1494 node = cast(command.Simple, UP_node)
1495
1496 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1497 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1498 # TODO: blame_tok should always be set.
1499 if node.blame_tok is not None:
1500 self.mem.SetTokenForLine(node.blame_tok)
1501
1502 self._MaybeRunDebugTrap()
1503 status = self._DoSimple(node, cmd_st)
1504
1505 elif case(command_e.ExpandedAlias):
1506 node = cast(command.ExpandedAlias, UP_node)
1507 status = self._DoExpandedAlias(node)
1508
1509 elif case(command_e.Sentence):
1510 node = cast(command.Sentence, UP_node)
1511
1512 # Don't check_errexit since this isn't a leaf command
1513 if node.terminator.id == Id.Op_Semi:
1514 status = self._Execute(node.child)
1515 else:
1516 status = self.shell_ex.RunBackgroundJob(node.child)
1517
1518 elif case(command_e.Pipeline):
1519 node = cast(command.Pipeline, UP_node)
1520 status = self._DoPipeline(node, cmd_st)
1521
1522 elif case(command_e.Subshell):
1523 node = cast(command.Subshell, UP_node)
1524 cmd_st.check_errexit = True
1525 status = self.shell_ex.RunSubshell(node.child)
1526
1527 elif case(command_e.DBracket): # LEAF command
1528 node = cast(command.DBracket, UP_node)
1529
1530 self.mem.SetTokenForLine(node.left)
1531 self._MaybeRunDebugTrap()
1532
1533 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1534
1535 cmd_st.check_errexit = True
1536 cmd_st.show_code = True # this is a "leaf" for errors
1537 result = self.bool_ev.EvalB(node.expr)
1538 status = 0 if result else 1
1539
1540 elif case(command_e.DParen): # LEAF command
1541 node = cast(command.DParen, UP_node)
1542
1543 self.mem.SetTokenForLine(node.left)
1544 self._MaybeRunDebugTrap()
1545
1546 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1547
1548 cmd_st.check_errexit = True
1549 cmd_st.show_code = True # this is a "leaf" for errors
1550 i = self.arith_ev.EvalToInt(node.child)
1551 status = 1 if i == 0 else 0
1552
1553 elif case(command_e.ControlFlow): # LEAF command
1554 node = cast(command.ControlFlow, UP_node)
1555
1556 self.mem.SetTokenForLine(node.keyword)
1557 self._MaybeRunDebugTrap()
1558
1559 status = self._DoControlFlow(node)
1560
1561 elif case(command_e.VarDecl): # LEAF command
1562 node = cast(command.VarDecl, UP_node)
1563
1564 # Point to var name (bare assignment has no keyword)
1565 self.mem.SetTokenForLine(node.lhs[0].left)
1566 status = self._DoVarDecl(node)
1567
1568 elif case(command_e.Mutation): # LEAF command
1569 node = cast(command.Mutation, UP_node)
1570
1571 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1572 self._DoMutation(node)
1573 status = 0 # if no exception is thrown, it succeeds
1574
1575 elif case(command_e.ShAssignment): # LEAF command
1576 node = cast(command.ShAssignment, UP_node)
1577
1578 self.mem.SetTokenForLine(node.pairs[0].left)
1579 self._MaybeRunDebugTrap()
1580
1581 # Only unqualified assignment a=b
1582 status = self._DoShAssignment(node, cmd_st)
1583
1584 elif case(command_e.Expr): # YSH LEAF command
1585 node = cast(command.Expr, UP_node)
1586
1587 self.mem.SetTokenForLine(node.keyword)
1588 # YSH debug trap?
1589
1590 status = self._DoExpr(node)
1591
1592 elif case(command_e.Retval): # YSH LEAF command
1593 node = cast(command.Retval, UP_node)
1594
1595 self.mem.SetTokenForLine(node.keyword)
1596 # YSH debug trap? I think we don't want the debug trap in func
1597 # dialect, for speed?
1598
1599 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1600 raise vm.ValueControlFlow(node.keyword, val)
1601
1602 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1603 # DoGroup has 'do' and 'done' spids for translation.
1604 elif case(command_e.CommandList):
1605 node = cast(command.CommandList, UP_node)
1606 status = self._ExecuteList(node.children)
1607 cmd_st.check_errexit = False
1608
1609 elif case(command_e.DoGroup):
1610 node = cast(command.DoGroup, UP_node)
1611 status = self._ExecuteList(node.children)
1612 cmd_st.check_errexit = False # not real statements
1613
1614 elif case(command_e.BraceGroup):
1615 node = cast(BraceGroup, UP_node)
1616 status = self._ExecuteList(node.children)
1617 cmd_st.check_errexit = False
1618
1619 elif case(command_e.AndOr):
1620 node = cast(command.AndOr, UP_node)
1621 status = self._DoAndOr(node, cmd_st)
1622
1623 elif case(command_e.WhileUntil):
1624 node = cast(command.WhileUntil, UP_node)
1625
1626 self.mem.SetTokenForLine(node.keyword)
1627 status = self._DoWhileUntil(node)
1628
1629 elif case(command_e.ForEach):
1630 node = cast(command.ForEach, UP_node)
1631
1632 self.mem.SetTokenForLine(node.keyword)
1633 status = self._DoForEach(node)
1634
1635 elif case(command_e.ForExpr):
1636 node = cast(command.ForExpr, UP_node)
1637
1638 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1639 status = self._DoForExpr(node)
1640
1641 elif case(command_e.ShFunction):
1642 node = cast(command.ShFunction, UP_node)
1643 self._DoShFunction(node)
1644 status = 0
1645
1646 elif case(command_e.Proc):
1647 node = cast(Proc, UP_node)
1648 self._DoProc(node)
1649 status = 0
1650
1651 elif case(command_e.Func):
1652 node = cast(Func, UP_node)
1653
1654 # Needed for error, when the func is an existing variable name
1655 self.mem.SetTokenForLine(node.name)
1656
1657 self._DoFunc(node)
1658 status = 0
1659
1660 elif case(command_e.If):
1661 node = cast(command.If, UP_node)
1662
1663 # No SetTokenForLine() because
1664 # - $LINENO can't appear directly in 'if'
1665 # - 'if' doesn't directly cause errors
1666 # It will be taken care of by command.Simple, condition, etc.
1667 status = self._DoIf(node)
1668
1669 elif case(command_e.NoOp):
1670 status = 0 # make it true
1671
1672 elif case(command_e.Case):
1673 node = cast(command.Case, UP_node)
1674
1675 # Must set location for 'case $LINENO'
1676 self.mem.SetTokenForLine(node.case_kw)
1677 self._MaybeRunDebugTrap()
1678 status = self._DoCase(node)
1679
1680 elif case(command_e.TimeBlock):
1681 node = cast(command.TimeBlock, UP_node)
1682 status = self._DoTimeBlock(node)
1683
1684 else:
1685 raise NotImplementedError(node.tag())
1686
1687 # Return to caller. Note the only case that didn't set it was Pipeline,
1688 # which set cmd_st.pipe_status.
1689 return status
1690
1691 def RunPendingTraps(self):
1692 # type: () -> None
1693
1694 trap_nodes = self.trap_state.GetPendingTraps()
1695 if trap_nodes is not None:
1696 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1697 True):
1698 for trap_node in trap_nodes:
1699 # Isolate the exit status.
1700 with state.ctx_Registers(self.mem):
1701 # Trace it. TODO: Show the trap kind too
1702 with dev.ctx_Tracer(self.tracer, 'trap', None):
1703 self._Execute(trap_node)
1704
1705 def _Execute(self, node):
1706 # type: (command_t) -> int
1707 """Apply redirects, call _Dispatch(), and performs the errexit check.
1708
1709 Also runs trap handlers.
1710 """
1711 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1712 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1713 # and maybe throw an exception.
1714 self.RunPendingTraps()
1715
1716 # We only need this somewhat hacky check in osh-cpp since python's runtime
1717 # handles SIGINT for us in osh.
1718 if mylib.CPP:
1719 if self.signal_safe.PollSigInt():
1720 raise KeyboardInterrupt()
1721
1722 # Manual GC point before every statement
1723 mylib.MaybeCollect()
1724
1725 # This has to go around redirect handling because the process sub could be
1726 # in the redirect word:
1727 # { echo one; echo two; } > >(tac)
1728
1729 # Optimization: These 2 records have rarely-used lists, so we don't pass
1730 # alloc_lists=True. We create them on demand.
1731 cmd_st = CommandStatus.CreateNull()
1732 if len(self.status_array_pool):
1733 # Optimized to avoid allocs
1734 process_sub_st = self.status_array_pool.pop()
1735 else:
1736 process_sub_st = StatusArray.CreateNull()
1737
1738 errexit_loc = loc.Missing # type: loc_t
1739 check_errexit = True
1740
1741 status = 0
1742
1743 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1744 try:
1745 redirects = self._EvalRedirects(node)
1746 except error.RedirectEval as e:
1747 self.errfmt.PrettyPrintError(e)
1748 redirects = None
1749 except error.FailGlob as e: # e.g. echo hi > foo-*
1750 if not e.HasLocation():
1751 e.location = self.mem.GetFallbackLocation()
1752 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1753 redirects = None
1754 if redirects is None:
1755 # Error evaluating redirect words
1756 status = 1
1757
1758 # Translation fix: redirect I/O errors may happen in a C++
1759 # destructor ~vm::ctx_Redirect, which means they must be signaled
1760 # by out params, not exceptions.
1761 io_errors = [] # type: List[error.IOError_OSError]
1762
1763 # If we evaluated redirects, apply/push them
1764 if status == 0:
1765 self.shell_ex.PushRedirects(redirects, io_errors)
1766 if len(io_errors):
1767 # core/process.py prints cryptic errors, so we repeat them
1768 # here. e.g. Bad File Descriptor
1769 self.errfmt.PrintMessage(
1770 'I/O error applying redirect: %s' %
1771 pyutil.strerror(io_errors[0]),
1772 self.mem.GetFallbackLocation())
1773 status = 1
1774
1775 # If we applied redirects successfully, run the command_t, and pop
1776 # them.
1777 if status == 0:
1778 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1779 try:
1780 status = self._Dispatch(node, cmd_st)
1781 check_errexit = cmd_st.check_errexit
1782 except error.FailGlob as e:
1783 if not e.HasLocation(): # Last resort!
1784 e.location = self.mem.GetFallbackLocation()
1785 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1786 status = 1 # another redirect word eval error
1787 check_errexit = True # probably not necessary?
1788 if len(io_errors):
1789 # It would be better to point to the right redirect
1790 # operator, but we don't track it specifically
1791 e_die("Fatal error popping redirect: %s" %
1792 pyutil.strerror(io_errors[0]))
1793
1794 # end with - we've waited for process subs
1795
1796 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1797 # @_pipeline_status
1798 pipe_status = cmd_st.pipe_status
1799 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1800 # makes it annoying to check both _process_sub_status and
1801 # _pipeline_status
1802
1803 if pipe_status is not None:
1804 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1805 # for a REAL pipeline (but not singleton pipelines)
1806 assert status == -1, (
1807 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1808 status)
1809
1810 self.mem.SetPipeStatus(pipe_status)
1811
1812 if self.exec_opts.pipefail():
1813 # The status is that of the last command that is non-zero.
1814 status = 0
1815 for i, st in enumerate(pipe_status):
1816 if st != 0:
1817 status = st
1818 errexit_loc = cmd_st.pipe_locs[i]
1819 else:
1820 # The status is that of last command, period.
1821 status = pipe_status[-1]
1822
1823 if cmd_st.pipe_negated:
1824 status = 1 if status == 0 else 0
1825
1826 # Compute status from _process_sub_status
1827 if process_sub_st.codes is None:
1828 # Optimized to avoid allocs
1829 self.status_array_pool.append(process_sub_st)
1830 else:
1831 codes = process_sub_st.codes
1832 self.mem.SetProcessSubStatus(codes)
1833 if status == 0 and self.exec_opts.process_sub_fail():
1834 # Choose the LAST non-zero status, consistent with pipefail above.
1835 for i, st in enumerate(codes):
1836 if st != 0:
1837 status = st
1838 errexit_loc = process_sub_st.locs[i]
1839
1840 self.mem.SetLastStatus(status)
1841
1842 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1843 # However, any bash construct can appear in a pipeline. So it's easier
1844 # just to put it at the end, instead of after every node.
1845 #
1846 # Possible exceptions:
1847 # - function def (however this always exits 0 anyway)
1848 # - assignment - its result should be the result of the RHS?
1849 # - e.g. arith sub, command sub? I don't want arith sub.
1850 # - ControlFlow: always raises, it has no status.
1851 if check_errexit:
1852 #log('cmd_st %s', cmd_st)
1853 self._CheckStatus(status, cmd_st, node, errexit_loc)
1854
1855 return status
1856
1857 def _ExecuteList(self, children):
1858 # type: (List[command_t]) -> int
1859 status = 0 # for empty list
1860 for child in children:
1861 # last status wins
1862 status = self._Execute(child)
1863 return status
1864
1865 def LastStatus(self):
1866 # type: () -> int
1867 """For main_loop.py to determine the exit code of the shell itself."""
1868 return self.mem.LastStatus()
1869
1870 def _NoForkLast(self, node):
1871 # type: (command_t) -> None
1872
1873 if 0:
1874 log('optimizing')
1875 node.PrettyPrint(sys.stderr)
1876 log('')
1877
1878 UP_node = node
1879 with tagswitch(node) as case:
1880 if case(command_e.Simple):
1881 node = cast(command.Simple, UP_node)
1882 node.do_fork = False
1883 if 0:
1884 log('Simple optimized')
1885
1886 elif case(command_e.Pipeline):
1887 node = cast(command.Pipeline, UP_node)
1888 if node.negated is None:
1889 #log ('pipe')
1890 self._NoForkLast(node.children[-1])
1891
1892 elif case(command_e.Sentence):
1893 node = cast(command.Sentence, UP_node)
1894 self._NoForkLast(node.child)
1895
1896 elif case(command_e.CommandList):
1897 # Subshells start with CommandList, even if there's only one.
1898 node = cast(command.CommandList, UP_node)
1899 self._NoForkLast(node.children[-1])
1900
1901 elif case(command_e.BraceGroup):
1902 # TODO: What about redirects?
1903 node = cast(BraceGroup, UP_node)
1904 self._NoForkLast(node.children[-1])
1905
1906 def _RemoveSubshells(self, node):
1907 # type: (command_t) -> command_t
1908 """Eliminate redundant subshells like ( echo hi ) | wc -l etc."""
1909 UP_node = node
1910 with tagswitch(node) as case:
1911 if case(command_e.Subshell):
1912 node = cast(command.Subshell, UP_node)
1913 if len(node.redirects) == 0:
1914 # Note: technically we could optimize this into BraceGroup with
1915 # redirects. Some shells appear to do that.
1916 if 0:
1917 log('removing subshell')
1918 # Optimize ( ( date ) ) etc.
1919 return self._RemoveSubshells(node.child)
1920 return node
1921
1922 def ExecuteAndCatch(self, node, cmd_flags=0):
1923 # type: (command_t, int) -> Tuple[bool, bool]
1924 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1925
1926 Args:
1927 node: LST subtree
1928 optimize: Whether to exec the last process rather than fork/exec
1929
1930 Returns:
1931 TODO: use enum 'why' instead of the 2 booleans
1932
1933 Used by
1934 - main_loop.py.
1935 - SubProgramThunk for pipelines, subshell, command sub, process sub
1936 - TODO: Signals besides EXIT trap
1937
1938 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
1939 finally_exit boolean. We use a different algorithm.
1940 """
1941 if cmd_flags & Optimize:
1942 node = self._RemoveSubshells(node)
1943 self._NoForkLast(node) # turn the last ones into exec
1944
1945 if 0:
1946 log('after opt:')
1947 node.PrettyPrint()
1948 log('')
1949
1950 is_return = False
1951 is_fatal = False
1952 is_errexit = False
1953
1954 err = None # type: error.FatalRuntime
1955 status = -1 # uninitialized
1956
1957 try:
1958 if cmd_flags & NoDebugTrap:
1959 with state.ctx_Option(self.mutable_opts,
1960 [option_i._no_debug_trap], True):
1961 status = self._Execute(node)
1962 else:
1963 status = self._Execute(node)
1964 except vm.IntControlFlow as e:
1965 if cmd_flags & RaiseControlFlow:
1966 raise # 'eval break' and 'source return.sh', etc.
1967 else:
1968 # Return at top level is OK, unlike in bash.
1969 if e.IsReturn():
1970 is_return = True
1971 status = e.StatusCode()
1972 else:
1973 # TODO: This error message is invalid. Can also happen in eval.
1974 # We need a flag.
1975
1976 # Invalid control flow
1977 self.errfmt.Print_(
1978 "Loop and control flow can't be in different processes",
1979 blame_loc=e.token)
1980 is_fatal = True
1981 # All shells exit 0 here. It could be hidden behind
1982 # strict_control_flow if the incompatibility causes problems.
1983 status = 1
1984 except error.Parse as e:
1985 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
1986 raise
1987 except error.ErrExit as e:
1988 err = e
1989 is_errexit = True
1990 except error.FatalRuntime as e:
1991 err = e
1992
1993 if err:
1994 status = err.ExitStatus()
1995
1996 is_fatal = True
1997 # Do this before unwinding stack
1998 self.dumper.MaybeRecord(self, err)
1999
2000 if not err.HasLocation(): # Last resort!
2001 #log('Missing location')
2002 err.location = self.mem.GetFallbackLocation()
2003 #log('%s', err.location)
2004
2005 if is_errexit:
2006 if self.exec_opts.verbose_errexit():
2007 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2008 posix.getpid())
2009 else:
2010 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2011
2012 assert status >= 0, 'Should have been initialized'
2013
2014 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2015 # created a crash dump. So we get 2 or more of them.
2016 self.dumper.MaybeDump(status)
2017
2018 self.mem.SetLastStatus(status)
2019 return is_return, is_fatal
2020
2021 def EvalCommand(self, block):
2022 # type: (command_t) -> int
2023 """For builtins to evaluate command args.
2024
2025 e.g. cd /tmp (x)
2026 """
2027 status = 0
2028 try:
2029 status = self._Execute(block) # can raise FatalRuntimeError, etc.
2030 except vm.IntControlFlow as e: # A block is more like a function.
2031 # return in a block
2032 if e.IsReturn():
2033 status = e.StatusCode()
2034 else:
2035 e_die('Unexpected control flow in block', e.token)
2036
2037 return status
2038
2039 def MaybeRunExitTrap(self, mut_status):
2040 # type: (IntParamBox) -> None
2041 """If an EXIT trap handler exists, run it.
2042
2043 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2044 all bash/dash/mksh seem to agree on it. See cases in
2045 builtin-trap.test.sh.
2046
2047 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2048 of this awkward interface. But that's true in Python and not C!
2049
2050 Could use i & (n-1) == i & 255 because we have a power of 2.
2051 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2052 """
2053 node = self.trap_state.GetHook('EXIT') # type: command_t
2054 if node:
2055 # NOTE: Don't set option_i._running_trap, because that's for
2056 # RunPendingTraps() in the MAIN LOOP
2057 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2058 try:
2059 is_return, is_fatal = self.ExecuteAndCatch(node)
2060 except util.UserExit as e: # explicit exit
2061 mut_status.i = e.status
2062 return
2063 if is_return: # explicit 'return' in the trap handler!
2064 mut_status.i = self.LastStatus()
2065
2066 def _MaybeRunDebugTrap(self):
2067 # type: () -> None
2068 """If a DEBUG trap handler exists, run it."""
2069
2070 # Fix lastpipe / job control / DEBUG trap interaction
2071 if self.exec_opts._no_debug_trap():
2072 return
2073
2074 # Don't run recursively run traps, etc.
2075 if not self.mem.ShouldRunDebugTrap():
2076 return
2077
2078 node = self.trap_state.GetHook('DEBUG') # type: command_t
2079 if node:
2080 # NOTE: Don't set option_i._running_trap, because that's for
2081 # RunPendingTraps() in the MAIN LOOP
2082
2083 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2084 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2085 # for SetTokenForLine $LINENO
2086 with state.ctx_DebugTrap(self.mem):
2087 # Don't catch util.UserExit, etc.
2088 self._Execute(node)
2089
2090 def _MaybeRunErrTrap(self):
2091 # type: () -> None
2092 """If a ERR trap handler exists, run it."""
2093
2094 # Prevent infinite recursion
2095 if self.running_err_trap:
2096 return
2097
2098 node = self.trap_state.GetHook('ERR') # type: command_t
2099 if node:
2100 # NOTE: Don't set option_i._running_trap, because that's for
2101 # RunPendingTraps() in the MAIN LOOP
2102
2103 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2104 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2105 with ctx_ErrTrap(self):
2106 self._Execute(node)
2107
2108 def RunProc(self, proc, cmd_val):
2109 # type: (value.Proc, cmd_value.Argv) -> int
2110 """Run procs aka "shell functions".
2111
2112 For SimpleCommand and registered completion hooks.
2113 """
2114 sig = proc.sig
2115 if sig.tag() == proc_sig_e.Closed:
2116 # We're binding named params. User should use @rest. No 'shift'.
2117 proc_argv = [] # type: List[str]
2118 else:
2119 proc_argv = cmd_val.argv[1:]
2120
2121 # Hm this sets "$@". TODO: Set ARGV only
2122 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2123 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2124
2125 # Redirects still valid for functions.
2126 # Here doc causes a pipe and Process(SubProgramThunk).
2127 try:
2128 status = self._Execute(proc.body)
2129 except vm.IntControlFlow as e:
2130 if e.IsReturn():
2131 status = e.StatusCode()
2132 else:
2133 # break/continue used in the wrong place.
2134 e_die(
2135 'Unexpected %r (in proc call)' %
2136 lexer.TokenVal(e.token), e.token)
2137 except error.FatalRuntime as e:
2138 # Dump the stack before unwinding it
2139 self.dumper.MaybeRecord(self, e)
2140 raise
2141
2142 return status
2143
2144 def RunFuncForCompletion(self, proc, argv):
2145 # type: (value.Proc, List[str]) -> int
2146 """
2147 Args:
2148 argv: $1 $2 $3 ... not including $0
2149 """
2150 cmd_val = MakeBuiltinArgv(argv)
2151
2152 # TODO: Change this to run YSH procs and funcs too
2153 try:
2154 status = self.RunProc(proc, cmd_val)
2155 except error.FatalRuntime as e:
2156 self.errfmt.PrettyPrintError(e)
2157 status = e.ExitStatus()
2158 except vm.IntControlFlow as e:
2159 # shouldn't be able to exit the shell from a completion hook!
2160 # TODO: Avoid overwriting the prompt!
2161 self.errfmt.Print_('Attempted to exit from completion hook.',
2162 blame_loc=e.token)
2163
2164 status = 1
2165 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2166 return status
2167
2168
2169# vim: sw=4