CODE-0006 · claude-opus-4-8 (medium)I'll write a recursive descent parser. Let me deliver the solution.
The final message is the deliverable.
import re
def eval_expr(s):
"""Evaluate an arithmetic expression string and return its numeric value.
Supports + - * / with standard precedence and left associativity,
parentheses, unary +/-, integer and decimal literals, and ignores
whitespace. True division is used; results that are exact integers are
returned as int, otherwise float. Raises ValueError on malformed input
or division by zero.
"""
tokens = _tokenize(s)
parser = _Parser(tokens)
value = parser.parse()
return _normalize(value)
_TOKEN_RE = re.compile(r"""
\s*(?:
(?P<number>(?:\d+\.\d*|\.\d+|\d+))
| (?P<op>[+\-*/()])
)
""", re.VERBOSE)
def _tokenize(s):
if s is None:
raise ValueError("expression must be a string")
tokens = []
pos = 0
n = len(s)
while pos < n:
# Skip leading whitespace explicitly to detect trailing junk cleanly.
m = _TOKEN_RE.match(s, pos)
if not m or m.end() == pos:
# Check whether the remainder is only whitespace.
if s[pos:].strip() == "":
break
raise ValueError("unknown token at position {}".format(pos))
if m.group("number") is not None:
text = m.group("number")
try:
if "." in text:
value = float(text)
else:
value = int(text)
except ValueError:
raise ValueError("invalid number literal: {!r}".format(text))
tokens.append(("number", value))
else:
tokens.append(("op", m.group("op")))
pos = m.end()
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):
raise ValueError("unexpected trailing token")
return value
def _expr(self):
# Addition and subtraction, left associative.
value = self._term()
while True:
kind, tok = self._peek()
if kind == "op" and tok in ("+", "-"):
self._advance()
right = self._term()
if tok == "+":
value = value + right
else:
value = value - right
else:
break
return value
def _term(self):
# Multiplication and division, left associative.
value = self._factor()
while True:
kind, tok = self._peek()
if kind == "op" and tok in ("*", "/"):
self._advance()
right = self._factor()
if tok == "*":
value = value * right
else:
if right == 0:
raise ValueError("division by zero")
value = value / right
else:
break
return value
def _factor(self):
# Unary operators, then a primary.
kind, tok = self._peek()
if kind == "op" and tok in ("+", "-"):
self._advance()
operand = self._factor()
return operand if tok == "+" else -operand
return self._primary()
def _primary(self):
kind, tok = self._peek()
if kind == "number":
self._advance()
return tok
if kind == "op" and tok == "(":
self._advance()
value = self._expr()
kind2, tok2 = self._peek()
if not (kind2 == "op" and tok2 == ")"):
raise ValueError("mismatched parentheses")
self._advance()
return value
if kind is None:
raise ValueError("unexpected end of expression")
raise ValueError("unexpected token: {!r}".format(tok))
def _normalize(value):
if isinstance(value, int):
return value
if isinstance(value, float):
if value == int(value) and value not in (float("inf"), float("-inf")):
return int(value)
return value
raise ValueError("non-numeric result")