feat(interpreter): add basic interpreter

This commit is contained in:
2026-02-05 15:36:23 +01:00
parent b3ef412af1
commit 9c2a1fb908
5 changed files with 118 additions and 0 deletions

View File

@@ -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__':

View File

7
src/interpreter/error.py Normal file
View 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

View 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.")

View File

@@ -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})")