chapter 2.1 calculator project setup

This commit is contained in:
2025-07-31 16:31:52 +02:00
parent f9a9c430e8
commit 3fb2ef23bf
4 changed files with 155 additions and 0 deletions

22
calculator/main.py Normal file
View File

@@ -0,0 +1,22 @@
import sys
from pkg.calculator import Calculator
from pkg.render import render
def main():
calculator = Calculator()
if len(sys.argv) <= 1:
print("Calculator App")
print("Usage: python main.py \"<expression>\"")
print("Example: python main.py \"3 + 5\"")
return
expression = " ".join(sys.argv[1:])
try:
result = calculator.evaluate(expression)
to_print = render(expression, result)
print(to_print)
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,61 @@
# calculator.py
class Calculator:
def __init__(self):
self.operators = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b,
}
self.precedence = {
"+": 1,
"-": 1,
"*": 2,
"/": 2,
}
def evaluate(self, expression):
if not expression or expression.isspace():
return None
tokens = expression.strip().split()
return self._evaluate_infix(tokens)
def _evaluate_infix(self, tokens):
values = []
operators = []
for token in tokens:
if token in self.operators:
while (
operators
and operators[-1] in self.operators
and self.precedence[operators[-1]] >= self.precedence[token]
):
self._apply_operator(operators, values)
operators.append(token)
else:
try:
values.append(float(token))
except ValueError:
raise ValueError(f"invalid token: {token}")
while operators:
self._apply_operator(operators, values)
if len(values) != 1:
raise ValueError("invalid expression")
return values[0]
def _apply_operator(self, operators, values):
if not operators:
return
operator = operators.pop()
if len(values) < 2:
raise ValueError(f"not enough operands for operator {operator}")
b = values.pop()
a = values.pop()
values.append(self.operators[operator](a, b))

23
calculator/pkg/render.py Normal file
View File

@@ -0,0 +1,23 @@
# render.py
def render(expression, result):
if isinstance(result, float) and result.is_integer():
result_str = str(int(result))
else:
result_str = str(result)
box_width = max(len(expression), len(result_str)) + 4
box = []
box.append("" + "" * box_width + "")
box.append(
"" + " " * 2 + expression + " " * (box_width - len(expression) - 2) + ""
)
box.append("" + " " * box_width + "")
box.append("" + " " * 2 + "=" + " " * (box_width - 3) + "")
box.append("" + " " * box_width + "")
box.append(
"" + " " * 2 + result_str + " " * (box_width - len(result_str) - 2) + ""
)
box.append("" + "" * box_width + "")
return "\n".join(box)

49
calculator/tests.py Normal file
View File

@@ -0,0 +1,49 @@
# tests.py
import unittest
from pkg.calculator import Calculator
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calculator = Calculator()
def test_addition(self):
result = self.calculator.evaluate("3 + 5")
self.assertEqual(result, 8)
def test_subtraction(self):
result = self.calculator.evaluate("10 - 4")
self.assertEqual(result, 6)
def test_multiplication(self):
result = self.calculator.evaluate("3 * 4")
self.assertEqual(result, 12)
def test_division(self):
result = self.calculator.evaluate("10 / 2")
self.assertEqual(result, 5)
def test_nested_expression(self):
result = self.calculator.evaluate("3 * 4 + 5")
self.assertEqual(result, 17)
def test_complex_expression(self):
result = self.calculator.evaluate("2 * 3 - 8 / 2 + 5")
self.assertEqual(result, 7)
def test_empty_expression(self):
result = self.calculator.evaluate("")
self.assertIsNone(result)
def test_invalid_operator(self):
with self.assertRaises(ValueError):
self.calculator.evaluate("$ 3 5")
def test_not_enough_operands(self):
with self.assertRaises(ValueError):
self.calculator.evaluate("+ 3")
if __name__ == "__main__":
unittest.main()