From 8f5c7411212d492d6844ea29e0ec7d9482279fd5 Mon Sep 17 00:00:00 2001 From: Aditya Alok Date: Sun, 3 Oct 2021 23:21:18 +0530 Subject: [PATCH 1/5] Refactor: make it ready for school level project --- src/sylvie/__init__.py | 113 ------------------ src/sylvie/__main__.py | 40 ------- src/sylvie/ast_based/ast.py | 2 - src/sylvie/ast_based/nodes.py | 21 ---- sylvie/__init__.py | 21 ++++ sylvie/__main__.py | 41 +++++++ sylvie/bin.py | 15 +++ .../interpreter}/lexer.py | 21 ++-- .../interpreter}/parser.py | 27 +++-- {src/sylvie => sylvie/interpreter}/tokens.py | 0 10 files changed, 108 insertions(+), 193 deletions(-) delete mode 100644 src/sylvie/__init__.py delete mode 100644 src/sylvie/__main__.py delete mode 100644 src/sylvie/ast_based/ast.py delete mode 100644 src/sylvie/ast_based/nodes.py create mode 100644 sylvie/__init__.py create mode 100644 sylvie/__main__.py create mode 100644 sylvie/bin.py rename {src/sylvie/syntax_directed => sylvie/interpreter}/lexer.py (83%) rename {src/sylvie/syntax_directed => sylvie/interpreter}/parser.py (89%) rename {src/sylvie => sylvie/interpreter}/tokens.py (100%) diff --git a/src/sylvie/__init__.py b/src/sylvie/__init__.py deleted file mode 100644 index aa196a5..0000000 --- a/src/sylvie/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Sylvie: the maths interpreter. - -It is a mathematical interpreter. It takes mathematical expressions -as text and then evaluate it. - -Examples: - As REPL: - - >> 4*8(93-2/4*3)-9 - -8.650273224043715 - - >> 8/4 - 2.0 - - >> 5*5 - 25 - - As module: - - calc = Sylvie() - print(calc.parse("4*8")) -""" - -from typing import Union - - -class Sylvie: - """Evaluates the given mathematical expressions. - - Attributes: - interpreter_type: Optional; An integer value representing - type of interpreter to be used. - """ - - def __init__(self, interpreter_type: int = 0) -> None: - """Initializes Sylvie's interpreter type. - - Args: - interpreter_type: Optional; An integer representing interpreter - type to be used. 1 for abstract-syntax-tree based while - 0 for syntax-directed. (Default 0) - """ - self.interpreter_type = interpreter_type - - @property - def interpreter_type(self): - """Returns interpreter type.""" - return self._interpreter_type - - @interpreter_type.setter - def interpreter_type(self, value: int): - """Set interpreter type. - - Args: - value: 0 for syntax-directed interpreter or - 1 for abstract-syntax-tree based interpreter. - """ - self._interpreter_type: int = value - - def parse(self, text: str) -> Union[int, float]: - """Parses the given text as mathematical expressions. - - Args: - text : mathematical expressions to be evaluated. - - Returns: - Result of expressions as integer or float. - """ - if self.interpreter_type == 0: - # pylint: disable=C0415 - from sylvie.syntax_directed.parser import Parser - else: - # from sylvie.ast_based.parser import Parser - raise NotImplementedError( - "Parser for ast based interpreter has not been implemented yet." - ) - - return Parser(text=text).expr() - - -def _main(): # skipcq: PY-D0003 - import argparse # pylint: disable=C0415 - - parser = argparse.ArgumentParser(description="Sylvie's math interpreter") - parser.add_argument( - "-i", - "--interpreter-type", - nargs=1, - default=0, - type=int, - help=( - "Change interpreter type." - " Pass 1 for ast based or 0 (default) for syntax directed." - ), - ) - s = Sylvie(parser.parse_args().interpreter_type) # pylint: disable=C0103 - - def evaluate(): # skipcq: PY-D0003 - try: - text = input(">> ") - print(s.parse(text=text)) - except (ValueError, SyntaxError) as err: - print("Error :", err) - - try: - while True: - evaluate() - except EOFError: - print("Exiting as requested.") - - -if __name__ == "__main__": - _main() diff --git a/src/sylvie/__main__.py b/src/sylvie/__main__.py deleted file mode 100644 index 0eb1e71..0000000 --- a/src/sylvie/__main__.py +++ /dev/null @@ -1,40 +0,0 @@ -"""This module allows using package from command line. - -This allows `python -m sylvie` command as well as `python sylvie` -to start REPL. -""" - -try: - from sylvie import _main -except ImportError: - import sys - from pathlib import Path - - # if it was run from directory other than src/ then directory - # src/ would not be present in sys.path, from where imports start. - # Thus, add src/ to sys.path and remove current file directory(i.e sylvie/). - # when __file__ is not defined then use parent of '.' (Path.cwd()) - # Note: it would still not work if current directory is not sylvie/. - # There is nothing I could think of to solve this issue. - # - # Although it's highly unlikley for __file__ to be not defined. - script_dir = Path(globals().get("__file__", "./_")).absolute().parent - - # sys.path may contain a trailing '/' depending on the the command used - # to invoke. - # For example: - # - when using `python sylvie`, sys.path will not contain trailing '/' - # in search path of this file. - # - # - but when `python sylvie/` is used, it will contain trailing '/' - try: - sys.path.remove(str(script_dir)) - except ValueError: - sys.path.remove(f"{script_dir}/") - - # insert parent of script_dir (sylvie/), i.e src/ to sys.path - sys.path.insert(0, str(script_dir.parent)) - - from sylvie import _main - -_main() diff --git a/src/sylvie/ast_based/ast.py b/src/sylvie/ast_based/ast.py deleted file mode 100644 index 1f05e3e..0000000 --- a/src/sylvie/ast_based/ast.py +++ /dev/null @@ -1,2 +0,0 @@ -class AST: - pass diff --git a/src/sylvie/ast_based/nodes.py b/src/sylvie/ast_based/nodes.py deleted file mode 100644 index 37e65c6..0000000 --- a/src/sylvie/ast_based/nodes.py +++ /dev/null @@ -1,21 +0,0 @@ -from dataclasses import dataclass, field -from typing import Union - -from sylvie.ast_based.ast import AST -from sylvie.tokens import Token - - -@dataclass -class BinOp(AST): - right_child: Token - op: Token - left_child: Token - - -@dataclass -class Num(AST): - token: Token - value: Union[int, float] = field(init=False) - - def __post_init__(self): - self.value = self.token.value diff --git a/sylvie/__init__.py b/sylvie/__init__.py new file mode 100644 index 0000000..8b4a65d --- /dev/null +++ b/sylvie/__init__.py @@ -0,0 +1,21 @@ +"""Sylvie is a mathematical interpreter. +It takes mathematical expressions as text and then evaluate it. + +Examples: + From command line: + + Sylvie>> 4*8(93-2/4*3)-9 + -8.650273224043715 + + Sylvie>> 8/4 + 2.0 + + Sylvie>> 5*5 + 25 +""" + + +if __name__ == "__main__": + from sylvie.bin import cmd + + cmd() diff --git a/sylvie/__main__.py b/sylvie/__main__.py new file mode 100644 index 0000000..d66e8f7 --- /dev/null +++ b/sylvie/__main__.py @@ -0,0 +1,41 @@ +"""This module allows using package from command line. + +This allows `python -m sylvie` command as well as `python sylvie` +to start REPL. +""" + +import sys +from pathlib import Path + +# if it was run from directory other than sylvie then directory +# sylvie/ would not be present in sys.path, from where imports start. +# Thus, add sylvie/ to sys.path and remove current file directory(i.e sylvie/). +# when __file__ is not defined then use parent of '.' (Path.cwd()) +# Note: it would still not work if current directory is not sylvie/. +# There is nothing I could think of to solve this issue. +# +# Although it's highly unlikley for __file__ to be not defined. +script_dir = Path(globals().get("__file__", "./_")).absolute().parent + +if str(script_dir.parent) not in sys.path: + + # remove current path, although it is not necessary. + # + # sys.path may contain a trailing '/' depending on the the command used + # to invoke. + # For example: + # - when using `python sylvie`, sys.path will not contain trailing '/' + # in search path of this file. + # + # - but when `python sylvie/` is used, it will contain trailing '/' + try: + sys.path.remove(str(script_dir)) + except ValueError: + sys.path.remove(f"{script_dir}/") + + # insert parent of script_dir (sylvie/), i.e src/ to sys.path + sys.path.insert(0, str(script_dir.parent)) + +from sylvie.bin import cmd + +cmd() diff --git a/sylvie/bin.py b/sylvie/bin.py new file mode 100644 index 0000000..593cc5c --- /dev/null +++ b/sylvie/bin.py @@ -0,0 +1,15 @@ +from typing import Union +from sylvie.interpreter.parser import Parser + + +def cmd(): + parser = Parser() + try: + while True: + try: + text = input("Sylvie >> ") + print(parser.parse(text)) + except (ValueError, SyntaxError, ZeroDivisionError) as err: + print("Error :", err) + except (EOFError, KeyboardInterrupt): + print("Exiting as requested.") diff --git a/src/sylvie/syntax_directed/lexer.py b/sylvie/interpreter/lexer.py similarity index 83% rename from src/sylvie/syntax_directed/lexer.py rename to sylvie/interpreter/lexer.py index 4126600..b34ee2c 100644 --- a/src/sylvie/syntax_directed/lexer.py +++ b/sylvie/interpreter/lexer.py @@ -6,7 +6,7 @@ from typing import Any, Generator, Union -from sylvie.tokens import Token, TokenType +from sylvie.interpreter.tokens import Token, TokenType class Lexer: @@ -16,7 +16,12 @@ class Lexer: text: input mathematical expression """ - def __init__(self, text: str) -> None: + def __init__(self) -> None: + """Initialize lexer.""" + + pass + + def analyze(self, text: str) -> Generator: """Converts input text into a stream of tokens. Args: @@ -25,6 +30,8 @@ def __init__(self, text: str) -> None: self.text = iter(text) self.advance() + return self.tokens() + def advance(self) -> None: """Move to next character.""" try: @@ -34,9 +41,9 @@ def advance(self) -> None: @staticmethod def error(msg: str): # skipcq: PY-D0003 - raise ValueError(f"ValueError: {msg}") + raise ValueError(msg) - def get_tokens(self) -> Generator: + def tokens(self) -> Generator: """Yields tokens generated by the lexer.""" while self.current_char: if self.current_char.isspace(): @@ -50,9 +57,9 @@ def get_tokens(self) -> Generator: # TokenType('(') --> TokenType.LPAREN token_type = TokenType(self.current_char) # skipcq: PYL-W0511 - except ValueError: # TODO: define custom exceptions - # if self.current_char not member of TokenType - self.error(f"Invalid character '{self.current_char}'") + except ValueError: + # if self.current_char not member of TokenType raise error + self.error(f"invalid character '{self.current_char}'") else: token = Token(token_type, token_type.value) diff --git a/src/sylvie/syntax_directed/parser.py b/sylvie/interpreter/parser.py similarity index 89% rename from src/sylvie/syntax_directed/parser.py rename to sylvie/interpreter/parser.py index d45524a..7732cc7 100644 --- a/src/sylvie/syntax_directed/parser.py +++ b/sylvie/interpreter/parser.py @@ -13,8 +13,8 @@ from typing import Union -from sylvie.syntax_directed.lexer import Lexer -from sylvie.tokens import TokenType +from sylvie.interpreter.lexer import Lexer +from sylvie.interpreter.tokens import TokenType class Parser: @@ -27,16 +27,24 @@ class Parser: expr: returns result of input expression. """ - def __init__(self, text: str) -> None: + def __init__(self) -> None: + """Initialize parser.""" + + self.lexer = Lexer() + + def parse(self, text: str) -> Union[int, float]: """Parses tokens and evaluates the expression formed. Args: text: text to be parsed. """ - self.tokens = Lexer(text).get_tokens() + + self.tokens = self.lexer.analyze(text) self.column = 0 self.advance() + return self.expr() + def advance(self) -> None: """Move to next token and increase column count.""" try: @@ -64,11 +72,11 @@ def eat(self, expected_token: TokenType) -> None: if self.current_token.type == expected_token: self.advance() else: - self.error( - "Unexpected character '{}' at column {}, expected '{}'".format( - self.current_token.value, self.column, expected_token.value - ) - ) + msg = f"character '{self.current_token.value}'" + if self.current_token.type == TokenType.EOF: + msg = "end of expression" + + self.error(f"unexpected {msg} at column {self.column}") def factor(self) -> Union[int, float]: """Extracts NUMBER or PAREN terminal. @@ -117,7 +125,6 @@ def mul(self) -> Union[int, float]: try: result /= self.term() except ZeroDivisionError: - print("Oh! Its beyond my limit") raise return result diff --git a/src/sylvie/tokens.py b/sylvie/interpreter/tokens.py similarity index 100% rename from src/sylvie/tokens.py rename to sylvie/interpreter/tokens.py From 2e267a34faac0c24a122e57c2428da77e6b9364c Mon Sep 17 00:00:00 2001 From: Aditya Alok Date: Sun, 3 Oct 2021 23:38:12 +0530 Subject: [PATCH 2/5] Fix: formatting and anti-pattern issues --- sylvie/__init__.py | 2 +- sylvie/bin.py | 4 ++++ sylvie/interpreter/lexer.py | 5 +++-- sylvie/interpreter/parser.py | 8 ++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/sylvie/__init__.py b/sylvie/__init__.py index 8b4a65d..a10679a 100644 --- a/sylvie/__init__.py +++ b/sylvie/__init__.py @@ -1,4 +1,4 @@ -"""Sylvie is a mathematical interpreter. +"""Sylvie is a mathematical interpreter. It takes mathematical expressions as text and then evaluate it. Examples: diff --git a/sylvie/bin.py b/sylvie/bin.py index 593cc5c..5cffa12 100644 --- a/sylvie/bin.py +++ b/sylvie/bin.py @@ -1,8 +1,12 @@ +"""This module contains functioncs to run Sylvie from command line.""" + from typing import Union from sylvie.interpreter.parser import Parser def cmd(): + """Starts Sylvie's REPL.""" + parser = Parser() try: while True: diff --git a/sylvie/interpreter/lexer.py b/sylvie/interpreter/lexer.py index b34ee2c..bb8310e 100644 --- a/sylvie/interpreter/lexer.py +++ b/sylvie/interpreter/lexer.py @@ -19,7 +19,8 @@ class Lexer: def __init__(self) -> None: """Initialize lexer.""" - pass + self.text = None + self.current_char: Union[str, Any] = None def analyze(self, text: str) -> Generator: """Converts input text into a stream of tokens. @@ -35,7 +36,7 @@ def analyze(self, text: str) -> Generator: def advance(self) -> None: """Move to next character.""" try: - self.current_char: Union[str, Any] = next(self.text) + self.current_char = next(self.text) except StopIteration: self.current_char = "EOF" if self.current_char != "EOF" else None diff --git a/sylvie/interpreter/parser.py b/sylvie/interpreter/parser.py index 7732cc7..33283f7 100644 --- a/sylvie/interpreter/parser.py +++ b/sylvie/interpreter/parser.py @@ -31,6 +31,9 @@ def __init__(self) -> None: """Initialize parser.""" self.lexer = Lexer() + self.tokens = None + self.column = 0 + self.current_token = None def parse(self, text: str) -> Union[int, float]: """Parses tokens and evaluates the expression formed. @@ -122,10 +125,7 @@ def mul(self) -> Union[int, float]: result *= self.term() elif self.current_token.type == TokenType.DIV: self.eat(TokenType.DIV) - try: - result /= self.term() - except ZeroDivisionError: - raise + result /= self.term() return result def sub_expr(self) -> Union[int, float]: From e9a2cdcb1af5621ca6820203b1d919d83da98cb2 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sun, 3 Oct 2021 18:13:58 +0000 Subject: [PATCH 3/5] Autofix issues in 3 files Resolved issues in the following files via DeepSource Autofix: 1. sylvie/bin.py 2. sylvie/interpreter/lexer.py 3. sylvie/interpreter/parser.py --- sylvie/bin.py | 1 - sylvie/interpreter/lexer.py | 1 - sylvie/interpreter/parser.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/sylvie/bin.py b/sylvie/bin.py index 5cffa12..1787908 100644 --- a/sylvie/bin.py +++ b/sylvie/bin.py @@ -6,7 +6,6 @@ def cmd(): """Starts Sylvie's REPL.""" - parser = Parser() try: while True: diff --git a/sylvie/interpreter/lexer.py b/sylvie/interpreter/lexer.py index bb8310e..1e4dfb0 100644 --- a/sylvie/interpreter/lexer.py +++ b/sylvie/interpreter/lexer.py @@ -18,7 +18,6 @@ class Lexer: def __init__(self) -> None: """Initialize lexer.""" - self.text = None self.current_char: Union[str, Any] = None diff --git a/sylvie/interpreter/parser.py b/sylvie/interpreter/parser.py index 33283f7..34084ff 100644 --- a/sylvie/interpreter/parser.py +++ b/sylvie/interpreter/parser.py @@ -29,7 +29,6 @@ class Parser: def __init__(self) -> None: """Initialize parser.""" - self.lexer = Lexer() self.tokens = None self.column = 0 @@ -41,7 +40,6 @@ def parse(self, text: str) -> Union[int, float]: Args: text: text to be parsed. """ - self.tokens = self.lexer.analyze(text) self.column = 0 self.advance() From 9ce476e2376a591949bbb7d7994c8e0715c78b11 Mon Sep 17 00:00:00 2001 From: Aditya Alok Date: Sun, 3 Oct 2021 23:55:12 +0530 Subject: [PATCH 4/5] Fix: formatting issues --- sylvie/__init__.py | 1 + sylvie/__main__.py | 2 +- sylvie/bin.py | 2 -- sylvie/interpreter/lexer.py | 1 - sylvie/interpreter/parser.py | 4 +++- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sylvie/__init__.py b/sylvie/__init__.py index a10679a..e014c98 100644 --- a/sylvie/__init__.py +++ b/sylvie/__init__.py @@ -1,4 +1,5 @@ """Sylvie is a mathematical interpreter. + It takes mathematical expressions as text and then evaluate it. Examples: diff --git a/sylvie/__main__.py b/sylvie/__main__.py index d66e8f7..9590921 100644 --- a/sylvie/__main__.py +++ b/sylvie/__main__.py @@ -36,6 +36,6 @@ # insert parent of script_dir (sylvie/), i.e src/ to sys.path sys.path.insert(0, str(script_dir.parent)) -from sylvie.bin import cmd +from sylvie.bin import cmd # skipcq: FLK-E402 cmd() diff --git a/sylvie/bin.py b/sylvie/bin.py index 5cffa12..7fedc49 100644 --- a/sylvie/bin.py +++ b/sylvie/bin.py @@ -1,12 +1,10 @@ """This module contains functioncs to run Sylvie from command line.""" -from typing import Union from sylvie.interpreter.parser import Parser def cmd(): """Starts Sylvie's REPL.""" - parser = Parser() try: while True: diff --git a/sylvie/interpreter/lexer.py b/sylvie/interpreter/lexer.py index bb8310e..1e4dfb0 100644 --- a/sylvie/interpreter/lexer.py +++ b/sylvie/interpreter/lexer.py @@ -18,7 +18,6 @@ class Lexer: def __init__(self) -> None: """Initialize lexer.""" - self.text = None self.current_char: Union[str, Any] = None diff --git a/sylvie/interpreter/parser.py b/sylvie/interpreter/parser.py index 33283f7..b21c892 100644 --- a/sylvie/interpreter/parser.py +++ b/sylvie/interpreter/parser.py @@ -29,7 +29,6 @@ class Parser: def __init__(self) -> None: """Initialize parser.""" - self.lexer = Lexer() self.tokens = None self.column = 0 @@ -38,6 +37,9 @@ def __init__(self) -> None: def parse(self, text: str) -> Union[int, float]: """Parses tokens and evaluates the expression formed. + This function takes an expression as input and returns its value + after evaluating. + Args: text: text to be parsed. """ From bbaeadd449f49097e3ea666b130914a927a3a592 Mon Sep 17 00:00:00 2001 From: Aditya Alok Date: Mon, 4 Oct 2021 00:00:06 +0530 Subject: [PATCH 5/5] Fix: formatting issues --- sylvie/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sylvie/__main__.py b/sylvie/__main__.py index 9590921..eb925b2 100644 --- a/sylvie/__main__.py +++ b/sylvie/__main__.py @@ -36,6 +36,7 @@ # insert parent of script_dir (sylvie/), i.e src/ to sys.path sys.path.insert(0, str(script_dir.parent)) -from sylvie.bin import cmd # skipcq: FLK-E402 +# skipcq: FLK-E402 +from sylvie.bin import cmd # noqa: E402 cmd()