def Eval(self, node): # type: (command_t) -> None UP_node = node with tagswitch(node) as case: if case(command_e.Simple): node = cast(command__Simple, UP_node) # Need splitter for this. if 0: cmd_val = self.word_ev.EvalWordSequence2(node.words, allow_assign=True) for arg in cmd_val.argv: log('arg %s', arg) words = braces.BraceExpandWords(node.words) for w in words: val = self.word_ev.EvalWordToString(w) log('arg %r', val.s) elif case(command_e.DParen): node = cast(command__DParen, UP_node) a = self.arith_ev.Eval(node.child) # TODO: how to print repr() in C++? log('arith val %d', a.tag_()) else: log('Unhandled node %s', NewStr(command_str(node.tag_())))
def EvalRhsWord(self, word): """syntax.word -> value Used for RHS of assignment. There is no splitting. """ if word.tag == word_e.EmptyWord: return value.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 value.StrArray(strs) # If RHS doens't look like a=( ... ), then it must be a string. return self.EvalWordToString(word)
def EvalExpr(self, node): # type: (expr_t) -> Any """ This is a naive PyObject evaluator! It uses the type dispatch of the host Python interpreter. Returns: A Python object of ANY type. Should be wrapped in value.Obj() for storing in Mem. """ if 0: print('EvalExpr()') node.PrettyPrint() print('') if node.tag == expr_e.Const: id_ = node.c.id if id_ == Id.Expr_DecInt: return int(node.c.val) elif id_ == Id.Expr_BinInt: return int(node.c.val, 2) elif id_ == Id.Expr_OctInt: return int(node.c.val, 8) elif id_ == Id.Expr_HexInt: return int(node.c.val, 16) elif id_ == Id.Expr_Float: return float(node.c.val) elif id_ == Id.Expr_Null: return None elif id_ == Id.Expr_True: return True elif id_ == Id.Expr_False: return False elif id_ == Id.Expr_Name: # for {name: 'bob'} # Maybe also :Symbol? return node.c.val # NOTE: We could allow Ellipsis for a[:, ...] here, but we're not using # it yet. raise AssertionError(id_) if node.tag == expr_e.Var: return self.LookupVar(node.name.val) if node.tag == expr_e.CommandSub: return self.ex.RunCommandSub(node.command_list) if node.tag == expr_e.ShArrayLiteral: words = braces.BraceExpandWords(node.words) strs = self.word_ev.EvalWordSequence(words) #log('ARRAY LITERAL EVALUATED TO -> %s', strs) return objects.StrArray(strs) if node.tag == expr_e.DoubleQuoted: # In an ideal world, I would *statically* disallow: # - "$@" and "${array[@]}" # - backticks like `echo hi` # - $(( 1+2 )) and $[] -- although useful for refactoring # - not sure: ${x%%} -- could disallow this # - these enters the ArgDQ state: "${a:-foo bar}" ? # But that would complicate the parser/evaluator. So just rely on # strict_array to disallow the bad parts. return self.word_ev.EvalDoubleQuotedToString(node) if node.tag == expr_e.SingleQuoted: return word_eval.EvalSingleQuoted(node) if node.tag == expr_e.BracedVarSub: return self.word_ev.EvalBracedVarSubToString(node) if node.tag == expr_e.SimpleVarSub: return self.word_ev.EvalSimpleVarSubToString(node.token) if node.tag == expr_e.Unary: child = self.EvalExpr(node.child) if node.op.id == Id.Arith_Minus: return -child if node.op.id == Id.Arith_Tilde: return ~child if node.op.id == Id.Expr_Not: return not child raise NotImplementedError(node.op.id) if node.tag == expr_e.Binary: left = self.EvalExpr(node.left) right = self.EvalExpr(node.right) if node.op.id == Id.Arith_Plus: return left + right if node.op.id == Id.Arith_Minus: return left - right if node.op.id == Id.Arith_Star: return left * right if node.op.id == Id.Arith_Slash: # NOTE: from __future__ import division changes 5/2! # But just make it explicit. return float(left) / right # floating point division if node.op.id == Id.Expr_Div: return left // right # integer divison if node.op.id == Id.Expr_Mod: return left % right if node.op.id == Id.Arith_Caret: # Exponentiation return left**right # Bitwise if node.op.id == Id.Arith_Amp: return left & right if node.op.id == Id.Arith_Pipe: return left | right if node.op.id == Id.Expr_Xor: return left ^ right if node.op.id == Id.Arith_DGreat: return left >> right if node.op.id == Id.Arith_DLess: return left << right # Logical if node.op.id == Id.Expr_And: return left and right if node.op.id == Id.Expr_Or: return left or right raise NotImplementedError(node.op.id) if node.tag == expr_e.Range: # 1:10 or 1:10:2 lower = self.EvalExpr(node.lower) upper = self.EvalExpr(node.upper) return xrange(lower, upper) if node.tag == expr_e.Slice: # a[:0] lower = self.EvalExpr(node.lower) if node.lower else None upper = self.EvalExpr(node.upper) if node.upper else None return slice(lower, upper) if node.tag == expr_e.Compare: left = self.EvalExpr(node.left) result = True # Implicit and for op, right_expr in zip(node.ops, node.comparators): right = self.EvalExpr(right_expr) if op.id == Id.Arith_Less: result = left < right elif op.id == Id.Arith_Great: result = left > right elif op.id == Id.Arith_GreatEqual: result = left >= right elif op.id == Id.Arith_LessEqual: result = left <= right elif op.id == Id.Arith_DEqual: result = left == right elif op.id == Id.Expr_In: result = left in right elif op.id == Id.Node_NotIn: result = left not in right elif op.id == Id.Expr_Is: result = left is right elif op.id == Id.Node_IsNot: result = left is not right else: try: if op.id == Id.Arith_Tilde: result = self._EvalMatch(left, right, True) elif op.id == Id.Expr_NotTilde: result = not self._EvalMatch(left, right, False) else: raise AssertionError(op.id) except RuntimeError as e: # Status 2 indicates a regex parse error. This is fatal in OSH but # not in bash, which treats [[ like a command with an exit code. e_die("Invalid regex %r", right, span_id=op.span_id, status=2) if not result: return result left = right return result if node.tag == expr_e.IfExp: b = self.EvalExpr(node.test) if b: return self.EvalExpr(node.body) else: return self.EvalExpr(node.orelse) if node.tag == expr_e.List: return [self.EvalExpr(e) for e in node.elts] if node.tag == expr_e.Tuple: return tuple(self.EvalExpr(e) for e in node.elts) if node.tag == expr_e.Dict: # NOTE: some keys are expr.Const keys = [self.EvalExpr(e) for e in node.keys] values = [] for i, e in enumerate(node.values): if e.tag == expr_e.Implicit: v = self.LookupVar(keys[i]) # {name} else: v = self.EvalExpr(e) values.append(v) return dict(zip(keys, values)) if node.tag == expr_e.ListComp: # TODO: # - Consolidate with command_e.OilForIn in osh/cmd_exec.py? # - Do I have to push a temp frame here? # Hm... lexical or dynamic scope is an issue. result = [] comp = node.generators[0] obj = self.EvalExpr(comp.iter) # TODO: Handle x,y etc. iter_name = comp.lhs[0].name.val if isinstance(obj, str): e_die("Strings aren't iterable") else: it = obj.__iter__() while True: try: loop_val = it.next() # e.g. x except StopIteration: break self.mem.SetVar(lvalue.Named(iter_name), value.Obj(loop_val), scope_e.LocalOnly) if comp.cond: b = self.EvalExpr(comp.cond) else: b = True if b: item = self.EvalExpr(node.elt) # e.g. x*2 result.append(item) return result if node.tag == expr_e.GeneratorExp: comp = node.generators[0] obj = self.EvalExpr(comp.iter) # TODO: Support (x for x, y in ...) iter_name = comp.lhs[0].name.val it = obj.__iter__() # TODO: There is probably a much better way to do this! # The scope of the loop variable is wrong, etc. def _gen(): while True: try: loop_val = it.next() # e.g. x except StopIteration: break self.mem.SetVar(lvalue.Named(iter_name), value.Obj(loop_val), scope_e.LocalOnly) if comp.cond: b = self.EvalExpr(comp.cond) else: b = True if b: item = self.EvalExpr(node.elt) # e.g. x*2 yield item return _gen() if node.tag == expr_e.Lambda: return objects.Lambda(node, self.ex) if node.tag == expr_e.FuncCall: func = self.EvalExpr(node.func) pos_args, named_args = self.EvalArgList(node.args) ret = func(*pos_args, **named_args) return ret if node.tag == expr_e.Subscript: obj = self.EvalExpr(node.obj) index = self._EvalIndices(node.indices) return obj[index] # TODO: obj.method() should be separate if node.tag == expr_e.Attribute: # obj.attr o = self.EvalExpr(node.obj) id_ = node.op.id if id_ == Id.Expr_Dot: name = node.attr.val # TODO: Does this do the bound method thing we do NOT want? return getattr(o, name) if id_ == Id.Expr_RArrow: # d->key is like d['key'] name = node.attr.val return o[name] if id_ == Id.Expr_DColon: # StaticName::member raise NotImplementedError(id_) # TODO: We should prevent virtual lookup here? This is a pure static # namespace lookup? # But Python doesn't any hook for this. # Maybe we can just check that it's a module? And modules don't lookup # in a supertype or __class__, etc. raise AssertionError(id_) if node.tag == expr_e.RegexLiteral: # obj.attr # TODO: Should this just be an object that ~ calls? return objects.Regex(self.EvalRegex(node.regex)) if node.tag == expr_e.ArrayLiteral: # obj.attr items = [self.EvalExpr(item) for item in node.items] if items: # Determine type at runtime? If we have something like @[(i) (j)] # then we don't know its type until runtime. first = items[0] if isinstance(first, bool): return objects.BoolArray(bool(x) for x in items) elif isinstance(first, int): return objects.IntArray(int(x) for x in items) elif isinstance(first, float): return objects.FloatArray(float(x) for x in items) elif isinstance(first, str): return objects.StrArray(str(x) for x in items) else: raise AssertionError(first) else: # TODO: Should this have an unknown type? # What happens when you mutate or extend it? You have to make sure # that the type tags match? return objects.BoolArray(items) raise NotImplementedError(node.__class__.__name__)
def EvalExpr(self, node): # type: (expr_t) -> Any """ This is a naive PyObject evaluator! It uses the type dispatch of the host Python interpreter. Returns: A Python object of ANY type. Should be wrapped in value.Obj() for storing in Mem. """ if 0: print('EvalExpr()') node.PrettyPrint() print('') if node.tag == expr_e.Const: return int(node.c.val) if node.tag == expr_e.Var: return self.LookupVar(node.name.val) if node.tag == expr_e.CommandSub: return self.ex.RunCommandSub(node.command_list) if node.tag == expr_e.ShellArrayLiteral: words = node.items words = braces.BraceExpandWords(words) strs = self.word_ev.EvalWordSequence(words) #log('ARRAY LITERAL EVALUATED TO -> %s', strs) return objects.StrArray(strs) if node.tag == expr_e.DoubleQuoted: s = ''.join(self.EvalWordPart(part) for part in node.parts) return s if node.tag == expr_e.Unary: child = self.EvalExpr(node.child) if node.op.id == Id.Arith_Minus: return -child raise NotImplementedError(node.op.id) if node.tag == expr_e.Binary: left = self.EvalExpr(node.left) right = self.EvalExpr(node.right) if node.op.id == Id.Arith_Plus: return left + right if node.op.id == Id.Arith_Minus: return left - right if node.op.id == Id.Arith_Star: return left * right if node.op.id == Id.Arith_Less: return left < right if node.op.id == Id.Arith_Great: return left > right raise NotImplementedError(node.op.id) if node.tag == expr_e.List: return [self.EvalExpr(e) for e in node.elts] if node.tag == expr_e.FuncCall: # TODO: # First cut builtins: # # len() Int # split(s Str) StrArray (what about List[Str])? # join(s StrArray) Str # # Let Python handle type errors for now? # TODO: Lookup in equivalent of __builtins__ # # shopt -s namespaces # # builtin log "hello" # builtin log "hello" #node.PrettyPrint() # TODO: All functions called like f(x, y) must be in 'mem'. # Only 'procs' are in self.funcs # First look up the name in 'funcs'. And then look it up # in 'mem' for first-class functions? #if node.func.tag == expr_e.Var: # func = self.funcs.get(node.func.name.val) func = self.EvalExpr(node.func) args = [self.EvalExpr(a) for a in node.args] ret = func(*args) return ret if node.tag == expr_e.Subscript: collection = self.EvalExpr(node.collection) # TODO: handle multiple indices like a[i, j] index = self.EvalExpr(node.indices[0]) return collection[index] raise NotImplementedError(node.__class__.__name__)
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) # NOTE: RunSimpleCommand never returns when fork_external=False! if node.more_env: # I think this guard is necessary? self.mem.PushTemp() try: self._EvalTempEnv(node.more_env) status = self.RunSimpleCommand(argv, fork_external, span_id) finally: self.mem.PopTemp() else: status = self.RunSimpleCommand(argv, fork_external, span_id) elif node.tag == command_e.ExpandedAlias: # Expanded aliases need redirects and env bindings from the calling # context, as well as redirects in the expansion! # TODO: SetCurrentSpanId to OUTSIDE? Don't bother with stuff inside # expansion, since aliase are discouarged. if node.more_env: self.mem.PushTemp() try: self._EvalTempEnv(node.more_env) status = self._Execute(node.child) finally: self.mem.PopTemp() else: status = self._Execute(node.child) 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.command_list) 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: # TODO: Also do dynamic assignment here 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 = value.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 = value.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, value_t), 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) # 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 EvalExpr(self, node): # type: (expr_t) -> Any """ This is a naive PyObject evaluator! It uses the type dispatch of the host Python interpreter. Returns: A Python object of ANY type. Should be wrapped in value.Obj() for storing in Mem. """ if 0: print('EvalExpr()') node.PrettyPrint() print('') if node.tag == expr_e.Const: id_ = node.c.id if id_ == Id.Expr_DecInt: return int(node.c.val) elif id_ == Id.Expr_BinInt: return int(node.c.val, 2) elif id_ == Id.Expr_OctInt: return int(node.c.val, 8) elif id_ == Id.Expr_HexInt: return int(node.c.val, 16) elif id_ == Id.Expr_Float: return float(node.c.val) elif id_ == Id.Expr_Null: return None elif id_ == Id.Expr_True: return True elif id_ == Id.Expr_False: return False elif id_ == Id.Expr_Name: # for {name: 'bob'} # Maybe also :Symbol? return node.c.val raise AssertionError(id_) if node.tag == expr_e.Var: return self.LookupVar(node.name.val) if node.tag == expr_e.CommandSub: return self.ex.RunCommandSub(node.command_list) if node.tag == expr_e.ShArrayLiteral: words = braces.BraceExpandWords(node.words) strs = self.word_ev.EvalWordSequence(words) #log('ARRAY LITERAL EVALUATED TO -> %s', strs) return objects.StrArray(strs) if node.tag == expr_e.DoubleQuoted: # In an ideal world, I would *statically* disallow: # - "$@" and "${array[@]}" # - backticks like `echo hi` # - $(( 1+2 )) and $[] -- although useful for refactoring # - not sure: ${x%%} -- could disallow this # - these enters the ArgDQ state: "${a:-foo bar}" ? # But that would complicate the parser/evaluator. So just rely on # strict_array to disallow the bad parts. return self.word_ev.EvalDoubleQuotedToString(node) if node.tag == expr_e.SingleQuoted: return word_eval.EvalSingleQuoted(node) if node.tag == expr_e.BracedVarSub: return self.word_ev.EvalBracedVarSubToString(node) if node.tag == expr_e.SimpleVarSub: return self.word_ev.EvalSimpleVarSubToString(node.token) if node.tag == expr_e.Unary: child = self.EvalExpr(node.child) if node.op.id == Id.Arith_Minus: return -child if node.op.id == Id.Arith_Tilde: return ~child if node.op.id == Id.Expr_Not: return not child raise NotImplementedError(node.op.id) if node.tag == expr_e.Binary: left = self.EvalExpr(node.left) right = self.EvalExpr(node.right) if node.op.id == Id.Arith_Plus: return left + right if node.op.id == Id.Arith_Minus: return left - right if node.op.id == Id.Arith_Star: return left * right if node.op.id == Id.Arith_Slash: # NOTE: from __future__ import division changes 5/2! # But just make it explicit. return float(left) / right # floating point division if node.op.id == Id.Expr_Div: return left // right # integer divison if node.op.id == Id.Arith_Percent: return left % right if node.op.id == Id.Arith_Caret: # Exponentiation return left**right # Bitwise if node.op.id == Id.Arith_Amp: return left & right if node.op.id == Id.Arith_Pipe: return left | right if node.op.id == Id.Expr_Xor: return left ^ right if node.op.id == Id.Arith_DGreat: return left >> right if node.op.id == Id.Arith_DLess: return left << right # Logical if node.op.id == Id.Expr_And: return left and right if node.op.id == Id.Expr_Or: return left or right raise NotImplementedError(node.op.id) if node.tag == expr_e.Compare: left = self.EvalExpr(node.left) result = True # Implicit and for op, right_expr in zip(node.ops, node.comparators): right = self.EvalExpr(right_expr) if op.id == Id.Arith_Less: result = left < right elif op.id == Id.Arith_Great: result = left > right elif op.id == Id.Arith_GreatEqual: result = left >= right elif op.id == Id.Arith_LessEqual: result = left <= right elif op.id == Id.Arith_DEqual: result = left == right elif op.id == Id.Expr_In: result = left in right elif op.id == Id.Node_NotIn: result = left not in right elif op.id == Id.Expr_Is: result = left is right elif op.id == Id.Node_IsNot: result = left is not right else: try: if op.id == Id.Arith_Tilde: result = self._EvalMatch(left, right, True) elif op.id == Id.Expr_NotTilde: result = not self._EvalMatch(left, right, False) else: raise AssertionError(op.id) except RuntimeError as e: # Status 2 indicates a regex parse error. This is fatal in OSH but # not in bash, which treats [[ like a command with an exit code. e_die("Invalid regex %r", right, span_id=op.span_id, status=2) if not result: return result left = right return result if node.tag == expr_e.IfExp: b = self.EvalExpr(node.test) if b: return self.EvalExpr(node.body) else: return self.EvalExpr(node.orelse) if node.tag == expr_e.List: return [self.EvalExpr(e) for e in node.elts] if node.tag == expr_e.Tuple: return tuple(self.EvalExpr(e) for e in node.elts) if node.tag == expr_e.Dict: # NOTE: some keys are expr.Const keys = [self.EvalExpr(e) for e in node.keys] values = [] for i, e in enumerate(node.values): if e.tag == expr_e.Implicit: v = self.LookupVar(keys[i]) # {name} else: v = self.EvalExpr(e) values.append(v) return dict(zip(keys, values)) if node.tag == expr_e.ListComp: # TODO: # - Consolidate with command_e.OilForIn in osh/cmd_exec.py? # - Do I have to push a temp frame here? # Hm... lexical or dynamic scope is an issue. result = [] comp = node.generators[0] obj = self.EvalExpr(comp.iter) # TODO: Handle x,y etc. iter_name = comp.target.name.val if isinstance(obj, str): e_die("Strings aren't iterable") else: it = iter(obj) while True: try: loop_val = next(it) # e.g. x except StopIteration: break self.mem.SetVar(lvalue.Named(iter_name), value.Obj(loop_val), (), scope_e.LocalOnly) if comp.ifs: b = self.EvalExpr(comp.ifs[0]) else: b = True if b: item = self.EvalExpr(node.elt) # e.g. x*2 result.append(item) return result if node.tag == expr_e.FuncCall: # TODO: # # Let Python handle type errors for now? # TODO: Lookup in equivalent of __builtins__ # # shopt -s namespaces # # builtin log "hello" # builtin log "hello" #node.PrettyPrint() # TODO: All functions called like f(x, y) must be in 'mem'. # Only 'procs' are in self.funcs # First look up the name in 'funcs'. And then look it up # in 'mem' for first-class functions? #if node.func.tag == expr_e.Var: # func = self.funcs.get(node.func.name.val) func = self.EvalExpr(node.func) args = [self.EvalExpr(a) for a in node.args] ret = func(*args) return ret if node.tag == expr_e.Subscript: collection = self.EvalExpr(node.collection) # TODO: handle multiple indices like a[i, j] index = self.EvalExpr(node.indices[0]) return collection[index] # TODO: obj.method() should be separate if node.tag == expr_e.Attribute: # obj.attr o = self.EvalExpr(node.value) id_ = node.op.id if id_ == Id.Expr_Dot: name = node.attr.val # TODO: Does this do the bound method thing we do NOT want? return getattr(o, name) if id_ == Id.Expr_RArrow: # d->key is like d['key'] name = node.attr.val return o[name] if id_ == Id.Expr_DColon: # StaticName::member raise NotImplementedError(id_) # TODO: We should prevent virtual lookup here? This is a pure static # namespace lookup? # But Python doesn't any hook for this. # Maybe we can just check that it's a module? And modules don't lookup # in a supertype or __class__, etc. raise AssertionError(id_) if node.tag == expr_e.RegexLiteral: # obj.attr # TODO: Should this just be an object that ~ calls? return objects.Regex(self.EvalRegex(node.regex)) raise NotImplementedError(node.__class__.__name__)