CODE-0006 · claude-opus-4-8 (high)"""Arithmetic expression evaluator.
Hand-written recursive-descent parser for a small arithmetic grammar:
expr := term (('+' | '-') term)*
term := factor (('*' | '/') factor)*
factor := ('+' | '-') factor | primary
primary:= NUMBER | '(' expr ')'
Supports +, -, *, / with standard precedence and left associativity,
parentheses, unary + and -, integer and decimal literals, and arbitrary
whitespace. `/` is true division. An exact integer result is returned as an
`int`, otherwise as a `float`. Malformed input and division by zero raise
`ValueError`.
"""
def _tokenize(s):
"""Turn the input string into a list of tokens.
Each token is a tuple (kind, value). `kind` is one of
'NUMBER', '+', '-', '*', '/', '(', ')'. Numbers carry their float value.
Raises ValueError on an unknown character or a malformed number.
"""
tokens = []
i = 0
n = len(s)
while i < n:
c = s[i]
if c.isspace():
i += 1
continue
if c in '+-*/()':
tokens.append((c, c))
i += 1
continue
if c.isdigit() or c == '.':
start = i
seen_dot = False
while i < n and (s[i].isdigit() or s[i] == '.'):
if s[i] == '.':
if seen_dot:
raise ValueError(
"malformed number: multiple decimal points "
"in %r" % s[start:i + 1]
)
seen_dot = True
i += 1
text = s[start:i]
if text == '.':
raise ValueError("malformed number: lone '.'")
try:
value = float(text)
except ValueError:
raise ValueError("malformed number: %r" % text)
tokens.append(('NUMBER', value))
continue
raise ValueError("unknown token: %r" % c)
return tokens
class _Parser:
def __init__(self, tokens):
self._tokens = tokens
self._pos = 0
def _peek(self):
if self._pos < len(self._tokens):
return self._tokens[self._pos]
return (None, None)
def _advance(self):
tok = self._tokens[self._pos]
self._pos += 1
return tok
def parse(self):
if not self._tokens:
raise ValueError("empty expression")
value = self._expr()
if self._pos != len(self._tokens):
kind, _ = self._peek()
raise ValueError("unexpected token: %r" % kind)
return value
def _expr(self):
value = self._term()
while self._peek()[0] in ('+', '-'):
op, _ = self._advance()
rhs = self._term()
if op == '+':
value = value + rhs
else:
value = value - rhs
return value
def _term(self):
value = self._factor()
while self._peek()[0] in ('*', '/'):
op, _ = self._advance()
rhs = self._factor()
if op == '*':
value = value * rhs
else:
if rhs == 0:
raise ValueError("division by zero")
value = value / rhs
return value
def _factor(self):
kind, _ = self._peek()
if kind == '+':
self._advance()
return self._factor()
if kind == '-':
self._advance()
return -self._factor()
return self._primary()
def _primary(self):
kind, value = self._peek()
if kind == 'NUMBER':
self._advance()
return value
if kind == '(':
self._advance()
inner = self._expr()
if self._peek()[0] != ')':
raise ValueError("mismatched parentheses: expected ')'")
self._advance()
return inner
if kind is None:
raise ValueError("unexpected end of expression")
raise ValueError("unexpected token: %r" % kind)
def eval_expr(s):
"""Evaluate the arithmetic expression `s` and return its numeric value.
Returns an `int` when the result is an exact integer value, otherwise a
`float`. Raises `ValueError` for malformed input or division by zero.
"""
if not isinstance(s, str):
raise ValueError("expression must be a string")
tokens = _tokenize(s)
result = _Parser(tokens).parse()
# Normalise an exact integer result to int.
if isinstance(result, float):
if result != result: # NaN guard (cannot arise here, defensive)
return result
if result == int(result) and result not in (float('inf'), float('-inf')):
return int(result)
return result