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

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