def __init__(self, mem, fd_state, status_lines, funcs, readline, completion, comp_lookup, exec_opts, arena): """ Args: mem: Mem instance for storing variables fd_state: FdState() for managing descriptors status_lines: shared with completion. TODO: Move this to the end. funcs: registry of functions (these names are completed) completion: completion module, if available comp_lookup: completion pattern/action exec_opts: ExecOpts arena: for printing error locations """ self.mem = mem self.fd_state = fd_state self.status_lines = status_lines # function space is different than var space. Not hierarchical. self.funcs = funcs self.completion = completion # Completion hooks, set by 'complete' builtin. self.comp_lookup = comp_lookup # This is for shopt and set -o. They are initialized by flags. self.exec_opts = exec_opts self.exec_opts.readline = readline self.arena = arena self.splitter = legacy.SplitContext(self.mem) self.word_ev = word_eval.NormalWordEvaluator( mem, exec_opts, self.splitter, self) self.arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, self.word_ev) self.bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, self.word_ev) self.traps = {} # signal/hook name -> callable self.nodes_to_run = [] # list of nodes, appended to by signal handlers self.dir_stack = state.DirStack() # TODO: Pass these in from main() self.aliases = {} # alias name -> string self.targets = [] # make syntax enters stuff here -- Target() # metaprogramming or regular target syntax # Whether argv[0] is make determines if it is executed self.waiter = process.Waiter() # sleep 5 & puts a (PID, job#) entry here. And then "jobs" displays it. self.job_state = process.JobState() self.loop_level = 0 # for detecting bad top-level break/continue self.tracer = Tracer(exec_opts, mem, self.word_ev) self.check_command_sub_status = False # a hack
def __init__(self, mem, fd_state, funcs, comp_lookup, exec_opts, parse_ctx, devtools): """ Args: mem: Mem instance for storing variables fd_state: FdState() for managing descriptors funcs: dict of functions comp_lookup: registry of completion hooks exec_opts: ExecOpts parse_ctx: for instantiating parsers """ self.mem = mem self.fd_state = fd_state self.funcs = funcs # Completion hooks, set by 'complete' builtin. self.comp_lookup = comp_lookup # This is for shopt and set -o. They are initialized by flags. self.exec_opts = exec_opts self.parse_ctx = parse_ctx self.arena = parse_ctx.arena self.aliases = parse_ctx.aliases # alias name -> string self.dumper = devtools.dumper self.debug_f = devtools.debug_f # Used by ShellFuncAction too self.splitter = legacy.SplitContext(self.mem) self.word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, self.splitter, self.arena, self) self.arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, self.word_ev, self.arena) self.bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, self.word_ev, self.arena) self.traps = {} # signal/hook name -> callable self.nodes_to_run = [] # list of nodes, appended to by signal handlers self.dir_stack = state.DirStack() self.targets = [] # make syntax enters stuff here -- Target() # metaprogramming or regular target syntax # Whether argv[0] is make determines if it is executed self.waiter = process.Waiter() # sleep 5 & puts a (PID, job#) entry here. And then "jobs" displays it. self.job_state = process.JobState() self.tracer = Tracer(parse_ctx, exec_opts, mem, self.word_ev, devtools.trace_f) self.loop_level = 0 # for detecting bad top-level break/continue self.check_command_sub_status = False # a hack
def __init__(self, mem, status_lines, funcs, completion, comp_lookup, exec_opts, arena): """ Args: mem: Mem instance for storing variables status_lines: shared with completion. TODO: Move this to the end. funcs: registry of functions (these names are completed) completion: completion module, if available comp_lookup: completion pattern/action exec_opts: ExecOpts arena: for printing error locations """ self.mem = mem self.status_lines = status_lines # function space is different than var space. Not hierarchical. self.funcs = funcs self.completion = completion # Completion hooks, set by 'complete' builtin. self.comp_lookup = comp_lookup # This is for shopt and set -o. They are initialized by flags. self.exec_opts = exec_opts self.arena = arena self.ev = word_eval.NormalWordEvaluator(mem, exec_opts, self) self.arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, self.ev) self.bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, self.ev) self.traps = {} self.fd_state = process.FdState() self.dir_stack = [] # TODO: Pass these in from main() self.aliases = {} # alias name -> string self.targets = [] # make syntax enters stuff here -- Target() # metaprogramming or regular target syntax # Whether argv[0] is make determines if it is executed self.waiter = process.Waiter() # sleep 5 & puts a (PID, job#) entry here. And then "jobs" displays it. self.job_state = process.JobState()
def Test(argv, need_right_bracket): """The test/[ builtin. The only difference between test and [ is that [ needs a matching ]. """ if need_right_bracket: if not argv or argv[-1] != ']': util.error('[: missing closing ]') return 2 del argv[-1] w_parser = _StringWordEmitter(argv) b_parser = bool_parse.BoolParser(w_parser) # There is a fundamental ambiguity due to poor language design, in cases like: # [ -z ] # [ -z -a ] # [ -z -a ] ] # # See posixtest() in bash's test.c: # "This is an implementation of a Posix.2 proposal by David Korn." # It dispatches on expressions of length 0, 1, 2, 3, 4, and N args. We do # the same here. # # Another ambiguity: # -a is both a unary prefix operator and an infix operator. How to fix this # ambiguity? bool_node = None n = len(argv) try: if n == 0: return 1 # [ ] is False elif n == 1: bool_node = _StringWordTest(argv[0]) elif n == 2: bool_node = _TwoArgs(argv) elif n == 3: bool_node = _ThreeArgs(argv) if n == 4: a0 = argv[0] if a0 == '!': child = _ThreeArgs(argv[1:]) bool_node = ast.LogicalNot(child) elif a0 == '(' and argv[3] == ')': bool_node = _TwoArgs(argv[1:3]) else: pass # fallthrough if bool_node is None: bool_node = b_parser.ParseForBuiltin() except util.ParseError as e: # TODO: There should be a nice method to print argv. And some way to point # to the error. log("Error parsing %s", argv) util.error("test: %s", e.UserErrorString()) return 2 # parse error is 2 # mem: Don't need it for BASH_REMATCH? Or I guess you could support it # exec_opts: don't need it, but might need it later mem = None # Not necessary word_ev = _WordEvaluator() arena = None # We want [ a -eq a ] to always be an error, unlike [[ a -eq a ]]. This is a # weird case of [[ being less strict. class _DummyExecOpts(): def __init__(self): self.strict_arith = True exec_opts = _DummyExecOpts() bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, word_ev, arena) try: b = bool_ev.Eval(bool_node) except util.FatalRuntimeError as e: # e.g. [ -t xxx ] # TODO: Printing the location would be nice. util.error('test: %s', e.UserErrorString()) return 2 # because this is more like a parser error. status = 0 if b else 1 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) 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 Test(argv, need_right_bracket): """The test/[ builtin. The only difference between test and [ is that [ needs a matching ]. """ if need_right_bracket: if argv[-1] != ']': util.error('[: missing closing ]') return 2 del argv[-1] w_parser = _StringWordEmitter(argv) b_parser = bool_parse.BoolParser(w_parser) # There is a fundamental ambiguity due to poor language design, in cases like: # [ -z ] # [ -z -a ] # [ -z -a ] ] # # See posixtest() in bash's test.c: # "This is an implementation of a Posix.2 proposal by David Korn." # It dispatches on expressions of length 0, 1, 2, 3, 4, and N args. We do # the same here. # # Another ambiguity: # -a is both a unary prefix operator and an infix operator. How to fix this # ambiguity? bool_node = None n = len(argv) try: if n == 0: return 1 # [ ] is False elif n == 1: bool_node = _StringWordTest(argv[0]) elif n == 2: bool_node = _TwoArgs(argv) elif n == 3: bool_node = _ThreeArgs(argv) if n == 4: a0 = argv[0] if a0 == '!': child = _ThreeArgs(argv[1:]) bool_node = ast.LogicalNot(child) elif a0 == '(' and argv[3] == ')': bool_node = _TwoArgs(argv[1:3]) else: pass # fallthrough if bool_node is None: bool_node = b_parser.ParseForBuiltin() #log('Bool expr %s', bool_node) if bool_node is None: for e in b_parser.Error(): log("test: %s", e.UserErrorString()) # TODO: There should be a nice method to print argv. And some way to # point to the error. log("Error parsing test/[ expression: %s", argv) return 2 # parse error is 2 except util.ParseError as e: util.error(e.UserErrorString()) return 2 # mem: Don't need it for BASH_REMATCH? Or I guess you could support it # exec_opts: don't need it, but might need it later mem = None exec_opts = None word_ev = _WordEvaluator() bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, word_ev) try: b = bool_ev.Eval(bool_node) except util.FatalRuntimeError as e: # e.g. [ -t xxx ] # TODO: Printing the location would be nice. print('test: %s' % e.UserErrorString(), file=sys.stderr) return 2 status = 0 if b else 1 return status
def Execute(self, node): """ Args: node: of type AstNode """ redirects = self._EvalRedirects(node) # TODO: Change this to its own enum? # or add EBuiltin.THROW _throw? For testing. # Is this different han exit? exit should really be throw. Because we # want to be able to unwind the stack, show stats, etc. Exiting in the # middle is bad. # exit and _throw could be the same, except _throw takes an error message, # and exits 1, and shows traceback. cflow = EBuiltin.NONE # TODO: Only eval argv[0] once. It can have side effects! if node.tag == command_e.SimpleCommand: argv = self.ev.EvalWords(node.words) if argv is None: err = self.ev.Error() # TODO: Throw shell exception raise AssertionError('Error evaluating words: %s' % err) more_env = self.ev.EvalEnv(node.more_env) if more_env is None: print(self.error_stack) # TODO: throw exception raise AssertionError() 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() if os.WIFEXITED(status): status = os.WEXITSTATUS(status) #print('exited with code', code) else: sig = os.WTERMSIG(status) #print('exited with signal', sig) # TODO: Is this right? status = 0 else: # Internal for r in redirects: r.ApplyInParent(self.fd_state) status, cflow = 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.RestoreAll() else: self.fd_state.ForgetAll() elif node.tag == command_e.Sentence: # TODO: Compile this away status, cflow = self.Execute(node.command) elif node.tag == command_e.Pipeline: status, cflow = 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: # NOTE: do_glob=False, because foo=*.a makes foo equal to '*.a', # literally. # TODO: Also have to evaluate the right hand side. ok, val = self.ev.EvalCompoundWord(pair.rhs) if not ok: return None pairs.append((pair.lhs, val)) flags = 0 # TODO: Calculate from keyword/flags if node.keyword == Id.Assign_Local: self.mem.SetLocal(pairs, flags) else: # could be readonly/export/etc. self.mem.SetGlobal(pairs, flags) # TODO: This should be eval of RHS, unlike bash! 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 = 0 # for empty list for child in node.children: status, cflow = self.Execute(child) # last status wins if cflow in (EBuiltin.BREAK, EBuiltin.CONTINUE): break elif node.tag == command_e.AndOr: #print(node.children) left, right = node.children status, cflow = self.Execute(left) if node.op_id == Id.Op_DPipe: if status != 0: status, cflow = self.Execute(right) elif node.op_id == Id.Op_DAmp: if status == 0: status, cflow = self.Execute(right) else: raise AssertionError elif node.tag == command_e.While: while True: status, _ = self.Execute(node.cond) if status != 0: break status, cflow = self.Execute(node.body) # last one wins if cflow == EBuiltin.BREAK: cflow = EBuiltin.NONE # reset since we respected it break if cflow == EBuiltin.CONTINUE: cflow = EBuiltin.NONE # reset since we respected it elif node.tag == command_e.ForEach: iter_name = node.iter_name if node.do_arg_iter: iter_list = self.mem.GetArgv() else: iter_list = self.ev.EvalWords(node.iter_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 cflow = EBuiltin.NONE for x in iter_list: self.mem.SetSimpleVar(iter_name, Value.FromString(x)) status, cflow = self.Execute(node.body) if cflow == EBuiltin.BREAK: cflow = EBuiltin.NONE # reset since we respected it break if cflow == EBuiltin.CONTINUE: cflow = EBuiltin.NONE # reset since we respected it elif node.tag == command_e.DoGroup: # Delegate to command list # TODO: This should be compiled out! status, cflow = 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: raise NotImplementedError 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)) # cflow should be EXCEPT # TODO: Is this the right place to put it? Does it need a stack for # function calls? self.mem.last_status = status return status, cflow
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