Skip to content

Commit

Permalink
Add location info to the warning given by a[undef]=1.
Browse files Browse the repository at this point in the history
The code is cleaner, but still needs more work.  Some warnings/errors
still don't have location info.
  • Loading branch information
Andy Chu committed Oct 1, 2018
1 parent 8ba085f commit 4b1fa54
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 43 deletions.
2 changes: 0 additions & 2 deletions core/cmd_exec_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

from core import cmd_exec # module under test
from core import dev
from core import legacy
from core import word_eval
from core import process
from core import state
from core import test_lib
Expand Down
61 changes: 33 additions & 28 deletions core/expr_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
scope_e = runtime.scope_e


def _StringToInteger(s, word=None):
def _StringToInteger(s, span_id=const.NO_INTEGER):
"""Use bash-like rules to coerce a string to an integer.
0xAB -- hex constant
Expand All @@ -61,22 +61,22 @@ def _StringToInteger(s, word=None):
integer = int(s, 16)
except ValueError:
# TODO: Show line number
e_die('Invalid hex constant %r', s, word=word)
e_die('Invalid hex constant %r', s, span_id=span_id)
return integer

if s.startswith('0'):
try:
integer = int(s, 8)
except ValueError:
e_die('Invalid octal constant %r', s, word=word) # TODO: Show line number
e_die('Invalid octal constant %r', s, span_id=span_id) # TODO: Show line number
return integer

if '#' in s:
b, digits = s.split('#', 1)
try:
base = int(b)
except ValueError:
e_die('Invalid base for numeric constant %r', b, word=word)
e_die('Invalid base for numeric constant %r', b, span_id=span_id)

integer = 0
n = 1
Expand All @@ -92,10 +92,10 @@ def _StringToInteger(s, word=None):
elif char.isdigit():
digit = int(char)
else:
e_die('Invalid digits for numeric constant %r', digits, word=word)
e_die('Invalid digits for numeric constant %r', digits, span_id=span_id)

if digit >= base:
e_die('Digits %r out of range for base %d', digits, base, word=word)
e_die('Digits %r out of range for base %d', digits, base, span_id=span_id)

integer += digit * n
n *= base
Expand All @@ -105,7 +105,7 @@ def _StringToInteger(s, word=None):
try:
integer = int(s)
except ValueError:
e_die("Invalid integer constant %r", s, word=word)
e_die("Invalid integer constant %r", s, span_id=span_id)
return integer


Expand All @@ -124,10 +124,14 @@ def __init__(self, mem, exec_opts, word_ev, arena):
self.word_ev = word_ev # type: word_eval.WordEvaluator
self.arena = arena

def _StringToIntegerOrError(self, s, word=None):
def _StringToIntegerOrError(self, s, blame_word=None,
span_id=const.NO_INTEGER):
"""Used by both [[ $x -gt 3 ]] and (( $x ))."""
if span_id == const.NO_INTEGER and blame_word:
span_id = word.LeftMostSpanForWord(blame_word)

try:
i = _StringToInteger(s, word=word)
i = _StringToInteger(s, span_id=span_id)
except util.FatalRuntimeError as e:
if self.exec_opts.strict_arith:
raise
Expand Down Expand Up @@ -222,24 +226,19 @@ def EvalLhs(node, arith_ev, mem, exec_opts):

class ArithEvaluator(_ExprEvaluator):

def _ValToArith(self, val, int_coerce=True, blame_word=None):
def _ValToArith(self, val, span_id, int_coerce=True):
"""Convert runtime.value to a Python int or list of strings."""
assert isinstance(val, runtime.value), '%r %r' % (val, type(val))

if int_coerce:
if val.tag == value_e.Undef: # 'nounset' already handled before got here
# Happens upon a[undefined]=42, which unfortunately turns into a[0]=42.
#log('blame_word %s arena %s', blame_word, self.arena)
if blame_word and self.arena:
span_id = word.LeftMostSpanForWord(blame_word)
if span_id != const.NO_INTEGER:
ui.PrintFilenameAndLine(span_id, self.arena)

warn('converting undefined variable to 0')
e_die('Coercing undefined value to 0 in arithmetic context', span_id=span_id)
return 0

if val.tag == value_e.Str:
return _StringToInteger(val.s, word=blame_word) # may raise FatalRuntimeError
return _StringToInteger(val.s, span_id=span_id) # may raise FatalRuntimeError

if val.tag == value_e.StrArray: # array is valid on RHS, but not on left
return val.strs
Expand All @@ -261,9 +260,13 @@ def _ValToArith(self, val, int_coerce=True, blame_word=None):
if val.tag == value_e.AssocArray:
return val.d

def _ValToArithOrError(self, val, int_coerce=True, word=None):
def _ValToArithOrError(self, val, int_coerce=True, blame_word=None,
span_id=const.NO_INTEGER):
if span_id == const.NO_INTEGER and blame_word:
span_id = word.LeftMostSpanForWord(blame_word)

try:
i = self._ValToArith(val, int_coerce=int_coerce, blame_word=word)
i = self._ValToArith(val, span_id, int_coerce=int_coerce)
except util.FatalRuntimeError as e:
if self.exec_opts.strict_arith:
raise
Expand Down Expand Up @@ -292,9 +295,9 @@ def _EvalLhsToArith(self, node):
if val.tag == value_e.StrArray:
e_die("Can't use assignment like ++ or += on arrays")

# TODO: attribute a word here. It really should be a span ID?
# We have a few nodes here like UnaryAssign and BinaryAssign.
i = self._ValToArithOrError(val, word=None)
# TODO: attribute a span ID here. There are a few cases, like UnaryAssign
# and BinaryAssign.
i = self._ValToArithOrError(val, span_id=const.NO_INTEGER)
return i, lval

def _Store(self, lval, new_int):
Expand All @@ -314,12 +317,14 @@ def Eval(self, node, int_coerce=True):
# to handle that as a special case.

if node.tag == arith_expr_e.ArithVarRef: # $(( x )) (can be array)
val = self._LookupVar(node.name)
return self._ValToArithOrError(val, int_coerce=int_coerce)
tok = node.token
val = self._LookupVar(tok.val)
return self._ValToArithOrError(val, int_coerce=int_coerce,
span_id=tok.span_id)

if node.tag == arith_expr_e.ArithWord: # $(( $x )) $(( ${x}${y} )), etc.
val = self.word_ev.EvalWordToString(node.w)
return self._ValToArithOrError(val, int_coerce=int_coerce, word=node.w)
return self._ValToArithOrError(val, int_coerce=int_coerce, blame_word=node.w)

if node.tag == arith_expr_e.UnaryAssign: # a++
op_id = node.op_id
Expand Down Expand Up @@ -439,7 +444,7 @@ def Eval(self, node, int_coerce=True):
return 0 # If not fatal, return 0

assert isinstance(item, str), item
return self._StringToIntegerOrError(item, word=lhs)
return self._StringToIntegerOrError(item)

if op_id == Id.Arith_Comma:
return rhs
Expand Down Expand Up @@ -642,8 +647,8 @@ def Eval(self, node):
if arg_type == bool_arg_type_e.Int:
# NOTE: We assume they are constants like [[ 3 -eq 3 ]].
# Bash also allows [[ 1+2 -eq 3 ]].
i1 = self._StringToIntegerOrError(s1, word=node.left)
i2 = self._StringToIntegerOrError(s2, word=node.right)
i1 = self._StringToIntegerOrError(s1, blame_word=node.left)
i2 = self._StringToIntegerOrError(s2, blame_word=node.right)

if op_id == Id.BoolBinary_eq:
return i1 == i2
Expand Down
13 changes: 6 additions & 7 deletions core/tdop.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ def ToLValue(node):
"""
# foo = bar, foo[1] = bar
if node.tag == arith_expr_e.ArithVarRef:
return ast.LhsName(node.name)
return ast.LhsName(node.token.val)
if node.tag == arith_expr_e.ArithBinary:
# For example, a[0][0] = 1 is NOT valid.
if (node.op_id == Id.Arith_LBracket and
node.left.tag == arith_expr_e.ArithVarRef):
return ast.LhsIndexedName(node.left.name, node.right)
return ast.LhsIndexedName(node.left.token.val, node.right)

