CODE-0006 · claude-fable-5 (high)def eval_expr(s):
"""Evaluate an arithmetic expression string and return int or float.
Supports +, -, *, / with standard precedence and left associativity,
parentheses, unary + and -, integer and decimal literals, and
arbitrary whitespace. Raises ValueError on malformed input or
division by zero.
"""
if not isinstance(s, str):
raise ValueError("expression must be a string")
tokens = _tokenize(s)
if not tokens:
raise ValueError("empty expression")
pos = 0
def peek():
return tokens[pos] if pos < len(tokens) else None
def advance():
nonlocal pos
tok = tokens[pos]
pos += 1
return tok
def parse_expr():
value = parse_term()
while peek() in ('+', '-'):
op = advance()
rhs = parse_term()
value = value + rhs if op == '+' else value - rhs
return value
def parse_term():
value = parse_factor()
while peek() in ('*', '/'):
op = advance()
rhs = parse_factor()
if op == '*':
value = value * rhs
else:
if rhs == 0:
raise ValueError("division by zero")
value = value / rhs
return value
def parse_factor():
tok = peek()
if tok == '+':
advance()
return parse_factor()
if tok == '-':
advance()
return -parse_factor()
if tok == '(':
advance()
value = parse_expr()
if peek() != ')':
raise ValueError("mismatched parentheses")
advance()
return value
if isinstance(tok, (int, float)):
return advance()
if tok is None:
raise ValueError("unexpected end of expression")
raise ValueError("unexpected token: %r" % (tok,))
result = parse_expr()
if pos != len(tokens):
raise ValueError("unexpected token: %r" % (tokens[pos],))
if isinstance(result, float) and result.is_integer():
return int(result)
return result
def _tokenize(s):
tokens = []
i = 0
n = len(s)
while i < n:
c = s[i]
if c.isspace():
i += 1
continue
if c in '+-*/()':
tokens.append(c)
i += 1
continue
if c.isdigit() or c == '.':
j = i
seen_dot = False
while j < n and (s[j].isdigit() or s[j] == '.'):
if s[j] == '.':
if seen_dot:
raise ValueError("malformed number: %r" % (s[i:j + 1],))
seen_dot = True
j += 1
text = s[i:j]
if text == '.':
raise ValueError("malformed number: '.'")
tokens.append(float(text) if '.' in text else int(text))
i = j
continue
raise ValueError("unknown character: %r" % (c,))
return tokens