def _GetProcessForNode(self, node): """ Assume we will run the node in another process. Return a process. """ if node.tag == command_e.SimpleCommand: words = braces.BraceExpandWords(node.words) argv = self.ev.EvalWordSequence(words) more_env = self.mem.GetExported() self._EvalEnv(node.more_env, more_env) thunk = self._GetThunkForSimpleCommand(argv, more_env) elif node.tag == command_e.ControlFlow: # Pipeline or subshells with control flow are invalid, e.g.: # - break | less # - continue | less # - ( return ) # NOTE: This could be done at parse time too. e_die('Invalid control flow %r in pipeline or subshell', node.token.val, token=node.token) else: thunk = process.SubProgramThunk(self, node) redirects = self._EvalRedirects(node) p = process.Process(thunk, fd_state=self.fd_state, redirects=redirects) return p
def EvalRhsWord(self, word): """word_t -> value_t. Used for RHS of assignment. There is no splitting. Args: ast.word_t Returns: runtime.value_t """ if word.tag == word_e.EmptyWord: return runtime.Str('') # Special case for a=(1 2). ArrayLiteralPart won't appear in words that # don't look like assignments. if (len(word.parts) == 1 and word.parts[0].tag == word_part_e.ArrayLiteralPart): array_words = word.parts[0].words words = braces.BraceExpandWords(array_words) strs = self._EvalWordSequence(words) #log('ARRAY LITERAL EVALUATED TO -> %s', strs) return runtime.StrArray(strs) # If RHS doens't look like a=( ... ), then it must be a string. return self.EvalWordToString(word)
def _GetProcessForNode(self, node): """ Assume we will run the node in another process. Return a process. """ if node.tag == command_e.SimpleCommand: words = braces.BraceExpandWords(node.words) argv = self.ev.EvalWordSequence(words) if argv is None: err = self.ev.Error() raise AssertionError("Error evaluating words: %s" % err) more_env = self.mem.GetExported() self._EvalEnv(node.more_env, more_env) thunk = self._GetThunkForSimpleCommand(argv, more_env) elif node.tag == command_e.ControlFlow: # TODO: Raise _FatalError # Pipeline or subshells with control flow are invalid, e.g.: # - break | less # - continue | less # - ( return ) # NOTE: This could be done at parse time too. raise AssertionError('Invalid control flow %s' % node) else: thunk = SubProgramThunk(self, node) redirects = self._EvalRedirects(node) p = Process(thunk, fd_state=self.fd_state, redirects=redirects) return p
def EvalWordToAny(self, word, glob_escape=False): """ Used for RHS of assignment Also used for default value? e.g. "${a:-"a" "b"}" and so forth. Returns: arg_value Or maybe just string? Whatever would go in ConstArg and GlobArg. But you don't need to distinguish it later. You could also have EvalWord and EvalGlobWord methods or EvalPatternWord """ # Special case for a=(1 2). ArrayLiteralPart won't appear in words that # don't look like assignments. if (len(word.parts) == 1 and word.parts[0].tag == word_part_e.ArrayLiteralPart): array_words = word.parts[0].words words = braces.BraceExpandWords(array_words) strs = self._EvalWordSequence(words) #log('ARRAY LITERAL EVALUATED TO -> %s', strs) return runtime.StrArray(strs) part_vals = self._EvalParts(word) #log('part_vals %s', part_vals) # Instead of splitting, do a trivial transformation to frag array. # Example: # foo="-$@-${a[@]}-" requires fragment, reframe, and simple join frag_arrays = [] for p in part_vals: if p.tag == part_value_e.StringPartValue: frag_arrays.append([runtime.fragment(p.s, False, False)]) elif p.tag == part_value_e.ArrayPartValue: frag_arrays.append( [runtime.fragment(s, False, False) for s in p.strs]) else: raise AssertionError frag_arrays = _Reframe(frag_arrays) #log('frag_arrays %s', frag_arrays) # Simple join args = [] for frag_array in frag_arrays: args.append(''.join(frag.s for frag in frag_array)) # Example: # a=(1 2) # b=$a # one word # c="${a[@]}" # two words if len(args) == 1: val = runtime.Str(args[0]) else: # NOTE: For bash compatibility, could have an option to join them here. # foo="-$@-${a[@]}-" -- join with IFS again, like "$*" ? # Or maybe do that in cmd_exec in assignment. val = runtime.StrArray(args) return val
def _Dispatch(self, node, fork_external): # If we call RunCommandSub in a recursive call to the executor, this will # be set true (if strict-errexit is false). But it only lasts for one # command. self.check_command_sub_status = False #argv0 = None # for error message check_errexit = False # for errexit if node.tag == command_e.SimpleCommand: check_errexit = True # Find span_id for a basic implementation of $LINENO, e.g. # PS4='+$SOURCE_NAME:$LINENO:' # NOTE: osh2oil uses node.more_env, but we don't need that. span_id = const.NO_INTEGER if node.words: first_word = node.words[0] span_id = word.LeftMostSpanForWord(first_word) self.mem.SetCurrentSpanId(span_id) # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already # redirected here, which screws up logging. For example, 'echo hi # >/dev/null 2>&1'. We want to evaluate argv and log it BEFORE applying # redirects. # Another problem: # - tracing can be called concurrently from multiple processes, leading # to overlap. Maybe have a mode that creates a file per process. # xtrace-proc # - line numbers for every command would be very nice. But then you have # to print the filename too. words = braces.BraceExpandWords(node.words) argv = self.word_ev.EvalWordSequence(words) # This comes before evaluating env, in case there are problems evaluating # it. We could trace the env separately? Also trace unevaluated code # with set-o verbose? self.tracer.OnSimpleCommand(argv) if node.more_env: self.mem.PushTemp() try: for env_pair in node.more_env: val = self.word_ev.EvalWordToString(env_pair.val) # Set each var so the next one can reference it. Example: # FOO=1 BAR=$FOO ls / self.mem.SetVar(ast.LhsName(env_pair.name), val, (var_flags_e.Exported,), scope_e.TempEnv) # NOTE: This might never return! In the case of fork_external=False. status = self._RunSimpleCommand(argv, fork_external, span_id) finally: if node.more_env: self.mem.PopTemp() elif node.tag == command_e.Sentence: # Don't check_errexit since this isn't a real node! if node.terminator.id == Id.Op_Semi: status = self._Execute(node.child) else: status = self._RunJobInBackground(node.child) elif node.tag == command_e.Pipeline: check_errexit = True if node.stderr_indices: raise NotImplementedError('|&') if node.negated: self._PushErrExit() try: status2 = self._RunPipeline(node) finally: self._PopErrExit() # errexit is disabled for !. check_errexit = False status = 1 if status2 == 0 else 0 else: status = self._RunPipeline(node) elif node.tag == command_e.Subshell: check_errexit = True # This makes sure we don't waste a process if we'd launch one anyway. p = self._MakeProcess(node.child) status = p.Run(self.waiter) elif node.tag == command_e.DBracket: check_errexit = True result = self.bool_ev.Eval(node.expr) status = 0 if result else 1 elif node.tag == command_e.DParen: check_errexit = True i = self.arith_ev.Eval(node.child) status = 0 if i != 0 else 1 elif node.tag == command_e.Assignment: flags = word_compile.ParseAssignFlags(node.flags) if node.keyword == Id.Assign_Local: lookup_mode = scope_e.LocalOnly # typeset and declare are synonyms? I see typeset -a a=() the most. elif node.keyword in (Id.Assign_Declare, Id.Assign_Typeset): # declare is like local, except it can also be used outside functions? if var_flags_e.Global in flags: lookup_mode = scope_e.GlobalOnly else: lookup_mode = scope_e.LocalOnly elif node.keyword == Id.Assign_Readonly: lookup_mode = scope_e.Dynamic flags.append(var_flags_e.ReadOnly) elif node.keyword == Id.Assign_None: # mutate existing local or global lookup_mode = scope_e.Dynamic else: raise AssertionError(node.keyword) for pair in node.pairs: if pair.op == assign_op_e.PlusEqual: assert pair.rhs, pair.rhs # I don't think a+= is valid? val = self.word_ev.EvalRhsWord(pair.rhs) old_val, lval = expr_eval.EvalLhsAndLookup(pair.lhs, self.arith_ev, self.mem, self.exec_opts) sig = (old_val.tag, val.tag) if sig == (value_e.Undef, value_e.Str): pass # val is RHS elif sig == (value_e.Undef, value_e.StrArray): pass # val is RHS elif sig == (value_e.Str, value_e.Str): val = runtime.Str(old_val.s + val.s) elif sig == (value_e.Str, value_e.StrArray): e_die("Can't append array to string") elif sig == (value_e.StrArray, value_e.Str): e_die("Can't append string to array") elif sig == (value_e.StrArray, value_e.StrArray): val = runtime.StrArray(old_val.strs + val.strs) else: # plain assignment spid = pair.spids[0] # Source location for tracing lval = self._EvalLhs(pair.lhs, spid, lookup_mode) # RHS can be a string or array. if pair.rhs: val = self.word_ev.EvalRhsWord(pair.rhs) assert isinstance(val, runtime.value), val else: # e.g. 'readonly x' or 'local x' val = None # NOTE: In bash and mksh, declare -a myarray makes an empty cell with # Undef value, but the 'array' attribute. #log('setting %s to %s with flags %s', lval, val, flags) self.mem.SetVar(lval, val, flags, lookup_mode, strict_array=self.exec_opts.strict_array) # Assignment always appears to have a spid. if node.spids: current_spid = node.spids[0] else: current_spid = const.NO_INTEGER self.mem.SetCurrentSpanId(current_spid) self.tracer.OnAssignment(lval, pair.op, val, flags, lookup_mode) # PATCH to be compatible with existing shells: If the assignment had a # command sub like: # # s=$(echo one; false) # # then its status will be in mem.last_status, and we can check it here. # If there was NOT a command sub in the assignment, then we don't want to # check it. if node.keyword == Id.Assign_None: # mutate existing local or global # Only do this if there was a command sub? How? Look at node? # Set a flag in mem? self.mem.last_status or if self.check_command_sub_status: self._CheckStatus(self.mem.last_status, node) # A global assignment shouldn't clear $?. status = self.mem.last_status else: status = 0 else: # To be compatible with existing shells, local assignments DO clear # $?. Even in strict mode, we don't need to bother setting # check_errexit = True, because we would have already checked the # command sub in RunCommandSub. status = 0 # TODO: maybe we should have a "sane-status" that respects this: # false; echo $?; local f=x; echo $? elif node.tag == command_e.ControlFlow: if node.arg_word: # Evaluate the argument val = self.word_ev.EvalWordToString(node.arg_word) assert val.tag == value_e.Str arg = int(val.s) # They all take integers else: arg = 0 # return 0, exit 0, break 0 levels, etc. # NOTE: We don't do anything about a top-level 'return' here. Unlike in # bash, that is OK. If you can return from a sourced script, it makes # sense to return from a main script. ok = True tok = node.token if (tok.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and self.loop_level == 0): ok = False msg = 'Invalid control flow at top level' if ok: raise _ControlFlow(tok, arg) if self.exec_opts.strict_control_flow: e_die(msg, token=tok) else: # Only print warnings, never fatal. # Bash oddly only exits 1 for 'return', but no other shell does. ui.PrintFilenameAndLine(tok.span_id, self.arena) util.warn(msg) status = 0 # The only difference between these two is that CommandList has no # redirects. We already took care of that above. elif node.tag in (command_e.CommandList, command_e.BraceGroup): status = self._ExecuteList(node.children) check_errexit = False elif node.tag == command_e.AndOr: # NOTE: && and || have EQUAL precedence in command mode. See case #13 # in dbracket.test.sh. left = node.children[0] # Suppress failure for every child except the last one. self._PushErrExit() try: status = self._Execute(left) finally: self._PopErrExit() i = 1 n = len(node.children) while i < n: #log('i %d status %d', i, status) child = node.children[i] op_id = node.ops[i-1] #log('child %s op_id %s', child, op_id) if op_id == Id.Op_DPipe and status == 0: i += 1 continue # short circuit elif op_id == Id.Op_DAmp and status != 0: i += 1 continue # short circuit if i == n - 1: # errexit handled differently for last child status = self._Execute(child) check_errexit = True else: self._PushErrExit() try: status = self._Execute(child) finally: self._PopErrExit() i += 1 elif node.tag == command_e.WhileUntil: if node.keyword.id == Id.KW_While: _DonePredicate = lambda status: status != 0 else: _DonePredicate = lambda status: status == 0 status = 0 self.loop_level += 1 try: while True: self._PushErrExit() try: cond_status = self._ExecuteList(node.cond) finally: self._PopErrExit() done = cond_status != 0 if _DonePredicate(cond_status): break try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 continue else: # return needs to pop up more raise finally: self.loop_level -= 1 elif node.tag == command_e.ForEach: iter_name = node.iter_name if node.do_arg_iter: iter_list = self.mem.GetArgv() else: words = braces.BraceExpandWords(node.iter_words) iter_list = self.word_ev.EvalWordSequence(words) # We need word splitting and so forth # NOTE: This expands globs too. TODO: We should pass in a Globber() # object. status = 0 # in case we don't loop self.loop_level += 1 try: for x in iter_list: #log('> ForEach setting %r', x) state.SetLocalString(self.mem, iter_name, x) #log('<') try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 else: # return needs to pop up more raise finally: self.loop_level -= 1 elif node.tag == command_e.ForExpr: status = 0 init, cond, body, update = node.init, node.cond, node.body, node.update if init: self.arith_ev.Eval(init) self.loop_level += 1 try: while True: if cond: b = self.arith_ev.Eval(cond) if not b: break try: status = self._Execute(body) except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 else: # return needs to pop up more raise if update: self.arith_ev.Eval(update) finally: self.loop_level -= 1 elif node.tag == command_e.DoGroup: status = self._ExecuteList(node.children) check_errexit = False # not real statements elif node.tag == command_e.FuncDef: # NOTE: Would it make sense to evaluate the redirects BEFORE entering? # It will save time on function calls. self.funcs[node.name] = node status = 0 elif node.tag == command_e.If: done = False for arm in node.arms: self._PushErrExit() try: status = self._ExecuteList(arm.cond) finally: self._PopErrExit() if status == 0: status = self._ExecuteList(arm.action) done = True break # TODO: The compiler should flatten this if not done and node.else_action is not None: status = self._ExecuteList(node.else_action) elif node.tag == command_e.NoOp: status = 0 # make it true elif node.tag == command_e.Case: val = self.word_ev.EvalWordToString(node.to_match) to_match = val.s status = 0 # If there are no arms, it should be zero? done = False for arm in node.arms: for pat_word in arm.pat_list: # NOTE: Is it OK that we're evaluating these as we go? # TODO: case "$@") shouldn't succeed? That's a type error? # That requires strict-array? pat_val = self.word_ev.EvalWordToString(pat_word, do_fnmatch=True) #log('Matching word %r against pattern %r', to_match, pat_val.s) if libc.fnmatch(pat_val.s, to_match): status = self._ExecuteList(arm.action) done = True # TODO: Parse ;;& and for fallthrough and such? break # Only execute action ONCE if done: break elif node.tag == command_e.TimeBlock: # TODO: # - When do we need RUSAGE_CHILDREN? # - Respect TIMEFORMAT environment variable. # "If this variable is not set, Bash acts as if it had the value" # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS' # "A trailing newline is added when the format string is displayed." start_t = time.time() # calls gettimeofday() under the hood start_u = resource.getrusage(resource.RUSAGE_SELF) status = self._Execute(node.pipeline) end_t = time.time() end_u = resource.getrusage(resource.RUSAGE_SELF) real = end_t - start_t user = end_u.ru_utime - start_u.ru_utime sys_ = end_u.ru_stime - start_u.ru_stime libc.print_time(real, user, sys_) else: raise NotImplementedError(node.__class__.__name__) return status, check_errexit
def _Dispatch(self, node, fork_external): argv0 = None # for error message check_errexit = True # for errexit if node.tag == command_e.SimpleCommand: # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already # redirected here, which screws up loggnig. For example, 'echo hi # >/dev/null 2>&1'. We want to evaluate argv and log it BEFORE applying # redirects. # Another problem: # - tracing can be called concurrently from multiple processes, leading # to overlap. Maybe have a mode that creates a file per process. # xtrace-proc # - line numbers for every command would be very nice. But then you have # to print the filename too. words = braces.BraceExpandWords(node.words) argv = self.ev.EvalWordSequence(words) if argv: argv0 = argv[0] environ = self.mem.GetExported() self._EvalEnv(node.more_env, environ) if self.exec_opts.xtrace: log('+ %s', argv) #print('+ %s' % argv, file=sys.stderr) #print('+ %s' % argv, file=self.XFILE) #os.write(2, '+ %s\n' % argv) status = self._RunSimpleCommand(argv, environ, fork_external) if self.exec_opts.xtrace: #log('+ %s -> %d', argv, status) pass elif node.tag == command_e.Sentence: if node.terminator.id == Id.Op_Semi: # Don't check_errexit since this isn't a real node! check_errexit = False status = self._Execute(node.child) else: status = self._RunJobInBackground(node.child) elif node.tag == command_e.Pipeline: if node.stderr_indices: raise NotImplementedError('|&') if node.negated: self._PushErrExit() try: status2 = self._RunPipeline(node) finally: self._PopErrExit() # errexit is disabled for !. check_errexit = False status = 1 if status2 == 0 else 0 else: status = self._RunPipeline(node) elif node.tag == command_e.Subshell: # This makes sure we don't waste a process if we'd launch one anyway. p = self._MakeProcess(node.child) status = p.Run(self.waiter) elif node.tag == command_e.DBracket: result = self.bool_ev.Eval(node.expr) status = 0 if result else 1 elif node.tag == command_e.DParen: i = self.arith_ev.Eval(node.child) status = 0 if i != 0 else 1 elif node.tag == command_e.Assignment: pairs = [] if node.keyword == Id.Assign_Local: lookup_mode = scope.LocalOnly flags = () elif node.keyword == Id.Assign_Declare: # declare is like local, except it can also be used outside functions? lookup_mode = scope.LocalOnly # TODO: Respect flags. -r and -x matter, but -a and -A might be # implicit in the RHS? flags = () elif node.keyword == Id.Assign_Readonly: lookup_mode = scope.Dynamic flags = (var_flags.ReadOnly,) elif node.keyword == Id.Assign_None: # mutate existing local or global lookup_mode = scope.Dynamic flags = () else: # TODO: typeset, declare, etc. Those are dynamic though. raise NotImplementedError(node.keyword) for pair in node.pairs: if pair.rhs: # RHS can be a string or array. val = self.ev.EvalWordToAny(pair.rhs) assert isinstance(val, runtime.value), val else: # 'local x' is equivalent to local x="" val = runtime.Str('') if pair.op == assign_op.PlusEqual: old_val, lval = expr_eval.EvalLhs(pair.lhs, self.arith_ev, self.mem, self.exec_opts) sig = (old_val.tag, val.tag) if sig == (value_e.Str, value_e.Str): val = runtime.Str(old_val.s + val.s) elif sig == (value_e.Str, value_e.StrArray): e_die("Can't append array to string") elif sig == (value_e.StrArray, value_e.Str): e_die("Can't append string to array") elif sig == (value_e.StrArray, value_e.StrArray): val = runtime.StrArray(old_val.strs + val.strs) else: lval = self._EvalLhs(pair.lhs) #log('ASSIGNING %s -> %s', lval, val) self.mem.SetVar(lval, val, flags, lookup_mode) # TODO: This should be eval of RHS, unlike bash! status = 0 elif node.tag == command_e.ControlFlow: if node.arg_word: # Evaluate the argument val = self.ev.EvalWordToString(node.arg_word) assert val.tag == value_e.Str arg = int(val.s) # They all take integers else: arg = 0 # return 0, break 0 levels, etc. # NOTE: always raises so we don't set status. raise _ControlFlow(node.token, arg) # The only difference between these two is that CommandList has no # redirects. We already took care of that above. elif node.tag in (command_e.CommandList, command_e.BraceGroup): status = self._ExecuteList(node.children) elif node.tag == command_e.AndOr: #print(node.children) left, right = node.children # This is everything except the last one. self._PushErrExit() try: status = self._Execute(left) finally: self._PopErrExit() if node.op_id == Id.Op_DPipe: if status != 0: status = self._Execute(right) elif node.op_id == Id.Op_DAmp: if status == 0: status = self._Execute(right) else: raise AssertionError elif node.tag in (command_e.While, command_e.Until): # TODO: Compile this out? if node.tag == command_e.While: _DonePredicate = lambda status: status != 0 else: _DonePredicate = lambda status: status == 0 status = 0 while True: self._PushErrExit() try: cond_status = self._ExecuteList(node.cond) finally: self._PopErrExit() done = cond_status != 0 if _DonePredicate(cond_status): break try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 continue else: # return needs to pop up more raise elif node.tag == command_e.ForEach: iter_name = node.iter_name if node.do_arg_iter: iter_list = self.mem.GetArgv() else: words = braces.BraceExpandWords(node.iter_words) iter_list = self.ev.EvalWordSequence(words) # We need word splitting and so forth # NOTE: This expands globs too. TODO: We should pass in a Globber() # object. status = 0 # in case we don't loop for x in iter_list: #log('> ForEach setting %r', x) state.SetLocalString(self.mem, iter_name, x) #log('<') try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 continue else: # return needs to pop up more raise elif node.tag == command_e.ForExpr: raise NotImplementedError(node.tag) elif node.tag == command_e.DoGroup: status = self._ExecuteList(node.children) elif node.tag == command_e.FuncDef: # NOTE: Would it make sense to evaluate the redirects BEFORE entering? # It will save time on function calls. self.funcs[node.name] = node status = 0 elif node.tag == command_e.If: done = False for arm in node.arms: self._PushErrExit() try: status = self._ExecuteList(arm.cond) finally: self._PopErrExit() if status == 0: status = self._ExecuteList(arm.action) done = True break # TODO: The compiler should flatten this if not done and node.else_action is not None: status = self._ExecuteList(node.else_action) elif node.tag == command_e.NoOp: status = 0 # make it true elif node.tag == command_e.Case: val = self.ev.EvalWordToString(node.to_match) to_match = val.s status = 0 # If there are no arms, it should be zero? done = False for arm in node.arms: for pat_word in arm.pat_list: # NOTE: Is it OK that we're evaluating these as we go? pat_val = self.ev.EvalWordToString(pat_word, do_fnmatch=True) #log('Matching word %r against pattern %r', to_match, pat_val.s) if libc.fnmatch(pat_val.s, to_match): status = self._ExecuteList(arm.action) done = True # TODO: Parse ;;& and for fallthrough and such? if done: break elif node.tag == command_e.TimeBlock: # TODO: # - When do we need RUSAGE_CHILDREN? # - Respect TIMEFORMAT environment variable. # "If this variable is not set, Bash acts as if it had the value" # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS' # "A trailing newline is added when the format string is displayed." start_t = time.time() # calls gettimeofday() under the hood start_u = resource.getrusage(resource.RUSAGE_SELF) status = self._Execute(node.pipeline) end_t = time.time() end_u = resource.getrusage(resource.RUSAGE_SELF) real = end_t - start_t user = end_u.ru_utime - start_u.ru_utime sys_ = end_u.ru_stime - start_u.ru_stime print('real\t%.3f' % real, file=sys.stderr) print('user\t%.3f' % user, file=sys.stderr) print('sys\t%.3f' % sys_, file=sys.stderr) else: raise AssertionError(node.tag) return status, check_errexit
def _Execute(self, node): """ Args: node: of type AstNode """ redirects = self._EvalRedirects(node) # TODO: Only eval argv[0] once. It can have side effects! if node.tag == command_e.SimpleCommand: words = braces.BraceExpandWords(node.words) argv = self.ev.EvalWordSequence(words) more_env = self.mem.GetExported() self._EvalEnv(node.more_env, more_env) thunk = self._GetThunkForSimpleCommand(argv, more_env) # Don't waste a process if we'd launch one anyway. if thunk.IsExternal(): p = process.Process(thunk, fd_state=self.fd_state, redirects=redirects) status = p.Run() else: # Internal #log('ARGV %s', argv) # NOTE: _EvalRedirects turns LST nodes into core/process.py nodes. And # then we use polymorphism here. Does it make sense to use functional # style based on the RedirType? Might be easier to read. self.fd_state.PushFrame() for r in redirects: r.ApplyInParent(self.fd_state) status = thunk.RunInParent() restore_fd_state = thunk.ShouldRestoreFdState() # Special case for exec 1>&2 (with no args): we permanently change the # fd state. BUT we don't want to restore later. # TODO: Instead of this, maybe r.ApplyPermaent(self.fd_state)? if restore_fd_state: self.fd_state.PopAndRestore() else: self.fd_state.PopAndForget() elif node.tag == command_e.Sentence: # TODO: Compile this away. status = self._Execute(node.command) elif node.tag == command_e.Pipeline: status = self._RunPipeline(node) elif node.tag == command_e.Subshell: # This makes sure we don't waste a process if we'd launch one anyway. p = self._GetProcessForNode(node.children[0]) status = p.Run() elif node.tag == command_e.DBracket: bool_ev = expr_eval.BoolEvaluator(self.mem, self.ev) ok = bool_ev.Eval(node.expr) if ok: status = 0 if bool_ev.Result() else 1 else: e_die('Error evaluating boolean: %s' % bool_ev.Error()) elif node.tag == command_e.DParen: arith_ev = expr_eval.ArithEvaluator(self.mem, self.ev) ok = arith_ev.Eval(node.child) if ok: i = arith_ev.Result() # Negate the value: non-zero in arithmetic is true, which is zero in # shell land status = 0 if i != 0 else 1 else: e_die('Error evaluating (( )): %s' % arith_ev.Error()) elif node.tag == command_e.Assignment: pairs = [] for pair in node.pairs: if pair.rhs: # RHS can be a string or array. val = self.ev.EvalWordToAny(pair.rhs) assert isinstance(val, runtime.value), val else: # 'local x' is equivalent to local x="" val = runtime.Str('') pairs.append((pair.lhs, val)) if node.keyword == Id.Assign_Local: self.mem.SetLocals(pairs) else: # NOTE: could be readonly/export/etc. self.mem.SetLocalsOrGlobals(pairs) # TODO: This should be eval of RHS, unlike bash! status = 0 elif node.tag == command_e.ControlFlow: if node.arg_word: # Evaluate the argument _, val = self.ev.EvalWordToString(node.arg_word) assert val.tag == value_e.Str arg = int(val.s) # They all take integers else: arg = 0 # return 0, break 0 levels, etc. raise _ControlFlow(node.token, arg) # The only difference between these two is that CommandList has no # redirects. We already took care of that above. elif node.tag in (command_e.CommandList, command_e.BraceGroup): self.fd_state.PushFrame() for r in redirects: r.ApplyInParent(self.fd_state) status = 0 # for empty list for child in node.children: status = self._Execute(child) # last status wins self.fd_state.PopAndRestore() elif node.tag == command_e.AndOr: #print(node.children) left, right = node.children status = self._Execute(left) if node.op_id == Id.Op_DPipe: if status != 0: status = self._Execute(right) elif node.op_id == Id.Op_DAmp: if status == 0: status = self._Execute(right) else: raise AssertionError elif node.tag in (command_e.While, command_e.Until): # TODO: Compile this out? if node.tag == command_e.While: _DonePredicate = lambda status: status != 0 else: _DonePredicate = lambda status: status == 0 while True: status = self._Execute(node.cond) done = status != 0 if _DonePredicate(status): break try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 continue else: # return needs to pop up more raise elif node.tag == command_e.ForEach: iter_name = node.iter_name if node.do_arg_iter: iter_list = self.mem.GetArgv() else: words = braces.BraceExpandWords(node.iter_words) iter_list = self.ev.EvalWordSequence(words) # We need word splitting and so forth # NOTE: This expands globs too. TODO: We should pass in a Globber() # object. status = 0 # in case we don't loop for x in iter_list: #log('> ForEach setting %r', x) self.mem.SetLocal(iter_name, runtime.Str(x)) #log('<') try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 continue else: # return needs to pop up more raise elif node.tag == command_e.ForExpr: raise NotImplementedError(node.tag) elif node.tag == command_e.DoGroup: # Delegate to command list # TODO: This should be compiled out! status = self._Execute(node.child) elif node.tag == command_e.FuncDef: self.funcs[node.name] = node status = 0 elif node.tag == command_e.If: done = False for arm in node.arms: status = self._Execute(arm.cond) if status == 0: status = self._Execute(arm.action) done = True break # TODO: The compiler should flatten this if not done and node.else_action is not None: status = self._Execute(node.else_action) elif node.tag == command_e.NoOp: status = 0 # make it true elif node.tag == command_e.Case: ok, val = self.ev.EvalWordToString(node.to_match) assert ok to_match = val.s status = 0 # If there are no arms, it should be zero? done = False for arm in node.arms: for pat_word in arm.pat_list: # NOTE: Is it OK that we're evaluating these as we go? ok, pat_val = self.ev.EvalWordToString(pat_word, do_fnmatch=True) assert ok #log('Matching word %r against pattern %r', to_match, pat_val.s) if libc.fnmatch(pat_val.s, to_match): status = self._Execute(arm.action) done = True # TODO: Parse ;;& and for fallthrough and such? if done: break elif node.tag == command_e.TimeBlock: # TODO: # - When do we need RUSAGE_CHILDREN? # - Respect TIMEFORMAT environment variable. # "If this variable is not set, Bash acts as if it had the value" # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS' # "A trailing newline is added when the format string is displayed." start_t = time.time() # calls gettimeofday() under the hood start_u = resource.getrusage(resource.RUSAGE_SELF) status = self._Execute(node.pipeline) end_t = time.time() end_u = resource.getrusage(resource.RUSAGE_SELF) real = end_t - start_t user = end_u.ru_utime - start_u.ru_utime sys_ = end_u.ru_stime - start_u.ru_stime print('real\t%.3f' % real, file=sys.stderr) print('user\t%.3f' % user, file=sys.stderr) print('sys\t%.3f' % sys_, file=sys.stderr) else: raise AssertionError(node.tag) if self.exec_opts.errexit and status != 0: if node.tag == command_e.SimpleCommand: # TODO: Add context e_die('%r command exited with status %d (%s)', argv[0], status, node.words[0]) else: e_die('%r command exited with status %d', node.__class__.__name__, status) # TODO: Is this the right place to put it? Does it need a stack for # function calls? self.mem.last_status = status return status
def _Execute(self, node): """ Args: node: of type AstNode """ redirects = self._EvalRedirects(node) # TODO: Only eval argv[0] once. It can have side effects! if node.tag == command_e.SimpleCommand: words = braces.BraceExpandWords(node.words) argv = self.ev.EvalWordSequence(words) if argv is None: self.error_stack.extend(self.ev.Error()) raise _FatalError() more_env = self.mem.GetExported() self._EvalEnv(node.more_env, more_env) thunk = self._GetThunkForSimpleCommand(argv, more_env) # Don't waste a process if we'd launch one anyway. if thunk.IsExternal(): p = Process(thunk, fd_state=self.fd_state, redirects=redirects) status = p.Run() else: # Internal #log('ARGV %s', argv) # NOTE: _EvalRedirects turns LST nodes into core/process.py nodes. And # then we use polymorphism here. Does it make sense to use functional # style based on the RedirType? Might be easier to read. self.fd_state.PushFrame() for r in redirects: r.ApplyInParent(self.fd_state) status = thunk.RunInParent() restore_fd_state = thunk.ShouldRestoreFdState() # Special case for exec 1>&2 (with no args): we permanently change the # fd state. BUT we don't want to restore later. # TODO: Instead of this, maybe r.ApplyPermaent(self.fd_state)? if restore_fd_state: self.fd_state.PopAndRestore() else: self.fd_state.PopAndForget() elif node.tag == command_e.Sentence: # TODO: Compile this away. status = self._Execute(node.command) elif node.tag == command_e.Pipeline: status = self._RunPipeline(node) elif node.tag == command_e.Subshell: # This makes sure we don't waste a process if we'd launch one anyway. p = self._GetProcessForNode(node.children[0]) status = p.Run() elif node.tag == command_e.DBracket: bool_ev = expr_eval.BoolEvaluator(self.mem, self.ev) ok = bool_ev.Eval(node.expr) if ok: status = 0 if bool_ev.Result() else 1 else: raise AssertionError('Error evaluating boolean: %s' % bool_ev.Error()) elif node.tag == command_e.DParen: arith_ev = expr_eval.ArithEvaluator(self.mem, self.ev) ok = arith_ev.Eval(node.child) if ok: i = arith_ev.Result() # Negate the value: non-zero in arithmetic is true, which is zero in # shell land status = 0 if i != 0 else 1 else: raise AssertionError('Error evaluating (( )): %s' % arith_ev.Error()) elif node.tag == command_e.Assignment: pairs = [] for pair in node.pairs: # RHS can be a string or array. ok, val = self.ev.EvalWordToAny(pair.rhs) assert isinstance(val, runtime.value), val #log('RHS %s -> %s', pair.rhs, val) if not ok: self.error_stack.extend(self.ev.Error()) raise _FatalError() pairs.append((pair.lhs, val)) if node.keyword == Id.Assign_Local: self.mem.SetLocals(pairs) else: # could be readonly/export/etc. self.mem.SetGlobals(pairs) # TODO: This should be eval of RHS, unlike bash! status = 0 elif node.tag == command_e.ControlFlow: if node.arg_word: # Evaluate the argument ok, val = self.ev.EvalWordToString(node.arg_word) if not ok: self.error_stack.extend(self.ev.Error()) raise _FatalError() assert val.tag == value_e.Str arg = int(val.s) # They all take integers else: arg = 0 # return 0, break 0 levels, etc. raise _ControlFlow(node.token, arg) # The only difference between these two is that CommandList has no # redirects. We already took care of that above. elif node.tag in (command_e.CommandList, command_e.BraceGroup): status = 0 # for empty list for child in node.children: status = self._Execute(child) # last status wins elif node.tag == command_e.AndOr: #print(node.children) left, right = node.children status = self._Execute(left) if node.op_id == Id.Op_DPipe: if status != 0: status = self._Execute(right) elif node.op_id == Id.Op_DAmp: if status == 0: status = self._Execute(right) else: raise AssertionError elif node.tag in (command_e.While, command_e.Until): # TODO: Compile this out? if node.tag == command_e.While: _DonePredicate = lambda status: status != 0 else: _DonePredicate = lambda status: status == 0 while True: status = self._Execute(node.cond) done = status != 0 if _DonePredicate(status): break try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 continue else: # return needs to pop up more raise elif node.tag == command_e.ForEach: iter_name = node.iter_name if node.do_arg_iter: iter_list = self.mem.GetArgv() else: words = braces.BraceExpandWords(node.iter_words) iter_list = self.ev.EvalWordSequence(words) # We need word splitting and so forth # NOTE: This expands globs too. TODO: We should pass in a Globber() # object. status = 0 # in case we don't loop for x in iter_list: #log('> ForEach setting %r', x) self.mem.SetLocal(iter_name, runtime.Str(x)) #log('<') try: status = self._Execute(node.body) # last one wins except _ControlFlow as e: if e.IsBreak(): status = 0 break elif e.IsContinue(): status = 0 continue else: # return needs to pop up more raise elif node.tag == command_e.ForExpr: raise NotImplementedError(node.tag) elif node.tag == command_e.DoGroup: # Delegate to command list # TODO: This should be compiled out! status = self._Execute(node.child) elif node.tag == command_e.FuncDef: self.funcs[node.name] = node status = 0 elif node.tag == command_e.If: done = False for arm in node.arms: status = self._Execute(arm.cond) if status == 0: status = self._Execute(arm.action) done = True break # TODO: The compiler should flatten this if not done and node.else_action is not None: status = self._Execute(node.else_action) elif node.tag == command_e.NoOp: status = 0 # make it true elif node.tag == command_e.Case: ok, val = self.ev.EvalWordToString(node.to_match) assert ok to_match = val.s status = 0 # If there are no arms, it should be zero? done = False for arm in node.arms: for pat_word in arm.pat_list: # NOTE: Is it OK that we're evaluating these as we go? ok, pat_val = self.ev.EvalWordToString(pat_word, do_fnmatch=True) assert ok #log('Matching word %r against pattern %r', to_match, pat_val.s) if libc.fnmatch(pat_val.s, to_match): status = self._Execute(arm.action) done = True # TODO: Parse ;;& and for fallthrough and such? if done: break else: raise AssertionError(node.tag) if self.exec_opts.errexit: if status != 0: # TODO: token should be set to what? Is it node.begin_word and # node.end_word? token = None tb = self.mem.GetTraceback(token) self._SetException(tb, "Command %s exited with code %d" % ('TODO', status)) # TODO: raise _ControlFlow? Except? # Dummy? # TODO: Is this the right place to put it? Does it need a stack for # function calls? self.mem.last_status = status return status