Skip to content

Commit

Permalink
Merge pull request #110 from Jodus-Melodus/better-errors
Browse files Browse the repository at this point in the history
Better errors
  • Loading branch information
Jodus-Melodus committed Jun 17, 2024
2 parents d4a3574 + b86ba5c commit f88e6dc
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 126 deletions.
Binary file modified __pycache__/shell.cpython-311.pyc
Binary file not shown.
13 changes: 0 additions & 13 deletions ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,6 @@
"value": 5
},
"constant": "False"
},
{
"kind": "callExpression",
"arguments": [
{
"kind": "stringLiteral",
"value": "Hello world"
}
],
"caller": {
"kind": "identifier",
"symbol": "output"
}
}
]
}
2 changes: 1 addition & 1 deletion backend/Interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ def evaluate_delete_statement(self, delete_statement: DeleteNode, env: Environme
env.delete_variable(delete_statement.variable)
return NullValue()

def evaluate(self, astNode, env: Environment) -> NullValue | IntegerValue | ObjectValue | ArrayValue | StringValue | None:
def evaluate(self, astNode: ASTNode, env: Environment) -> RuntimeValue | None:
if isinstance(astNode, (str, float, int, Error)):
return astNode
match astNode.kind:
Expand Down
116 changes: 33 additions & 83 deletions frontend/Error.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import os

class Error:
def __init__(self, file:str="", column:int=0, line:int=0) -> None:
self.file = os.path.basename(file)
def __init__(self, file_path:str="", column:int=0, line:int=0) -> None:
self.file_path = os.path.basename(file_path)
self.column = column
self.line = line

def warning_message(self) -> str:
return ""

def __repr__(self) -> str:
return ""

Expand All @@ -21,157 +18,110 @@ def error_arrows(self) -> str:
out += '^'
return out + '\n'

###########################################################################################################

class SyntaxError(Error):
def __init__(self, file:str, stage: str, msg: str, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path:str, stage: str, msg: str, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.msg = msg
self.type = "syntaxError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Syntax Error] Ln {self.line}, Col {self.column} in {self.file}:
{self.msg}
[Syntax Error] Ln {self.line}, Col {self.column} in {self.file_path}: {self.msg}
"""

def warning_message(self) -> str:
return f"[Syntax Error] Ln {self.line}, Col {self.column} in {self.file}: {self.msg}"
###########################################################################################################

class NameError(Error):
def __init__(self, file:str, stage: str, name: str, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path:str, stage: str, name: str, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.name = name
self.type = "nameError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Name Error] Ln {self.line}, Col {self.column} in {self.file}:
"{self.name}" is not defined
[Name Error] Ln {self.line}, Col {self.column} in {self.file_path}: "{self.name}" is not defined
"""
def warning_message(self) -> str:
return f"[Name Error] Ln {self.line}, Col {self.column} in {self.file}: '{self.name}' is not defined"
###########################################################################################################

class InvalidCharacterError(Error):
def __init__(self, file:str, stage: str, character: str, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path:str, stage: str, character: str, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.character = character
self.type = "invalidCharacterError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Invalid Character Error] Ln {self.line}, Col {self.column} in {self.file}:
"{self.character}" is not a valid character
[Invalid Character Error] Ln {self.line}, Col {self.column} in {self.file_path}: "{self.character}" is not a valid character
"""

def warning_message(self) -> str:
return f"[Invalid Character Error] Ln {self.line}, Col {self.column} in {self.file}: '{self.character}' is not a valid character"

###########################################################################################################

class NotImplementedError(Error):
def __init__(self, file:str, stage: str, msg: str, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path:str, stage: str, msg: str, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.msg = msg
self.type = "notImplementedError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Not Implemented Error] Ln {self.line}, Col {self.column} in {self.file}:
"{self.msg}" is not yet Implemented
[Not Implemented Error] Ln {self.line}, Col {self.column} in {self.file_path}: "{self.msg}" is not yet Implemented
"""

def warning_message(self) -> str:
return f"[Not Implemented Error] Ln {self.line}, Col {self.column} in {self.file}: '{self.msg}' is not yet Implemented"
###########################################################################################################

class KeyError(Error):
def __init__(self, file:str, stage: str, key, obj, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path:str, stage: str, key, obj, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.key = key
self.obj = obj
self.type = "keyError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Key Error] Ln {self.line}, Col {self.column} in {self.file}:
"{self.key}" is not a valid key for "{self.obj}"
[Key Error] Ln {self.line}, Col {self.column} in {self.file_path}: "{self.key}" is not a valid key for "{self.obj}"
"""

def warning_message(self) -> str:
return f"[Key Error] Ln {self.line}, Col {self.column} in {self.file}: '{self.key}'' is not a valid key for '{self.obj}'"
###########################################################################################################

class TypeError(Error):
def __init__(self, file:str, stage: str, msg:str, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path:str, stage: str, msg:str, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.msg = msg
self.type = "typeError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Type Error] Ln {self.line}, Col {self.column} in {self.file}:
{self.msg}
[Type Error] Ln {self.line}, Col {self.column} in {self.file_path}: {self.msg}
"""

def warning_message(self) -> str:
return f"[Type Error] Ln {self.line}, Col {self.column} in {self.file}: {self.msg}"
###########################################################################################################

class ZeroDivisionError(Error):
def __init__(self, file:str, stage: str, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path:str, stage: str, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.type = "zeroDivisionError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Zero Division Error] Ln {self.line}, Col {self.column} in {self.file}:
Cannot divide by zero
[Zero Division Error] Ln {self.line}, Col {self.column} in {self.file_path}: Cannot divide by zero
"""

def warning_message(self) -> str:
return f"[Zero Division Error] Ln {self.line}, Col {self.column} in {self.file}: Cannot divide by zero"
###########################################################################################################

class FileNotFoundError(Error):
def __init__(self, file:str, stage: str, file_: str, column: int, line: int) -> None:
super().__init__(file, column, line)
class File_pathNotFoundError(Error):
def __init__(self, file_path:str, stage: str, file_path_: str, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.file_ = file_
self.type = "fileNotFoundError"
self.file_path_ = file_path_
self.type = "file_pathNotFoundError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[File Not Found Error] Ln {self.line}, Col {self.column} in {self.file}:
File "{self.file_}" not found
[File_path Not Found Error] Ln {self.line}, Col {self.column} in {self.file_path}: File_path "{self.file_path_}" not found
"""

def warning_message(self) -> str:
return f"[File Not Found Error] Ln {self.line}, Col {self.column} in {self.file}: File '{self.file_}' not found"

###########################################################################################################

class ValueError(Error):
def __init__(self, file: str, stage: str, value, column: int, line: int) -> None:
super().__init__(file, column, line)
def __init__(self, file_path: str, stage: str, value, column: int, line: int) -> None:
super().__init__(file_path, column, line)
self.stage = stage
self.value = value
self.type = "valueError"

def __repr__(self) -> str:
return f"""{self.error_arrows()}
[Value Error] Ln {self.line}, Col {self.column} in {self.file}:
Invalid value : "{self.value}"
"""

def warning_message(self) -> str:
return f"[Value Error] Ln {self.line}, Col {self.column} in {self.file}: Invalid value : '{self.value}'"
[Value Error] Ln {self.line}, Col {self.column} in {self.file_path}: Invalid value : "{self.value}"
"""
4 changes: 2 additions & 2 deletions frontend/Lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __init__(self, source_code: str, file_path:str=''):
self.column = 1

def __str__(self) -> str:
return 'Lexer'
return "Lexer"

def eat(self) -> None:
self.column += 1
Expand Down Expand Up @@ -281,7 +281,7 @@ def process_variables_and_identifiers(self):

while len(self.source_code) > 0:
char = self.get()
if char in f'{ALPHABET}1234567890':
if char in f'{ALPHABET}{DIGITS}':
name += char
else:
break
Expand Down
19 changes: 13 additions & 6 deletions frontend/Parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from frontend.Error import *

class Parser:
def __init__(self, tokens: list, filePath:str="") -> None:
self.file_path = filePath
def __init__(self, tokens: list, file_path:str="") -> None:
self.file_path = file_path
self.tokens = tokens
self.program = ProgramNode([])
self.conditional_operators = (TT.equal, TT.not_equal, TT.greater_than, TT.less_than, TT.greater_than_equal, TT.less_than_equal, TT._and, TT._or)
Expand Down Expand Up @@ -473,10 +473,13 @@ def parse_function_declaration(self) -> None:

def parse_variable_declaration(self) -> None:
datatype = self.eat().type

if self.get().type == TT.eof:
return SyntaxError(self.file_path, self, "Expected an identifier", self.column, self.line)

identifier = self.eat().value
constant = bool(identifier.isupper())

if self.get().type != TT.assignment_operator:
if self.get().type in (TT.eof, TT.lineend):
self.eat()
Expand All @@ -486,10 +489,14 @@ def parse_variable_declaration(self) -> None:
return SyntaxError(self.file_path, self, "Expected a variable declaration", self.column, self.line)
else:
self.eat()
statement = self.parse_statement()
if isinstance(statement, Error):
return statement
return VariableDeclarationExpressionNode(datatype, identifier, statement, constant, self.line, self.column)

if self.get().type in [TT.int_value, TT.string_value, TT.real_value, TT.identifier]:
statement = self.parse_statement()
if isinstance(statement, Error):
return statement
return VariableDeclarationExpressionNode(datatype, identifier, statement, constant, self.line, self.column)
else:
return SyntaxError(self.file_path, self, "Expected a value", self.column, self.line)

def parse_assignment_expression(self) -> None:
left = self.parse_object_expression()
Expand Down
30 changes: 15 additions & 15 deletions phIDE.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ def key_press_update(self, event) -> None:
if editor := self.current_tab:
self.line, self.column = editor.index("insert").split(".")
self.status_bar.configure(text=f"Ln {self.line}, Col {self.column}")

current_code = editor.get("0.0", "end")
self.get_warnings(editor, current_code)

Expand All @@ -735,36 +736,34 @@ def get_warnings(self, editor : ctk.CTkTextbox, current_code: str):

self.warnings.configure(state="normal")
self.warnings.delete("0.0", "end")
self.warnings.configure(state="disabled")

parser_warnings = shell.incremental_parsing(current_code, self.current_path)

if isinstance(parser_warnings, list) and len(parser_warnings) > 0 and isinstance(parser_warnings[0], Error):
for warning in parser_warnings:
warning: Error = warning
line = warning.line
editor.tag_add("warning", f"{line}.0", f"{line}.end")
self.warnings.configure(state="normal")
self.warnings.insert("end", warning.warning_message() + '\n')
self.warnings.configure(state="disabled")

# Update warning tab's name to match total warnings
warnings = self.warnings.get("0.0", "end").strip().split("\n")
tabs = list(self.bottom_tabview._tab_dict.keys())
program_line = editor.get(f"{line}.0", f"{line}.end")

editor.tag_add("warning", f"{line}.0", f"{line}.end")
self.warnings.insert("end", f"{program_line}\n{repr(warning)}\n")

if (len(warnings) > 0) and (warnings[0] != ""):
new_name = f"Warnings({len(warnings)})"
# Update warning tab's name to match total warnings
new_name = f"Warnings({len(parser_warnings)})"
else:
editor.tag_remove("warning", "0.0", "end")
new_name = "Warnings"

self.warnings.configure(state="disabled")
tabs = list(self.bottom_tabview._tab_dict.keys())
current_name = tabs[1]

if new_name not in self.bottom_tabview._tab_dict.keys():
self.bottom_tabview.rename(current_name, new_name)

def editor_press(self, _=None) -> None:
if editor := self.current_tab:
editor.tag_remove("warning", "0.0", "end")
name = self.center_tabview.get()
current_code = editor.get("0.0", "end")

Expand All @@ -788,11 +787,12 @@ def mouse_click_update(self, _=None) -> None:
def highlight_selected(self, _=None) -> None:
if not (editor := self.current_tab):
return
text = editor.get("0.0", "end").split("\n")

if editor.tag_ranges("sel"):
w = editor.get(ctk.SEL_FIRST, ctk.SEL_LAST)
word = re.escape(w)
pattern = f"({word})"
word = editor.get(ctk.SEL_FIRST, ctk.SEL_LAST)
pattern = f"({re.escape(word)})"
text = editor.get("0.0", "end").split("\n")

for ln, line in enumerate(text):
matches = [(match.start(), match.end())
for match in re.finditer(pattern, line)]
Expand Down
Loading

0 comments on commit f88e6dc

Please sign in to comment.