import math

operators = {'+', '-', '*', '/'}


def parse(expression):
    """
    Expects an expression in the form of left-paren left-expression operator right-expression right-paren,
    where each left/right expression is either an integer or another expression.

    Expressions may have literal sub-expressions
    >>> parse('(1 + 2)')
    ('1', '+', '2')

    And an expression may have non-literal sub-expressions
    >>> parse('((1+2) - (3*4))')
    ('(1+2)', '-', '(3*4)')

    Spaces may be included in the expression
    >>> parse('(1 - (3 * 4))')
    ('1', '-', '(3*4)')

    But spaces are not required
    >>> parse('((1+2)-3)')
    ('(1+2)', '-', '3')

    The expression must be enclosed within parentheses
    >>> parse('1+2')
    Traceback (most recent call last):
      ...
    SyntaxError: Expression must be bounded by parenthesis. 1+2 does not satisfy.

    And the parentheses must be balanced
    >>> parse('((1+2)')
    Traceback (most recent call last):
      ...
    SyntaxError: Expression must have balanced parentheses. ((1+2) does not satisfy.

    Finally, the expression must have two sub-expressions separated by one an arithmetic operator from {+ - * /}
    >>> parse('(3 3)')
    Traceback (most recent call last):
      ...
    SyntaxError: Expression must have an operator (3 3) does not satisfy.
    """
    if expression == '':
        raise SyntaxError('Vacuous expressions are disallowed.')
    if expression[0] != '(' or expression[-1] != ')':
        raise SyntaxError(f'Expression must be bounded by parenthesis. {expression} does not satisfy.')
    full_expression = expression.replace(' ', '')[1:-1]
    if full_expression[0] != '(':  # the left expression is an integer
        index = math.inf
        for operator in operators:
            this_operator_index = full_expression.find(operator)
            if -1 < this_operator_index < index:
                index = this_operator_index
        if index == math.inf:
            raise SyntaxError(f'Expression must have an operator {expression} does not satisfy.')
    else:
        parentheses_count = 1
        index = 1
        while parentheses_count > 0:
            if full_expression[index] == '(':
                parentheses_count += 1
            if full_expression[index] == ')':
                parentheses_count -= 1
            index += 1
            if index == len(full_expression) and parentheses_count > 0:
                raise SyntaxError(f'Expression must have balanced parentheses. {expression} does not satisfy.')
    left_expression = full_expression[:index]
    try:
        operator = full_expression[index]
        right_expression = full_expression[index + 1:]
    except IndexError:
        raise SyntaxError(f'Non-literal expression must have two subexpressions and an operator. '
                          f'{expression} does not satisfy.')
    return left_expression, operator, right_expression


def add(left_expression, right_expression):
    return evaluate(left_expression) + evaluate(right_expression)


def subtract(left_expression, right_expression):
    return evaluate(left_expression) - evaluate(right_expression)


def multiply(left_expression, right_expression):
    return evaluate(left_expression) * evaluate(right_expression)


def divide(left_expression, right_expression):
    return evaluate(left_expression) / evaluate(right_expression)


operator_map = {
    '+': add,
    '-': subtract,
    '*': multiply,
    '/': divide
}


def evaluate(expression):
    if expression.isnumeric():
        return int(expression)
    else:
        left_expression, operator, right_expression = parse(expression)
        return operator_map[operator](left_expression, right_expression)


if __name__ == '__main__':
    print(f'(1 + 2) = {evaluate("(1 + 2)")}')
    print(f'((1+2) - (3*4)) = {evaluate("((1+2) - (3*4))")}')
    print(f'(1 - (3 * 4)) = {evaluate("(1 - (3 * 4))")}')
    print(f'((1+2)-3) = {evaluate("((1+2)-3)")}')