mirror of
https://github.com/Zandercraft/Aurora-lang.git
synced 2026-10-31 04:59:20 +00:00
Tracebacks and SyntaxErrs and RuntimeErrs OH MY!
This commit is contained in:
@@ -12,4 +12,6 @@ expr : term ((PLUS|MINUS) term)*
|
|||||||
term : factor ((MUL|DIV) factor)*
|
term : factor ((MUL|DIV) factor)*
|
||||||
|
|
||||||
factor : INT | FLOAT
|
factor : INT | FLOAT
|
||||||
```
|
: (PLUS|MINUS) factor
|
||||||
|
: LPAREN expr RPAREN
|
||||||
|
```
|
||||||
|
|||||||
400
aurora.py
400
aurora.py
@@ -10,15 +10,18 @@ d88P aaa Y88888 888 Y88P 888 Y888888
|
|||||||
Purpose: Main implementation of Aurora
|
Purpose: Main implementation of Aurora
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#############
|
# Imports
|
||||||
# CONSTANTS #
|
from internal.term_utils import point_at
|
||||||
#############
|
|
||||||
|
####################
|
||||||
|
# CONSTANTS
|
||||||
|
####################
|
||||||
DIGITS = "0123456789"
|
DIGITS = "0123456789"
|
||||||
|
|
||||||
|
|
||||||
#############
|
####################
|
||||||
# ERRORS #
|
# ERRORS
|
||||||
#############
|
####################
|
||||||
class Error:
|
class Error:
|
||||||
def __init__(self, pos_start, pos_end, error_name, details):
|
def __init__(self, pos_start, pos_end, error_name, details):
|
||||||
self.pos_start = pos_start
|
self.pos_start = pos_start
|
||||||
@@ -29,17 +32,48 @@ class Error:
|
|||||||
def as_string(self):
|
def as_string(self):
|
||||||
result = f'{self.error_name}: {self.details}\n'
|
result = f'{self.error_name}: {self.details}\n'
|
||||||
result += f'File {self.pos_start.fn}, line {self.pos_start.ln + 1}'
|
result += f'File {self.pos_start.fn}, line {self.pos_start.ln + 1}'
|
||||||
|
result += f'\n{point_at(self.pos_start.ftxt, self.pos_start, self.pos_end)}'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class IllegalCharError(Error):
|
class IllegalCharErr(Error):
|
||||||
def __init__(self, pos_start, pos_end, details):
|
def __init__(self, pos_start, pos_end, details):
|
||||||
super().__init__(pos_start, pos_end, 'IllegalCharacter', details)
|
super().__init__(pos_start, pos_end, 'IllegalCharacter', details)
|
||||||
|
|
||||||
|
|
||||||
#######################################
|
class InvalidSyntaxErr(Error):
|
||||||
|
def __init__(self, pos_start, pos_end, details=''):
|
||||||
|
super().__init__(pos_start, pos_end, 'InvalidSyntax', details)
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeErr(Error):
|
||||||
|
def __init__(self, pos_start, pos_end, details, context):
|
||||||
|
super().__init__(pos_start, pos_end, 'RuntimeErr', details)
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
def as_string(self):
|
||||||
|
result = self.generate_traceback()
|
||||||
|
result += f'{self.error_name}: {self.details}\n'
|
||||||
|
result += f'\n{point_at(self.pos_start.ftxt, self.pos_start, self.pos_end)}'
|
||||||
|
return result
|
||||||
|
|
||||||
|
def generate_traceback(self):
|
||||||
|
result = ''
|
||||||
|
pos = self.pos_start
|
||||||
|
context = self.context
|
||||||
|
|
||||||
|
while context:
|
||||||
|
result = f' File {pos.fn} line {str(pos.ln + 1)}, in {context.display_name}\n' + result
|
||||||
|
pos = context.parent_entry_pos
|
||||||
|
context = context.parent
|
||||||
|
|
||||||
|
return f"Traceback (most recent call last):\n" \
|
||||||
|
f"{result}"
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
# POSITION
|
# POSITION
|
||||||
#######################################
|
####################
|
||||||
class Position:
|
class Position:
|
||||||
def __init__(self, idx, ln, col, fn, ftxt):
|
def __init__(self, idx, ln, col, fn, ftxt):
|
||||||
self.idx = idx
|
self.idx = idx
|
||||||
@@ -48,7 +82,7 @@ class Position:
|
|||||||
self.fn = fn
|
self.fn = fn
|
||||||
self.ftxt = ftxt
|
self.ftxt = ftxt
|
||||||
|
|
||||||
def advance(self, current_char):
|
def advance(self, current_char=None):
|
||||||
self.idx += 1
|
self.idx += 1
|
||||||
self.col += 1
|
self.col += 1
|
||||||
|
|
||||||
@@ -62,9 +96,9 @@ class Position:
|
|||||||
return Position(self.idx, self.ln, self.col, self.fn, self.ftxt)
|
return Position(self.idx, self.ln, self.col, self.fn, self.ftxt)
|
||||||
|
|
||||||
|
|
||||||
#############
|
####################
|
||||||
# Tokens #
|
# TOKENS
|
||||||
#############
|
####################
|
||||||
# Token types
|
# Token types
|
||||||
TT_INT = "INT"
|
TT_INT = "INT"
|
||||||
TT_FLOAT = "FLOAT"
|
TT_FLOAT = "FLOAT"
|
||||||
@@ -74,13 +108,22 @@ TT_MUL = "MUL"
|
|||||||
TT_DIV = "DIV"
|
TT_DIV = "DIV"
|
||||||
TT_LPAREN = "LPAREN"
|
TT_LPAREN = "LPAREN"
|
||||||
TT_RPAREN = "RPAREN"
|
TT_RPAREN = "RPAREN"
|
||||||
|
TT_EOF = "EOF"
|
||||||
|
|
||||||
|
|
||||||
class Token:
|
class Token:
|
||||||
def __init__(self, type_, value=None):
|
def __init__(self, type_, value=None, pos_start=None, pos_end=None):
|
||||||
self.type = type_
|
self.type = type_
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
if pos_start:
|
||||||
|
self.pos_start = pos_start.copy()
|
||||||
|
self.pos_end = pos_start.copy()
|
||||||
|
self.pos_end.advance()
|
||||||
|
|
||||||
|
if pos_end:
|
||||||
|
self.pos_end = pos_end
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""
|
"""
|
||||||
Will return "type:value" if a value exists. Otherwise, just "type".
|
Will return "type:value" if a value exists. Otherwise, just "type".
|
||||||
@@ -88,9 +131,9 @@ class Token:
|
|||||||
return f"<{self.type}:{self.value}>" if self.value else f"<{self.type}>"
|
return f"<{self.type}:{self.value}>" if self.value else f"<{self.type}>"
|
||||||
|
|
||||||
|
|
||||||
#############
|
####################
|
||||||
# Lexer #
|
# LEXER
|
||||||
#############
|
####################
|
||||||
class Lexer:
|
class Lexer:
|
||||||
def __init__(self, fn, text):
|
def __init__(self, fn, text):
|
||||||
self.fn = fn
|
self.fn = fn
|
||||||
@@ -112,35 +155,37 @@ class Lexer:
|
|||||||
self.advance()
|
self.advance()
|
||||||
elif self.current_char in DIGITS:
|
elif self.current_char in DIGITS:
|
||||||
tokens.append(self.make_number())
|
tokens.append(self.make_number())
|
||||||
elif self.current_char == "+":
|
elif self.current_char == '+':
|
||||||
tokens.append(Token(TT_PLUS))
|
tokens.append(Token(TT_PLUS, pos_start=self.pos))
|
||||||
self.advance()
|
self.advance()
|
||||||
elif self.current_char == "-":
|
elif self.current_char == '-':
|
||||||
tokens.append(Token(TT_MINUS))
|
tokens.append(Token(TT_MINUS, pos_start=self.pos))
|
||||||
self.advance()
|
self.advance()
|
||||||
elif self.current_char == "*":
|
elif self.current_char == '*':
|
||||||
tokens.append(Token(TT_MUL))
|
tokens.append(Token(TT_MUL, pos_start=self.pos))
|
||||||
self.advance()
|
self.advance()
|
||||||
elif self.current_char == "/":
|
elif self.current_char == '/':
|
||||||
tokens.append(Token(TT_DIV))
|
tokens.append(Token(TT_DIV, pos_start=self.pos))
|
||||||
self.advance()
|
self.advance()
|
||||||
elif self.current_char == "(":
|
elif self.current_char == '(':
|
||||||
tokens.append(Token(TT_LPAREN))
|
tokens.append(Token(TT_LPAREN, pos_start=self.pos))
|
||||||
self.advance()
|
self.advance()
|
||||||
elif self.current_char == ")":
|
elif self.current_char == ')':
|
||||||
tokens.append(Token(TT_RPAREN))
|
tokens.append(Token(TT_RPAREN, pos_start=self.pos))
|
||||||
self.advance()
|
self.advance()
|
||||||
else:
|
else:
|
||||||
pos_start = self.pos.copy()
|
pos_start = self.pos.copy()
|
||||||
char = self.current_char
|
char = self.current_char
|
||||||
self.advance()
|
self.advance()
|
||||||
return [], IllegalCharError(pos_start, self.pos, "'" + char + "'")
|
return [], IllegalCharErr(pos_start, self.pos, "'" + char + "'")
|
||||||
|
|
||||||
|
tokens.append(Token(TT_EOF, pos_start=self.pos))
|
||||||
return tokens, None
|
return tokens, None
|
||||||
|
|
||||||
def make_number(self):
|
def make_number(self):
|
||||||
num_str = ''
|
num_str = ''
|
||||||
dot_count = 0
|
dot_count = 0
|
||||||
|
pos_start = self.pos.copy()
|
||||||
|
|
||||||
while self.current_char is not None and self.current_char in (DIGITS + '.'):
|
while self.current_char is not None and self.current_char in (DIGITS + '.'):
|
||||||
if self.current_char == '.':
|
if self.current_char == '.':
|
||||||
@@ -153,16 +198,299 @@ class Lexer:
|
|||||||
self.advance()
|
self.advance()
|
||||||
|
|
||||||
if dot_count == 0:
|
if dot_count == 0:
|
||||||
return Token(TT_INT, int(num_str))
|
return Token(TT_INT, int(num_str), pos_start, self.pos)
|
||||||
else:
|
else:
|
||||||
return Token(TT_FLOAT, float(num_str))
|
return Token(TT_FLOAT, float(num_str), pos_start, self.pos)
|
||||||
|
|
||||||
|
|
||||||
#############
|
####################
|
||||||
# Runner #
|
# NODES
|
||||||
#############
|
####################
|
||||||
|
class NumberNode:
|
||||||
|
def __init__(self, token):
|
||||||
|
self.token = token
|
||||||
|
self.pos_start = self.token.pos_start
|
||||||
|
self.pos_end = self.token.pos_end
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.token}"
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryOperationNode:
|
||||||
|
def __init__(self, left_node, operation, right_node):
|
||||||
|
self.left_node = left_node
|
||||||
|
self.operation = operation
|
||||||
|
self.right_node = right_node
|
||||||
|
self.pos_start = self.left_node.pos_start
|
||||||
|
self.pos_end = self.right_node.pos_end
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"({self.left_node}, {self.operation}, {self.right_node})"
|
||||||
|
|
||||||
|
|
||||||
|
class UnaryOperationNode:
|
||||||
|
def __init__(self, operation, node):
|
||||||
|
self.operation = operation
|
||||||
|
self.node = node
|
||||||
|
self.pos_start = self.operation.pos_start
|
||||||
|
self.pos_end = self.node.pos_end
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'({self.operation}, {self.node})'
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# PARSER RESULT
|
||||||
|
####################
|
||||||
|
class ParsedResult:
|
||||||
|
def __init__(self):
|
||||||
|
self.error = None
|
||||||
|
self.node = None
|
||||||
|
|
||||||
|
def register(self, result):
|
||||||
|
if isinstance(result, ParsedResult):
|
||||||
|
if result.error:
|
||||||
|
self.error = result.error
|
||||||
|
return result.node
|
||||||
|
return result
|
||||||
|
|
||||||
|
def success(self, node):
|
||||||
|
self.node = node
|
||||||
|
return self
|
||||||
|
|
||||||
|
def failure(self, error):
|
||||||
|
self.error = error
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# RUNTIME RESULT
|
||||||
|
####################
|
||||||
|
class RuntimeResult:
|
||||||
|
def __init__(self):
|
||||||
|
self.value = None
|
||||||
|
self.error = None
|
||||||
|
|
||||||
|
def register(self, result):
|
||||||
|
if result.error:
|
||||||
|
self.error = result.error
|
||||||
|
return result.value
|
||||||
|
|
||||||
|
def success(self, value):
|
||||||
|
self.value = value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def failure(self, error):
|
||||||
|
self.error = error
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# PARSER
|
||||||
|
####################
|
||||||
|
class Parser:
|
||||||
|
def __init__(self, tokens):
|
||||||
|
self.tokens = tokens
|
||||||
|
self.token_idx = -1
|
||||||
|
self.current_token = None
|
||||||
|
self.advance()
|
||||||
|
|
||||||
|
def advance(self):
|
||||||
|
self.token_idx += 1
|
||||||
|
if self.token_idx < len(self.tokens):
|
||||||
|
self.current_token = self.tokens[self.token_idx]
|
||||||
|
return self.current_token
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
result = self.expr()
|
||||||
|
if not result.error and self.current_token.type != TT_EOF:
|
||||||
|
return result.failure(InvalidSyntaxErr(
|
||||||
|
self.current_token.pos_start,
|
||||||
|
self.current_token.pos_end,
|
||||||
|
"Expected '+', '-', '*', or '/'"
|
||||||
|
))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def factor(self):
|
||||||
|
result = ParsedResult()
|
||||||
|
token = self.current_token
|
||||||
|
|
||||||
|
if token.type in (TT_PLUS, TT_MINUS):
|
||||||
|
result.register(self.advance())
|
||||||
|
factor = result.register(self.factor())
|
||||||
|
if result.error:
|
||||||
|
return result
|
||||||
|
return result.success(UnaryOperationNode(token, factor))
|
||||||
|
elif token.type in (TT_INT, TT_FLOAT):
|
||||||
|
result.register(self.advance())
|
||||||
|
return result.success(NumberNode(token))
|
||||||
|
elif token.type == TT_LPAREN:
|
||||||
|
result.register(self.advance())
|
||||||
|
expr = result.register(self.expr())
|
||||||
|
if result.error:
|
||||||
|
return result
|
||||||
|
if self.current_token.type == TT_RPAREN:
|
||||||
|
result.register(self.advance())
|
||||||
|
return result.success(expr)
|
||||||
|
else:
|
||||||
|
return result.failure(InvalidSyntaxErr(
|
||||||
|
self.current_token.pos_start, self.current_token.pos_end,
|
||||||
|
"Expected ')'"
|
||||||
|
))
|
||||||
|
|
||||||
|
return result.failure(InvalidSyntaxErr(token.pos_start, token.pos_end, "Expected int or float"))
|
||||||
|
|
||||||
|
def term(self):
|
||||||
|
return self.binary_operation(self.factor, (TT_MUL, TT_DIV))
|
||||||
|
|
||||||
|
def expr(self):
|
||||||
|
return self.binary_operation(self.term, (TT_PLUS, TT_MINUS))
|
||||||
|
|
||||||
|
def binary_operation(self, func, operations):
|
||||||
|
result = ParsedResult()
|
||||||
|
left = result.register(func())
|
||||||
|
if result.error:
|
||||||
|
return result
|
||||||
|
|
||||||
|
while self.current_token.type in operations:
|
||||||
|
operation = self.current_token
|
||||||
|
result.register(self.advance())
|
||||||
|
right = result.register(func())
|
||||||
|
if result.error:
|
||||||
|
return result
|
||||||
|
left = BinaryOperationNode(left, operation, right)
|
||||||
|
|
||||||
|
return result.success(left)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# VALUES
|
||||||
|
####################
|
||||||
|
class Number:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
self.pos_start = None
|
||||||
|
self.pos_end = None
|
||||||
|
self.context = None
|
||||||
|
|
||||||
|
def set_pos(self, pos_start=None, pos_end=None):
|
||||||
|
self.pos_start = pos_start
|
||||||
|
self.pos_end = pos_end
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_context(self, context=None):
|
||||||
|
self.context = context
|
||||||
|
return self
|
||||||
|
|
||||||
|
def added_to(self, other):
|
||||||
|
if isinstance(other, Number):
|
||||||
|
return Number(self.value + other.value).set_context(self.context), None
|
||||||
|
|
||||||
|
def subtracted_by(self, other):
|
||||||
|
if isinstance(other, Number):
|
||||||
|
return Number(self.value - other.value).set_context(self.context), None
|
||||||
|
|
||||||
|
def multiplied_by(self, other):
|
||||||
|
if isinstance(other, Number):
|
||||||
|
return Number(self.value * other.value).set_context(self.context), None
|
||||||
|
|
||||||
|
def divided_by(self, other):
|
||||||
|
if isinstance(other, Number):
|
||||||
|
if other.value == 0:
|
||||||
|
return None, RuntimeErr(
|
||||||
|
other.pos_start,
|
||||||
|
other.pos_end,
|
||||||
|
"Division by zero",
|
||||||
|
self.context
|
||||||
|
)
|
||||||
|
return Number(self.value / other.value).set_context(self.context), None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# CONTEXT
|
||||||
|
####################
|
||||||
|
class Context:
|
||||||
|
def __init__(self, display_name, parent=None, parent_entry_pos=None):
|
||||||
|
self.display_name = display_name
|
||||||
|
self.parent = parent
|
||||||
|
self.parent_entry_pos = parent_entry_pos
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# INTERPRETER
|
||||||
|
####################
|
||||||
|
class Interpreter:
|
||||||
|
def visit(self, node, context):
|
||||||
|
method_name = f'visit_{type(node).__name__}'
|
||||||
|
method = getattr(self, method_name, self.no_visit_method)
|
||||||
|
return method(node, context)
|
||||||
|
|
||||||
|
def no_visit_method(self, node, context):
|
||||||
|
raise Exception(f"No visit_{type(node).__name__} method defined")
|
||||||
|
|
||||||
|
def visit_NumberNode(self, node, context):
|
||||||
|
return RuntimeResult().success(Number(node.token.value).set_context(context).set_pos(node.pos_start, node.pos_end))
|
||||||
|
|
||||||
|
def visit_BinaryOperationNode(self, node, context):
|
||||||
|
rt_result = RuntimeResult()
|
||||||
|
left = rt_result.register(self.visit(node.left_node, context))
|
||||||
|
if rt_result.error:
|
||||||
|
return rt_result
|
||||||
|
right = rt_result.register(self.visit(node.right_node, context))
|
||||||
|
if rt_result.error:
|
||||||
|
return rt_result
|
||||||
|
|
||||||
|
if node.operation.type == TT_PLUS:
|
||||||
|
result, error = left.added_to(right)
|
||||||
|
if node.operation.type == TT_MINUS:
|
||||||
|
result, error = left.subtracted_by(right)
|
||||||
|
if node.operation.type == TT_MUL:
|
||||||
|
result, error = left.multiplied_by(right)
|
||||||
|
if node.operation.type == TT_DIV:
|
||||||
|
result, error = left.divided_by(right)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return rt_result.failure(error)
|
||||||
|
return rt_result.success(result.set_pos(node.pos_start, node.pos_end))
|
||||||
|
|
||||||
|
def visit_UnaryOperationNode(self, node: UnaryOperationNode, context):
|
||||||
|
result = RuntimeResult()
|
||||||
|
number = result.register(self.visit(node.node, context))
|
||||||
|
if result.error:
|
||||||
|
return result
|
||||||
|
|
||||||
|
if node.operation.type == TT_MINUS:
|
||||||
|
number, error = number.multiplied_by(Number(-1))
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return result.failure(error)
|
||||||
|
return number.set_pos(node.pos_start, node.pos_end)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# RUNNER
|
||||||
|
####################
|
||||||
def evaluate(fn, text):
|
def evaluate(fn, text):
|
||||||
lexer = Lexer(fn, text)
|
lexer = Lexer(fn, text)
|
||||||
tokens, error = lexer.make_tokens()
|
tokens, error = lexer.make_tokens()
|
||||||
|
if error:
|
||||||
|
return None, error
|
||||||
|
|
||||||
return tokens, error
|
if error:
|
||||||
|
return None, error
|
||||||
|
|
||||||
|
# Generate the abstract syntax tree
|
||||||
|
parser = Parser(tokens)
|
||||||
|
ast = parser.parse()
|
||||||
|
if ast.error:
|
||||||
|
return None, ast.error
|
||||||
|
|
||||||
|
# Run the program
|
||||||
|
interpreter = Interpreter()
|
||||||
|
context = Context("<program>")
|
||||||
|
result = interpreter.visit(ast.node, context)
|
||||||
|
|
||||||
|
return result.value, result.error
|
||||||
|
|||||||
Reference in New Issue
Block a user