From 42ed25b9b3867e5cfb47164a4c3c62ed776f29c7 Mon Sep 17 00:00:00 2001
From: Florentin Labelle <florentin.labelle@student-cs.fr>
Date: Sun, 9 Oct 2022 17:11:58 +0200
Subject: [PATCH] Add calculator with perfect lint score

---
 calculator/Operators.py                     | 46 ---------------------
 calculator/__init__.py                      |  0
 calculator/{Calculator.py => calculator.py} | 35 ++++++++++------
 calculator/{Expression.py => expression.py} | 11 ++++-
 calculator/operators.py                     | 24 +++++++++++
 calculator/requirements.txt                 |  2 +
 calculator/server.py                        | 16 ++++---
 7 files changed, 69 insertions(+), 65 deletions(-)
 delete mode 100644 calculator/Operators.py
 create mode 100644 calculator/__init__.py
 rename calculator/{Calculator.py => calculator.py} (57%)
 rename calculator/{Expression.py => expression.py} (69%)
 create mode 100644 calculator/operators.py

diff --git a/calculator/Operators.py b/calculator/Operators.py
deleted file mode 100644
index fdd2090..0000000
--- a/calculator/Operators.py
+++ /dev/null
@@ -1,46 +0,0 @@
-class Operator:
-    def __init__(self, symbol, precedence):
-        self.symbol = symbol
-        self.precedence = precedence
-
-    def __repr__(self):
-        return self.symbol
-
-    def __call__(self):
-        raise NotImplemented
-
-class Adder(Operator):
-    def __init__(self):
-        super().__init__('+', 1)
-
-    def __call__(self, a, b):
-        return a + b
-
-class Subtracter(Operator):
-    def __init__(self):
-        super().__init__('-', 1)
-
-    def __call__(self, a, b):
-        return a - b
-
-class Multiplyer(Operator):
-    def __init__(self):
-        super().__init__('*', 2)
-
-    def __call__(self, a, b):
-        return a * b
-
-class Divider(Operator):
-    def __init__(self):
-        super().__init__('/', 2)
-
-    def __call__(self, a, b):
-        return a / b
-
-
-STANDARD_OPERATORS = {
-    '+': Adder(),
-    '-': Subtracter(),
-    '*': Multiplyer(),
-    '/': Divider(),
-}
diff --git a/calculator/__init__.py b/calculator/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/calculator/Calculator.py b/calculator/calculator.py
similarity index 57%
rename from calculator/Calculator.py
rename to calculator/calculator.py
index f69d723..ef0da93 100644
--- a/calculator/Calculator.py
+++ b/calculator/calculator.py
@@ -1,18 +1,24 @@
 """
-This Calculator module is a simple calculator that can parse and evaluate expressions of the form:
-expression ::= term | expression operator expression
-operator ::= + | - | * | /
-with the usual precedence rules.
+This Calculator holds the logic for the calculator.
 """
 
-from Operators import Operator, STANDARD_OPERATORS
-from Expression import Token, Term, Expression, TermExpression, OperatorExpression
+from calculator.operators import Operator, STANDARD_OPERATORS
+from calculator.expression import Token, Term, Expression, TermExpression, OperatorExpression
+
 
 class Calculator:
-    def __init__(self, operators = STANDARD_OPERATORS):
+    """
+    Calculator class is a simple calculator that can parse and evaluate expressions of the form:
+    expression ::= term | expression operator expression
+    operator ::= + | - | * | /
+    with the usual precedence rules.
+    """
+    def __init__(self, operators=None):
+        if operators is None:
+            operators = STANDARD_OPERATORS
         self.operators = operators
 
-    def _tokenize(self, line: str) -> list[Token]:
+    def tokenize(self, line: str) -> list[Token]:
         """
         Tokenize an expression into a list of tokens.
         """
@@ -24,11 +30,14 @@ class Calculator:
                 try:
                     term = float(token)
                     tokens.append(term)
-                except ValueError:
-                    raise ValueError(f"Unknown token: {token}")
+                except ValueError as exc:
+                    raise ValueError(f"Invalid token {token}") from exc
         return tokens
 
