Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: make it ready for school level project #5

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 0 additions & 113 deletions src/sylvie/__init__.py

This file was deleted.

40 changes: 0 additions & 40 deletions src/sylvie/__main__.py

This file was deleted.

2 changes: 0 additions & 2 deletions src/sylvie/ast_based/ast.py

This file was deleted.

21 changes: 0 additions & 21 deletions src/sylvie/ast_based/nodes.py

This file was deleted.

22 changes: 22 additions & 0 deletions sylvie/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""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()
42 changes: 42 additions & 0 deletions sylvie/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""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))

# skipcq: FLK-E402
from sylvie.bin import cmd # noqa: E402

cmd()
17 changes: 17 additions & 0 deletions sylvie/bin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""This module contains functioncs to run Sylvie from command line."""

from sylvie.interpreter.parser import Parser


def cmd():
"""Starts Sylvie's REPL."""
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.")
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -16,7 +16,12 @@ class Lexer:
text: input mathematical expression
"""

def __init__(self, text: str) -> None:
def __init__(self) -> None:
"""Initialize lexer."""
self.text = None
self.current_char: Union[str, Any] = None

def analyze(self, text: str) -> Generator:
"""Converts input text into a stream of tokens.

Args:
Expand All @@ -25,18 +30,20 @@ def __init__(self, text: str) -> None:
self.text = iter(text)
self.advance()

return self.tokens()

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

@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():
Expand All @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -27,16 +27,28 @@ class Parser:
expr: returns result of input expression.
"""

def __init__(self, text: str) -> None:
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.

This function takes an expression as input and returns its value
after evaluating.

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:
Expand Down Expand Up @@ -64,11 +76,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.
Expand Down Expand Up @@ -114,11 +126,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:
print("Oh! Its beyond my limit")
raise
result /= self.term()
return result

def sub_expr(self) -> Union[int, float]:
Expand Down
File renamed without changes.