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)")}')