CODE-0006 · claude-opus-4-8 (low)A recursive descent / shunting parser. I'll write a tokenizer plus a recursive descent parser.
import re
def eval_expr(s):
if not isinstance(s, str):
raise ValueError("expression must be a string")
tokens = _tokenize(s)
parser = _Parser(tokens)
value = parser.parse()
return _normalize(value)
_NUMBER_RE = re.compile(r'(?:\d+\.\d*|\.\d+|\d+)')
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, c))
i += 1
continue
if c.isdigit() or c == '.':
m = _NUMBER_RE.match(s, i)
if not m:
raise ValueError("malformed number at position %d" % i)
text = m.group(0)
if text == '.':
raise ValueError("malformed number at position %d" % i)
tokens.append(('num', float(text)))
i = m.end()
continue
raise ValueError("unknown token %r at position %d" % (c, i))
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 tokens")
return value
def _expr(self):
# handles + and -
value = self._term()
while True:
kind, _ = self._peek()
if kind == '+':
self._advance()
value = value + self._term()
elif kind == '-':
self._advance()
value = value - self._term()
else:
break
return value
def _term(self):
# handles * and /
value = self._factor()
while True:
kind, _ = self._peek()
if kind == '*':
self._advance()
value = value * self._factor()
elif kind == '/':
self._advance()
divisor = self._factor()
if divisor == 0:
raise ValueError("division by zero")
value = value / divisor
else:
break
return value
def _factor(self):
# handles unary + and -
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 == 'num':
self._advance()
return value
if kind == '(':
self._advance()
inner = self._expr()
close_kind, _ = self._peek()
if close_kind != ')':
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 _normalize(value):
if isinstance(value, float):
if value != value or value in (float('inf'), float('-inf')):
return value
if value.is_integer():
return int(value)
return value
return value