def _EvalLhsAndLookupArith(self, node): # type: (arith_expr_t) -> Tuple[int, lvalue_t] """ For x = y and x += y and ++x """ lval = self.EvalArithLhs(node, runtime.NO_SPID) val = OldValue(lval, self.mem, self.exec_opts) # BASH_LINENO, arr (array name with shopt -s compat_array), etc. if val.tag_() in (value_e.MaybeStrArray, value_e.AssocArray ) and lval.tag_() == lvalue_e.Named: named_lval = cast(lvalue__Named, lval) if word_eval.CheckCompatArray(named_lval.name, self.exec_opts): if val.tag_() == value_e.MaybeStrArray: lval = lvalue.Indexed(named_lval.name, 0) elif val.tag_() == value_e.AssocArray: lval = lvalue.Keyed(named_lval.name, '0') val = word_eval.ResolveCompatArray(val) # This error message could be better, but we already have one #if val.tag_() == value_e.MaybeStrArray: # e_die("Can't use assignment like ++ or += on arrays") span_id = location.SpanForArithExpr(node) i = self._ValToIntOrError(val, span_id=span_id) return i, lval
def EvalToInt(self, node): # type: (arith_expr_t) -> int """Used externally by ${a[i+1]} and ${a:start:len}. Also used internally. """ val = self.Eval(node) # TODO: Can we avoid the runtime cost of adding location info? span_id = location.SpanForArithExpr(node) i = self._ValToIntOrError(val, span_id=span_id) return i
def EvalToInt(self, node): # type: (arith_expr_t) -> int """Used externally by ${a[i+1]} and ${a:start:len}. Also used internally. """ val = self.Eval(node) # BASH_LINENO, arr (array name with shopt -s compat_array), etc. if val.tag_() in (value_e.MaybeStrArray, value_e.AssocArray) and node.tag_() == arith_expr_e.VarRef: tok = cast(Token, node) if word_eval.CheckCompatArray(tok.val, self.exec_opts): val = word_eval.ResolveCompatArray(val) # TODO: Can we avoid the runtime cost of adding location info? span_id = location.SpanForArithExpr(node) i = self._ValToIntOrError(val, span_id=span_id) return i
def Eval(self, node): # type: (arith_expr_t) -> value_t """ Args: node: arith_expr_t Returns: None for Undef (e.g. empty cell) TODO: Don't return 0! int for Str List[int] for MaybeStrArray Dict[str, str] for AssocArray (TODO: Should we support this?) NOTE: (( A['x'] = 'x' )) and (( x = A['x'] )) are syntactically valid in bash, but don't do what you'd think. 'x' sometimes a variable name and sometimes a key. """ # OSH semantics: Variable NAMES cannot be formed dynamically; but INTEGERS # can. ${foo:-3}4 is OK. $? will be a compound word too, so we don't have # to handle that as a special case. UP_node = node with tagswitch(node) as case: if case(arith_expr_e.VarRef): # $(( x )) (can be array) node = cast(arith_expr__VarRef, UP_node) tok = node.token return _LookupVar(tok.val, self.mem, self.exec_opts) elif case( arith_expr_e.ArithWord): # $(( $x )) $(( ${x}${y} )), etc. node = cast(arith_expr__ArithWord, UP_node) return self.word_ev.EvalWordToString(node.w) elif case(arith_expr_e.UnaryAssign): # a++ node = cast(arith_expr__UnaryAssign, UP_node) op_id = node.op_id old_int, lval = self._EvalLhsAndLookupArith(node.child) if op_id == Id.Node_PostDPlus: # post-increment new_int = old_int + 1 ret = old_int elif op_id == Id.Node_PostDMinus: # post-decrement new_int = old_int - 1 ret = old_int elif op_id == Id.Arith_DPlus: # pre-increment new_int = old_int + 1 ret = new_int elif op_id == Id.Arith_DMinus: # pre-decrement new_int = old_int - 1 ret = new_int else: raise AssertionError(op_id) #log('old %d new %d ret %d', old_int, new_int, ret) self._Store(lval, new_int) return value.Int(ret) elif case(arith_expr_e.BinaryAssign): # a=1, a+=5, a[1]+=5 node = cast(arith_expr__BinaryAssign, UP_node) op_id = node.op_id if op_id == Id.Arith_Equal: lval = _EvalLhsArith(node.left, self.mem, self) # Disallowing (( a = myarray )) # It has to be an integer rhs_int = self.EvalToInt(node.right) self._Store(lval, rhs_int) return value.Int(rhs_int) old_int, lval = self._EvalLhsAndLookupArith(node.left) rhs = self.EvalToInt(node.right) if op_id == Id.Arith_PlusEqual: new_int = old_int + rhs elif op_id == Id.Arith_MinusEqual: new_int = old_int - rhs elif op_id == Id.Arith_StarEqual: new_int = old_int * rhs elif op_id == Id.Arith_SlashEqual: if rhs == 0: e_die('Divide by zero') # TODO: location new_int = old_int / rhs elif op_id == Id.Arith_PercentEqual: if rhs == 0: e_die('Divide by zero') # TODO: location new_int = old_int % rhs elif op_id == Id.Arith_DGreatEqual: new_int = old_int >> rhs elif op_id == Id.Arith_DLessEqual: new_int = old_int << rhs elif op_id == Id.Arith_AmpEqual: new_int = old_int & rhs elif op_id == Id.Arith_PipeEqual: new_int = old_int | rhs elif op_id == Id.Arith_CaretEqual: new_int = old_int ^ rhs else: raise AssertionError(op_id) # shouldn't get here self._Store(lval, new_int) return value.Int(new_int) elif case(arith_expr_e.Unary): node = cast(arith_expr__Unary, UP_node) op_id = node.op_id i = self.EvalToInt(node.child) if op_id == Id.Node_UnaryPlus: ret = i elif op_id == Id.Node_UnaryMinus: ret = -i elif op_id == Id.Arith_Bang: # logical negation ret = 1 if i == 0 else 0 elif op_id == Id.Arith_Tilde: # bitwise complement ret = ~i else: raise AssertionError(op_id) # shouldn't get here return value.Int(ret) elif case(arith_expr_e.Binary): node = cast(arith_expr__Binary, UP_node) op_id = node.op_id # Short-circuit evaluation for || and &&. if op_id == Id.Arith_DPipe: lhs = self.EvalToInt(node.left) if lhs == 0: rhs = self.EvalToInt(node.right) ret = int(rhs != 0) else: ret = 1 # true return value.Int(ret) if op_id == Id.Arith_DAmp: lhs = self.EvalToInt(node.left) if lhs == 0: ret = 0 # false else: rhs = self.EvalToInt(node.right) ret = int(rhs != 0) return value.Int(ret) if op_id == Id.Arith_LBracket: # NOTE: Similar to bracket_op_e.ArrayIndex in osh/word_eval.py left = self.Eval(node.left) UP_left = left with tagswitch(left) as case: if case(value_e.MaybeStrArray): left = cast(value__MaybeStrArray, UP_left) rhs_int = self.EvalToInt(node.right) try: # could be None because representation is sparse s = left.strs[rhs_int] except IndexError: s = None elif case(value_e.AssocArray): left = cast(value__AssocArray, UP_left) key = self.EvalWordToString(node.right) s = left.d.get(key) else: # TODO: Add error context e_die( 'Expected array or assoc in index expression, got %s', ui.ValType(left)) if s is None: val = value.Undef() # type: value_t else: val = value.Str(s) return val if op_id == Id.Arith_Comma: self.Eval(node.left) # throw away result return self.Eval(node.right) # Rest are integers lhs = self.EvalToInt(node.left) rhs = self.EvalToInt(node.right) if op_id == Id.Arith_Plus: ret = lhs + rhs elif op_id == Id.Arith_Minus: ret = lhs - rhs elif op_id == Id.Arith_Star: ret = lhs * rhs elif op_id == Id.Arith_Slash: if rhs == 0: # TODO: Could also blame / e_die('Divide by zero', span_id=location.SpanForArithExpr(node.right)) ret = lhs / rhs elif op_id == Id.Arith_Percent: if rhs == 0: # TODO: Could also blame / e_die('Divide by zero', span_id=location.SpanForArithExpr(node.right)) ret = lhs % rhs elif op_id == Id.Arith_DStar: # OVM is stripped of certain functions that are somehow necessary for # exponentiation. # Python/ovm_stub_pystrtod.c:21: PyOS_double_to_string: Assertion `0' # failed. if rhs < 0: e_die("Exponent can't be less than zero" ) # TODO: error location ret = 1 for i in xrange(rhs): ret *= lhs elif op_id == Id.Arith_DEqual: ret = int(lhs == rhs) elif op_id == Id.Arith_NEqual: ret = int(lhs != rhs) elif op_id == Id.Arith_Great: ret = int(lhs > rhs) elif op_id == Id.Arith_GreatEqual: ret = int(lhs >= rhs) elif op_id == Id.Arith_Less: ret = int(lhs < rhs) elif op_id == Id.Arith_LessEqual: ret = int(lhs <= rhs) elif op_id == Id.Arith_Pipe: ret = lhs | rhs elif op_id == Id.Arith_Amp: ret = lhs & rhs elif op_id == Id.Arith_Caret: ret = lhs ^ rhs # Note: how to define shift of negative numbers? elif op_id == Id.Arith_DLess: ret = lhs << rhs elif op_id == Id.Arith_DGreat: ret = lhs >> rhs else: raise AssertionError(op_id) return value.Int(ret) elif case(arith_expr_e.TernaryOp): node = cast(arith_expr__TernaryOp, UP_node) cond = self.EvalToInt(node.cond) if cond: # nonzero return self.Eval(node.true_expr) else: return self.Eval(node.false_expr) else: raise AssertionError(node.tag_())