CODE-0006 · gpt-5.4-mini (high)
from fractions import Fraction


def eval_expr(s):
    def parse_number(num_str):
        if num_str.count(".") > 1:
            raise ValueError
        if "." not in num_str:
            return Fraction(int(num_str))
        left, right = num_str.split(".", 1)
        if left == "" and right == "":
            raise ValueError
        if left == "":
            left = "0"
        if right == "":
            return Fraction(int(left))
        return Fraction(int(left + right), 10 ** len(right))

    def tokenize(text):
        tokens = []
        i = 0
        n = len(text)
        while i < n:
            ch = text[i]
            if ch.isspace():
                i += 1
                continue
            if ch in "+-*/()":
                tokens.append((ch, None))
                i += 1
                continue
            if ch.isdigit() or ch == ".":
                start = i
                if ch == ".":
                    i += 1
                    if i >= n or not text[i].isdigit():
                        raise ValueError
                saw_dot = ch == "."
                while i < n:
                    ch = text[i]
                    if ch.isdigit():
                        i += 1
                    elif ch == ".":
                        if saw_dot:
                            break
                        saw_dot = True
                        i += 1
                    else:
                        break
                tokens.append(("NUMBER", parse_number(text[start:i])))
                continue
            raise ValueError
        return tokens

    values = []
    ops = []

    def precedence(op):
        if op in ("u+", "u-"):
            return 3
        if op in ("*", "/"):
            return 2
        if op in ("+", "-"):
            return 1
        return -1

    def is_left_assoc(op):
        return op in ("+", "-", "*", "/")

    def should_pop(top, current):
        if top == "(":
            return False
        pt = precedence(top)
        pc = precedence(current)
        if pt > pc:
            return True
        if pt < pc:
            return False
        return is_left_assoc(current)

    def apply_op(op):
        if op == "u+" or op == "u-":
            if not values:
                raise ValueError
            a = values.pop()
            values.append(a if op == "u+" else -a)
            return
        if len(values) < 2:
            raise ValueError
        b = values.pop()
        a = values.pop()
        if op == "+":
            values.append(a + b)
        elif op == "-":
            values.append(a - b)
        elif op == "*":
            values.append(a * b)
        elif op == "/":
            if b == 0:
                raise ValueError
            values.append(a / b)
        else:
            raise ValueError

    tokens = tokenize(s)
    expect_operand = True

    for kind, val in tokens:
        if kind == "NUMBER":
            if not expect_operand:
                raise ValueError
            values.append(val)
            expect_operand = False
        elif kind == "(":
            if not expect_operand:
                raise ValueError
            ops.append(kind)
            expect_operand = True
        elif kind == ")":
            if expect_operand:
                raise ValueError
            while ops and ops[-1] != "(":
                apply_op(ops.pop())
            if not ops:
                raise ValueError
            ops.pop()
            expect_operand = False
        else:
            op = kind
            if op not in "+-*/":
                raise ValueError
            if op in "+-" and expect_operand:
                op = "u" + op
            elif expect_operand:
                raise ValueError
            while ops and ops[-1] != "(" and should_pop(ops[-1], op):
                apply_op(ops.pop())
            ops.append(op)
            expect_operand = True

    if expect_operand:
        raise ValueError

    while ops:
        op = ops.pop()
        if op == "(":
            raise ValueError
        apply_op(op)

    if len(values) != 1:
        raise ValueError

    result = values[0]
    return int(result) if result.denominator == 1 else float(result)