Skip to content

Commit

Permalink
[frontend refactor] Use loc.Arith() consistently
Browse files Browse the repository at this point in the history
Add TokenForArith()
  • Loading branch information
Andy C committed May 16, 2023
1 parent a5fae85 commit a053748
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 32 deletions.
24 changes: 17 additions & 7 deletions frontend/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ def GetSpanId(loc_):
else:
return runtime.NO_SPID

elif case(loc_e.Arith):
loc_ = cast(loc.Arith, UP_location)
if loc_.a:
tok = TokenForArith(loc_.a)
if tok:
return tok.span_id
else:
return runtime.NO_SPID
else:
return runtime.NO_SPID

else:
raise AssertionError()

Expand Down Expand Up @@ -149,21 +160,20 @@ def TokenForCommand(node):
return None


def OfArithExpr(node):
# type: (arith_expr_t) -> loc_t
"""
TODO: replace with loc.Arith()
"""
def TokenForArith(node):
# type: (arith_expr_t) -> Optional[Token]
UP_node = node
with tagswitch(node) as case:
if case(arith_expr_e.VarSub):
vsub = cast(SimpleVarSub, UP_node)
return vsub.left
elif case(arith_expr_e.Word):
w = cast(CompoundWord, UP_node)
return loc.Word(w)
return LeftTokenForWord(w)

return loc.Missing
# TODO: Fill in other cases

return None


def OfWordPartLeft(part):
Expand Down
47 changes: 22 additions & 25 deletions osh/sh_expr_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def CheckCircularDeps(self):
# type: () -> None
assert self.word_ev is not None

def _StringToInteger(self, s, location):
def _StringToInteger(self, s, blame_loc):
# type: (str, loc_t) -> int
"""Use bash-like rules to coerce a string to an integer.
Expand All @@ -299,22 +299,22 @@ def _StringToInteger(self, s, location):
try:
integer = int(s, 16)
except ValueError:
e_strict('Invalid hex constant %r' % s, location)
e_strict('Invalid hex constant %r' % s, blame_loc)
return integer

if s.startswith('0'):
try:
integer = int(s, 8)
except ValueError:
e_strict('Invalid octal constant %r' % s, location)
e_strict('Invalid octal constant %r' % s, blame_loc)
return integer

if '#' in s:
b, digits = mylib.split_once(s, '#')
try:
base = int(b)
except ValueError:
e_strict('Invalid base for numeric constant %r' % b, location)
e_strict('Invalid base for numeric constant %r' % b, blame_loc)

integer = 0
for ch in digits:
Expand All @@ -329,10 +329,11 @@ def _StringToInteger(self, s, location):
elif ch.isdigit():
digit = int(ch)
else:
e_strict('Invalid digits for numeric constant %r' % digits, location)
e_strict('Invalid digits for numeric constant %r' % digits, blame_loc)

if digit >= base:
e_strict('Digits %r out of range for base %d' % (digits, base), location)
e_strict('Digits %r out of range for base %d' % (digits, base),
blame_loc)

integer = integer * base + digit
return integer
Expand All @@ -355,7 +356,7 @@ def _StringToInteger(self, s, location):
a_parser = self.parse_ctx.MakeArithParser(s)

# TODO: Fill in the variable name
with alloc.ctx_Location(arena, source.Variable(None, location)):
with alloc.ctx_Location(arena, source.Variable(None, blame_loc)):
try:
node2 = a_parser.Parse() # may raise error.Parse
except error.Parse as e:
Expand All @@ -365,7 +366,7 @@ def _StringToInteger(self, s, location):
# Prevent infinite recursion of $(( 1x )) -- it's a word that evaluates
# to itself, and you don't want to reparse it as a word.
if node2.tag() == arith_expr_e.Word:
e_die("Invalid integer constant %r" % s, location)
e_die("Invalid integer constant %r" % s, blame_loc)

if self.exec_opts.eval_unsafe_arith():
integer = self.EvalToInt(node2)
Expand All @@ -381,29 +382,29 @@ def _StringToInteger(self, s, location):
else:
if len(s.strip()) == 0 or match.IsValidVarName(s):
# x42 could evaluate to 0
e_strict("Invalid integer constant %r" % s, location)
e_strict("Invalid integer constant %r" % s, blame_loc)
else:
# 42x is always fatal!
e_die("Invalid integer constant %r" % s, location)
e_die("Invalid integer constant %r" % s, blame_loc)

return integer

def _ValToIntOrError(self, val, location):
# type: (value_t, loc_t) -> int
def _ValToIntOrError(self, val, blame):
# type: (value_t, arith_expr_t) -> int
try:
UP_val = val
with tagswitch(val) as case:
if case(value_e.Undef): # 'nounset' already handled before got here
# Happens upon a[undefined]=42, which unfortunately turns into a[0]=42.
e_strict('Undefined value in arithmetic context', location)
e_strict('Undefined value in arithmetic context', loc.Arith(blame))

elif case(value_e.Int):
val = cast(value.Int, UP_val)
return val.i

elif case(value_e.Str):
val = cast(value.Str, UP_val)
return self._StringToInteger(val.s, location) # calls e_strict
return self._StringToInteger(val.s, loc.Arith(blame)) # calls e_strict

elif case(value_e.Obj):
# Note: this handles var x = 42; echo $(( x > 2 )).
Expand All @@ -424,7 +425,7 @@ def _ValToIntOrError(self, val, location):
# In bash, (( a )) is like (( a[0] )), but I don't want that.
# And returning '0' gives different results.
e_die("Expected a value convertible to integer, got %s" % ui.ValType(val),
location)
loc.Arith(blame))

def _EvalLhsAndLookupArith(self, node):
# type: (arith_expr_t) -> Tuple[int, lvalue_t]
Expand All @@ -447,8 +448,7 @@ def _EvalLhsAndLookupArith(self, node):
#if val.tag() == value_e.MaybeStrArray:
# e_die("Can't use assignment like ++ or += on arrays")

expr_loc = location.OfArithExpr(node)
i = self._ValToIntOrError(val, expr_loc)
i = self._ValToIntOrError(val, node)
return i, lval

def _Store(self, lval, new_int):
Expand All @@ -470,9 +470,7 @@ def EvalToInt(self, node):
if word_eval.ShouldArrayDecay(vsub.var_name, self.exec_opts):
val = word_eval.DecayArray(val)

# TODO: Can we avoid the runtime cost of adding location info?
expr_loc = location.OfArithExpr(node)
i = self._ValToIntOrError(val, expr_loc)
i = self._ValToIntOrError(val, node)
return i

def Eval(self, node):
Expand Down Expand Up @@ -674,16 +672,15 @@ def Eval(self, node):
ret = lhs * rhs
elif op_id == Id.Arith_Slash:
if rhs == 0:
# TODO: Could also blame /
e_die('Divide by zero',
location.OfArithExpr(node.right))
# TODO: blame / operator
e_die('Divide by zero', loc.Arith(node.right))

ret = lhs / rhs

elif op_id == Id.Arith_Percent:
if rhs == 0:
# TODO: Could also blame /
e_die('Divide by zero', location.OfArithExpr(node.right))
# TODO: blame / operator
e_die('Divide by zero', loc.Arith(node.right))

ret = lhs % rhs

Expand Down

0 comments on commit a053748

Please sign in to comment.