diff --git a/calculator/main.py b/calculator/main.py new file mode 100644 index 0000000..a34e44a --- /dev/null +++ b/calculator/main.py @@ -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 \"\"") + 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() diff --git a/calculator/pkg/calculator.py b/calculator/pkg/calculator.py new file mode 100644 index 0000000..b78c7cd --- /dev/null +++ b/calculator/pkg/calculator.py @@ -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)) diff --git a/calculator/pkg/render.py b/calculator/pkg/render.py new file mode 100644 index 0000000..1ce352b --- /dev/null +++ b/calculator/pkg/render.py @@ -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) diff --git a/calculator/tests.py b/calculator/tests.py new file mode 100644 index 0000000..2d381f0 --- /dev/null +++ b/calculator/tests.py @@ -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()