"""Simple arithmetic expression calculator with a recursive-descent parser. Supported operations: +, -, *, / and parentheses. Does NOT use Python's eval(). Grammar: expression = term (('+' | '-') term)* term = factor (('*' | '/') factor)* factor = NUMBER | '(' expression ')' """ def tokenize(expression_text): """Convert an expression string into a list of tokens. Tokens are either numbers (float) or single-character operators / parentheses. Raises ValueError for characters that are not part of a valid expression. """ tokens = [] position = 0 while position < len(expression_text): character = expression_text[position] if character.isspace(): position += 1 continue if character in "+-*/()": tokens.append(character) position += 1 continue if character.isdigit() or character == ".": start = position while position < len(expression_text) and ( expression_text[position].isdigit() or expression_text[position] == "." ): position += 1 number_text = expression_text[start:position] tokens.append(float(number_text)) continue raise ValueError( f"Unexpected character '{character}' at position {position}" ) return tokens def parse_expression(tokens, position): """Parse an expression: term (('+' | '-') term)*.""" result, position = parse_term(tokens, position) while position < len(tokens) and tokens[position] in ("+", "-"): operator = tokens[position] position += 1 right_value, position = parse_term(tokens, position) if operator == "+": result += right_value else: result -= right_value return result, position def parse_term(tokens, position): """Parse a term: factor (('*' | '/') factor)*.""" result, position = parse_factor(tokens, position) while position < len(tokens) and tokens[position] in ("*", "/"): operator = tokens[position] position += 1 right_value, position = parse_factor(tokens, position) if operator == "*": result *= right_value else: if right_value == 0: raise ZeroDivisionError("Division by zero") result /= right_value return result, position def parse_factor(tokens, position): """Parse a factor: NUMBER | '(' expression ')'.""" if position >= len(tokens): raise ValueError("Unexpected end of expression") token = tokens[position] if token == "(": position += 1 result, position = parse_expression(tokens, position) if position >= len(tokens) or tokens[position] != ")": raise ValueError("Missing closing parenthesis") position += 1 return result, position if isinstance(token, float): return token, position + 1 raise ValueError(f"Unexpected token: {token}") def calculate(expression_text): """Evaluate an arithmetic expression string and return the result. Returns the numeric result or an error message string. """ if not expression_text.strip(): return "Error: empty expression" try: tokens = tokenize(expression_text) result, final_position = parse_expression(tokens, 0) if final_position != len(tokens): return f"Error: unexpected token '{tokens[final_position]}'" if result == int(result): return int(result) return round(result, 10) except (ValueError, ZeroDivisionError) as error: return f"Error: {error}" def main(): """Run the calculator on a set of test expressions.""" test_expressions = [ "3 + 5", "10 - 2 * 3", "(4 + 6) * 2", "100 / (5 * 2)", "3.5 + 2.5 * 4", "(1 + 2) * (3 + 4)", "", "10 / 0", "abc + 1", ] for expression in test_expressions: result = calculate(expression) display_expr = expression if expression else "(empty)" print(f"{display_expr} = {result}") if __name__ == "__main__": main()