chapter 2.1 calculator project setup
This commit is contained in:
22
calculator/main.py
Normal file
22
calculator/main.py
Normal 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()
|
||||||
61
calculator/pkg/calculator.py
Normal file
61
calculator/pkg/calculator.py
Normal 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
23
calculator/pkg/render.py
Normal 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
49
calculator/tests.py
Normal 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()
|
||||||
Reference in New Issue
Block a user