OILS / osh / cmd_eval.py View on Github | oils.pub

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