CODE-0006 · grok-4.5 (low)#!/usr/bin/env python3
"""Arithmetic expression evaluator without eval."""
from typing import List, Optional, Tuple, Union
def eval_expr(s: str) -> Union[int, float]:
tokens = _tokenize(s)
if not tokens:
raise ValueError("empty expression")
value, pos = _parse_expr(tokens, 0)
if pos != len(tokens):
raise ValueError("malformed expression")
return _normalize(value)
def _normalize(x: float) -> Union[int, float]:
if isinstance(x, bool):
return x
if isinstance(x, (int, float)) and float(x).is_integer():
return int(x)
return float(x)
def _tokenize(s: str) -> List[str]:
tokens: List[str] = []
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
has_dot = False
while j < n and (s[j].isdigit() or s[j] == "."):
if s[j] == ".":
if has_dot:
raise ValueError("invalid number")
has_dot = True
j += 1
num = s[i:j]
if num == "." or num.count(".") > 1:
raise ValueError("invalid number")
if not any(ch.isdigit() for ch in num):
raise ValueError("invalid number")
tokens.append(num)
i = j
continue
raise ValueError(f"unknown token near {c!r}")
return tokens
def _parse_expr(tokens: List[str], pos: int) -> Tuple[float, int]:
left, pos = _parse_term(tokens, pos)
while pos < len(tokens) and tokens[pos] in ("+", "-"):
op = tokens[pos]
pos += 1
right, pos = _parse_term(tokens, pos)
if op == "+":
left = left + right
else:
left = left - right
return left, pos
def _parse_term(tokens: List[str], pos: int) -> Tuple[float, int]:
left, pos = _parse_unary(tokens, pos)
while pos < len(tokens) and tokens[pos] in ("*", "/"):
op = tokens[pos]
pos += 1
right, pos = _parse_unary(tokens, pos)
if op == "*":
left = left * right
else:
if right == 0:
raise ValueError("division by zero")
left = left / right
return left, pos
def _parse_unary(tokens: List[str], pos: int) -> Tuple[float, int]:
if pos >= len(tokens):
raise ValueError("unexpected end of expression")
if tokens[pos] == "+":
return _parse_unary(tokens, pos + 1)
if tokens[pos] == "-":
val, pos = _parse_unary(tokens, pos + 1)
return -val, pos
return _parse_primary(tokens, pos)
def _parse_primary(tokens: List[str], pos: int) -> Tuple[float, int]:
if pos >= len(tokens):
raise ValueError("unexpected end of expression")
t = tokens[pos]
if t == "(":
val, pos = _parse_expr(tokens, pos + 1)
if pos >= len(tokens) or tokens[pos] != ")":
raise ValueError("mismatched parentheses")
return val, pos + 1
if t in ("+", "-", "*", "/", ")"):
raise ValueError("malformed expression")
# number
try:
if "." in t:
val = float(t)
else:
val = float(int(t))
except ValueError:
raise ValueError("invalid number") from None
return val, pos + 1