154 lines
4.2 KiB
Python
154 lines
4.2 KiB
Python
"""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()
|