feat(interpreter): add basic interpreter
This commit is contained in:
6
main.py
6
main.py
@@ -1,5 +1,8 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr
|
from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr
|
||||||
from src.ast.printer import AstPrinter
|
from src.ast.printer import AstPrinter
|
||||||
|
from src.interpreter.interpreter import Interpreter
|
||||||
from src.lexer import Lexer
|
from src.lexer import Lexer
|
||||||
from src.parser.parser import Parser
|
from src.parser.parser import Parser
|
||||||
from src.token import Token, TokenType
|
from src.token import Token, TokenType
|
||||||
@@ -38,6 +41,9 @@ def main():
|
|||||||
return
|
return
|
||||||
|
|
||||||
print(printer.print(ast))
|
print(printer.print(ast))
|
||||||
|
interpreter: Interpreter = Interpreter()
|
||||||
|
result: Any = interpreter.interpret(ast)
|
||||||
|
print(f"Result: {result}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
0
src/interpreter/__init__.py
Normal file
0
src/interpreter/__init__.py
Normal file
7
src/interpreter/error.py
Normal file
7
src/interpreter/error.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from src.token import Token
|
||||||
|
|
||||||
|
|
||||||
|
class PebbleRuntimeError(RuntimeError):
|
||||||
|
def __init__(self, token: Token, msg: str):
|
||||||
|
super().__init__(msg)
|
||||||
|
self.token: Token = token
|
||||||
100
src/interpreter/interpreter.py
Normal file
100
src/interpreter/interpreter.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from src.ast.expr import Visitor, LiteralExpr, T, GroupingExpr, UnaryExpr, BinaryExpr, Expr
|
||||||
|
from src.interpreter.error import PebbleRuntimeError
|
||||||
|
from src.pebble import Pebble
|
||||||
|
from src.token import TokenType, Token
|
||||||
|
|
||||||
|
|
||||||
|
class Interpreter(Visitor[Any]):
|
||||||
|
def interpret(self, expr: Expr) -> Any:
|
||||||
|
try:
|
||||||
|
return self.evaluate(expr)
|
||||||
|
except PebbleRuntimeError as e:
|
||||||
|
Pebble.runtime_error(e)
|
||||||
|
|
||||||
|
def evaluate(self, expr: Expr) -> Any:
|
||||||
|
return expr.accept(self)
|
||||||
|
|
||||||
|
def visit_binary_expr(self, expr: BinaryExpr) -> Any:
|
||||||
|
left: Any = self.evaluate(expr.left)
|
||||||
|
right: Any = self.evaluate(expr.right)
|
||||||
|
|
||||||
|
match expr.operator.type:
|
||||||
|
case TokenType.MINUS:
|
||||||
|
self.check_number_operands(expr.operator, left, right)
|
||||||
|
return float(left) - float(right)
|
||||||
|
case TokenType.SLASH:
|
||||||
|
self.check_number_operands(expr.operator, left, right)
|
||||||
|
return float(left) / float(right)
|
||||||
|
case TokenType.STAR:
|
||||||
|
self.check_number_operands(expr.operator, left, right)
|
||||||
|
return float(left) * float(right)
|
||||||
|
case TokenType.PLUS:
|
||||||
|
if isinstance(left, float) and isinstance(right, float):
|
||||||
|
return float(left) + float(right)
|
||||||
|
if isinstance(left, str) and isinstance(right, str):
|
||||||
|
return str(left) + str(right)
|
||||||
|
raise PebbleRuntimeError(expr.operator, "Operators must be two numbers or two strings.")
|
||||||
|
case TokenType.GREATER:
|
||||||
|
self.check_number_operands(expr.operator, left, right)
|
||||||
|
return float(left) > float(right)
|
||||||
|
case TokenType.GREATER_EQUAL:
|
||||||
|
self.check_number_operands(expr.operator, left, right)
|
||||||
|
return float(left) >= float(right)
|
||||||
|
case TokenType.LESS:
|
||||||
|
self.check_number_operands(expr.operator, left, right)
|
||||||
|
return float(left) < float(right)
|
||||||
|
case TokenType.LESS_EQUAL:
|
||||||
|
self.check_number_operands(expr.operator, left, right)
|
||||||
|
return float(left) <= float(right)
|
||||||
|
case TokenType.EQUAL_EQUAL:
|
||||||
|
return self.is_equal(left, right)
|
||||||
|
case TokenType.BANG_EQUAL:
|
||||||
|
return not self.is_equal(left, right)
|
||||||
|
|
||||||
|
# Unreachable
|
||||||
|
return None
|
||||||
|
|
||||||
|
def visit_unary_expr(self, expr: UnaryExpr) -> Any:
|
||||||
|
right: Any = self.evaluate(expr.right)
|
||||||
|
|
||||||
|
match expr.operator.type:
|
||||||
|
case TokenType.MINUS:
|
||||||
|
self.check_number_operand(expr.operator, right)
|
||||||
|
return -float(right)
|
||||||
|
case TokenType.BANG:
|
||||||
|
return not self.is_truthy(right)
|
||||||
|
|
||||||
|
# Unreachable
|
||||||
|
return None
|
||||||
|
|
||||||
|
def visit_grouping_expr(self, expr: GroupingExpr) -> Any:
|
||||||
|
return self.evaluate(expr.expression)
|
||||||
|
|
||||||
|
def visit_literal_expr(self, expr: LiteralExpr) -> Any:
|
||||||
|
return expr.value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_truthy(value: Any) -> bool:
|
||||||
|
if value is None or value is False:
|
||||||
|
return False
|
||||||
|
if value is True:
|
||||||
|
return True
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_equal(a: Any, b: Any) -> bool:
|
||||||
|
return a == b
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_number_operand(operator: Token, operand: Any):
|
||||||
|
if isinstance(operand, float):
|
||||||
|
return
|
||||||
|
raise PebbleRuntimeError(operator, "Operand must be a number.")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_number_operands(operator: Token, left: Any, right: Any):
|
||||||
|
if isinstance(left, float) and isinstance(right, float):
|
||||||
|
return
|
||||||
|
raise PebbleRuntimeError(operator, "Operands must be numbers.")
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from src.interpreter.error import PebbleRuntimeError
|
||||||
from src.position import Position
|
from src.position import Position
|
||||||
from src.token import Token, TokenType
|
from src.token import Token, TokenType
|
||||||
|
|
||||||
@@ -17,3 +18,7 @@ class Pebble:
|
|||||||
cls.report(token.position, " at end", msg)
|
cls.report(token.position, " at end", msg)
|
||||||
else:
|
else:
|
||||||
cls.report(token.position, f" at '{token.lexeme}'", msg)
|
cls.report(token.position, f" at '{token.lexeme}'", msg)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def runtime_error(cls, error: PebbleRuntimeError):
|
||||||
|
print(f"{error}\n({error.token.position})")
|
||||||
|
|||||||
Reference in New Issue
Block a user