CODE-0006 · glm-5.2 (default)
def eval_expr(s):
    tokens = _tokenize(s)
    if not tokens:
        raise ValueError("empty input")
    parser = _Parser(tokens)
    result = parser.parse()
    if parser.pos != len(parser.tokens):
        raise ValueError("unexpected trailing tokens")
    if isinstance(result, float) and result.is_integer():
        return int(result)
    return result


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)
            i += 1
            continue
        if c.isdigit() or c == '.':
            start = i
            seen_dot = False
            seen_digit = False
            while i < n and (s[i].isdigit() or s[i] == '.'):
                if s[i] == '.':
                    if seen_dot:
                        raise ValueError("malformed number")
                    seen_dot = True
                else:
                    seen_digit = True
                i += 1
            if not seen_digit:
                raise ValueError("malformed number")
            tokens.append(s[start:i])
            continue
        raise ValueError("unknown token: %r" % c)
    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

    def advance(self):
        tok = self.tokens[self.pos]
        self.pos += 1
        return tok

    def parse(self):
        return self.parse_expr()

    def parse_expr(self):
        left = self.parse_term()
        while self.peek() in ('+', '-'):
            op = self.advance()
            right = self.parse_term()
            if op == '+':
                left = left + right
            else:
                left = left - right
        return left

    def parse_term(self):
        left = self.parse_factor()
        while self.peek() in ('*', '/'):
            op = self.advance()
            right = self.parse_factor()
            if op == '*':
                left = left * right
            else:
                if right == 0:
                    raise ValueError("division by zero")
                left = left / right
        return left

    def parse_factor(self):
        tok = self.peek()
        if tok == '+':
            self.advance()
            return self.parse_factor()
        if tok == '-':
            self.advance()
            return -self.parse_factor()
        if tok == '(':
            self.advance()
            result = self.parse_expr()
            if self.peek() != ')':
                raise ValueError("mismatched parentheses")
            self.advance()
            return result
        if tok is None:
            raise ValueError("unexpected end of input")
        if tok in ('+', '-', '*', '/', ')'):
            raise ValueError("dangling operator")
        self.advance()
        if '.' in tok:
            return float(tok)
        return int(tok)