return None

Expand All @@ -86,11 +86,10 @@ def NullError(p, t, bp):


def NullConstant(p, w, bp):
# The word itself is a node
if w.tag == word_e.CompoundWord:
var_name = word.AsArithVarName(w)
if var_name:
return ast.ArithVarRef(var_name)
var_name_token = word.LooksLikeArithVar(w)
if var_name_token:
return ast.ArithVarRef(var_name_token)

return ast.ArithWord(w)


Expand Down
9 changes: 5 additions & 4 deletions core/word.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,23 +346,24 @@ def AsFuncName(w):
return True, s


def AsArithVarName(w):
def LooksLikeArithVar(w):
"""Returns a string if this word looks like an arith var; otherwise False.
NOTE: This can't be combined with DetectAssignment because VarLike and
ArithVarLike must be different tokens. Otherwise _ReadCompoundWord will be
confused between array assigments foo=(1 2) and function calls foo(1, 2).
"""
assert w.tag == word_e.CompoundWord
if w.tag != word_e.CompoundWord:
return False

if len(w.parts) != 1:
return ""
return False

part0 = w.parts[0]
if _LiteralPartId(part0) != Id.Lit_ArithVarLike:
return False

return part0.token.val
return part0.token


def IsVarLike(w):
Expand Down
3 changes: 1 addition & 2 deletions osh/osh.asdl
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ module osh
| LhsIndexedName(string name, arith_expr index)

arith_expr =
-- TODO: need token
ArithVarRef(string name) -- variable without $
ArithVarRef(token token) -- variable without $
| ArithWord(word w) -- a string that looks like an integer

| UnaryAssign(id op_id, lhs_expr child)
Expand Down

0 comments on commit 4b1fa54

Please sign in to comment.