-    def _parse(self, tokens: list[Token]) -> Expression:
+    def parse(self, tokens: list[Token]) -> Expression:
+        """
+        Parse a list of tokens into an ordered expression.
+        """
         if not tokens:
             raise ValueError("Empty expression")
         if len(tokens) == 1:
@@ -51,7 +60,7 @@ class Calculator:
         right = tokens[operator_index + 1:]
 
         # Parse the left and right parts recursively
-        return OperatorExpression(operator, self._parse(left), self._parse(right))
+        return OperatorExpression(operator, self.parse(left), self.parse(right))
 
     def __call__(self, expression: str) -> Term:
-        return self._parse(self._tokenize(expression))()
+        return self.parse(self.tokenize(expression))()
diff --git a/calculator/Expression.py b/calculator/expression.py
similarity index 69%
rename from calculator/Expression.py
rename to calculator/expression.py
index d19ea88..c461c95 100644
--- a/calculator/Expression.py
+++ b/calculator/expression.py
@@ -1,11 +1,17 @@
+"""
+Expression module defines the structure of an expression.
+"""
 from typing import Union
-from Operators import Operator
+from calculator.operators import Operator
 
 Term: type = float
 Token: type = Union[Operator, Term]
 
 
 class OperatorExpression:
+    """
+    OperatorExpression class is an expression that contains an operator and two sub-expressions.
+    """
     def __init__(self, operator: Operator, left, right):
         self.operator = operator
         self.left = left
@@ -19,6 +25,9 @@ class OperatorExpression:
 
 
 class TermExpression:
+    """
+    TermExpression class is an expression that contains a single term.
+    """
     def __init__(self, value: Term):
         self.value = value
 
diff --git a/calculator/operators.py b/calculator/operators.py
new file mode 100644
index 0000000..a353a01
--- /dev/null
+++ b/calculator/operators.py
@@ -0,0 +1,24 @@
+"""
+Operator module contains the Operator class and a list of standard operators.
+"""
+class Operator:
+    """
+    Operator class is a binary operator with a symbol, a precedence and an evaluation function.
+    """
+    def __init__(self, symbol, precedence, evaluate_function):
+        self.symbol = symbol
+        self.precedence = precedence
+        self.evaluate_function = evaluate_function
+
+    def __repr__(self):
+        return self.symbol
+
+    def __call__(self, left, right):
+        return self.evaluate_function(left, right)
+
+STANDARD_OPERATORS = {
+    '+': Operator('+', 1, lambda a, b: a + b),
+    '-': Operator('-', 1, lambda a, b: a - b),
+    '*': Operator('×', 2, lambda a, b: a * b),
+    '/': Operator('/', 2, lambda a, b: a / b),
+}
diff --git a/calculator/requirements.txt b/calculator/requirements.txt
index 9c7788a..21bed0c 100644
--- a/calculator/requirements.txt
+++ b/calculator/requirements.txt
@@ -1,2 +1,4 @@
 fastapi[all]
 uvicorn[standard]
+pylint
+pytest
diff --git a/calculator/server.py b/calculator/server.py
index 8f5fed6..7f34df5 100644
--- a/calculator/server.py
+++ b/calculator/server.py
@@ -1,19 +1,25 @@
+"""
+Server module for the web calculator.
+"""
 from fastapi import FastAPI
 from fastapi.requests import Request
 from fastapi.templating import Jinja2Templates
-from Calculator import Calculator
+from calculator.calculator import Calculator
 
 app = FastAPI()
 templates = Jinja2Templates(directory="templates")
 calc = Calculator()
 
-@app.get("/icon")
-
 @app.get("/")
 async def root(request: Request):
+    """
+    Default and only route of the web calculator.
+    Expects either no query parameters or a single query parameter named "expression".
+    """
     expression = request.query_params.get("expression", "")
-    context = { "request": request }
+    context = {"request": request}
     if expression:
         result = calc(expression)
-        context = { "request": request, "expression": expression, "result": result}
+        context = {"request": request,
+                   "expression": expression, "result": result}
     return templates.TemplateResponse("index.html", context)
-- 
GitLab