CODE-0006 · claude-haiku-4-5 (default)
def eval_expr(s):
    """Evaluate an arithmetic expression given as a string."""
    tokens = tokenize(s)
    if not tokens:
        raise ValueError("Empty expression")
    parser = Parser(tokens)
    result = parser.parse_expression()
    if parser.pos < len(tokens):
        raise ValueError("Unexpected token after expression")
    
    # Convert to int if it's a whole number
    if isinstance(result, float) and result == int(result):
        return int(result)
    return result


def tokenize(s):
    """Tokenize the input string into a list of tokens."""
    tokens = []
    i = 0
    while i < len(s):
        # Skip whitespace
        if s[i].isspace():
            i += 1
            continue
        
        # Check for operators and parentheses
        if s[i] in '+-*/()':
            tokens.append(s[i])
            i += 1
        # Check for numbers
        elif s[i].isdigit() or (s[i] == '.' and i + 1 < len(s) and s[i + 1].isdigit()):
            j = i
            has_dot = False
            while j < len(s) and (s[j].isdigit() or (s[j] == '.' and not has_dot)):
                if s[j] == '.':
                    has_dot = True
                j += 1
            tokens.append(s[i:j])
            i = j
        else:
            raise ValueError(f"Unknown token: {s[i]}")
    
    return tokens


class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.pos = 0
    
    def parse_expression(self):
        """Parse addition and subtraction (lowest precedence)."""
        left = self.parse_term()
        
        while self.pos < len(self.tokens) and self.tokens[self.pos] in '+-':
            op = self.tokens[self.pos]
            self.pos += 1
            right = self.parse_term()
            if op == '+':
                left = left + right
            else:
                left = left - right
        
        return left
    
    def parse_term(self):
        """Parse multiplication and division (higher precedence)."""
        left = self.parse_unary()
        
        while self.pos < len(self.tokens) and self.tokens[self.pos] in '*/':
            op = self.tokens[self.pos]
            self.pos += 1
            right = self.parse_unary()
            if op == '*':
                left = left * right
            else:
                if right == 0:
                    raise ValueError("Division by zero")
                left = left / right
        
        return left
    
    def parse_unary(self):
        """Parse unary operators and primary expressions."""
        if self.pos < len(self.tokens) and self.tokens[self.pos] in '+-':
            op = self.tokens[self.pos]
            self.pos += 1
            value = self.parse_unary()
            if op == '-':
                return -value
            else:
                return value
        
        return self.parse_primary()
    
    def parse_primary(self):
        """Parse primary expressions (numbers and parenthesized expressions)."""
        if self.pos >= len(self.tokens):
            raise ValueError("Unexpected end of expression")
        
        token = self.tokens[self.pos]
        
        # Handle parenthesized expressions
        if token == '(':
            self.pos += 1
            result = self.parse_expression()
            if self.pos >= len(self.tokens) or self.tokens[self.pos] != ')':
                raise ValueError("Mismatched parentheses")
            self.pos += 1
            return result
        
        # Handle numbers
        if token[0].isdigit() or (token[0] == '.' and len(token) > 1):
            self.pos += 1
            try:
                if '.' in token:
                    return float(token)
                else:
                    return int(token)
            except ValueError:
                raise ValueError(f"Invalid number: {token}")
        
        # Handle closing parenthesis or operator as error
        if token in ')+-*/':
            raise ValueError(f"Unexpected token: {token}")
        
        raise ValueError(f"Unexpected token: {token}")