diff --git a/calculator/Operators.py b/calculator/Operators.py deleted file mode 100644 index fdd2090d31600b428cabcbd97622e1dc6e1d7cb1..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/calculator/Calculator.py b/calculator/calculator.py similarity index 57% rename from calculator/Calculator.py rename to calculator/calculator.py index f69d7238f0f2b3a48b51b380f256ffede8fb70dc..ef0da93e221160bb9ecee49d1d917dc1bca739b2 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 d19ea88ffd75c185ae706239408735ac488f9f79..c461c95f688b652fe5fe2d3fe357c10ac1ab2350 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 0000000000000000000000000000000000000000..a353a0108b7235105d0287dfec68c5d8d7c37910 --- /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 9c7788af16fa05ef099b6c72c09d5702edf5712c..21bed0c340d06b80af1992f45528a1d5b5163fe4 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 8f5fed6b46f404572e5e3dfdad0b628f8add9db8..7f34df5b5e367ce1bb52be74cc2fac7c881ef3b7 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)