diff --git a/main.py b/main.py index 52cacac..91871f5 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,8 @@ +from typing import Any + from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr from src.ast.printer import AstPrinter +from src.interpreter.interpreter import Interpreter from src.lexer import Lexer from src.parser.parser import Parser from src.token import Token, TokenType @@ -38,6 +41,9 @@ def main(): return print(printer.print(ast)) + interpreter: Interpreter = Interpreter() + result: Any = interpreter.interpret(ast) + print(f"Result: {result}") if __name__ == '__main__': diff --git a/src/interpreter/__init__.py b/src/interpreter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/interpreter/error.py b/src/interpreter/error.py new file mode 100644 index 0000000..ac5761b --- /dev/null +++ b/src/interpreter/error.py @@ -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 diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py new file mode 100644 index 0000000..dbc323c --- /dev/null +++ b/src/interpreter/interpreter.py @@ -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.") diff --git a/src/pebble.py b/src/pebble.py index 6a3f031..628092f 100644 --- a/src/pebble.py +++ b/src/pebble.py @@ -1,3 +1,4 @@ +from src.interpreter.error import PebbleRuntimeError from src.position import Position from src.token import Token, TokenType @@ -17,3 +18,7 @@ class Pebble: cls.report(token.position, " at end", msg) else: cls.report(token.position, f" at '{token.lexeme}'", msg) + + @classmethod + def runtime_error(cls, error: PebbleRuntimeError): + print(f"{error}\n({error.token.position})")