Files
pebble/src/interpreter/interpreter.py

316 lines
11 KiB
Python

from typing import Any, Optional
from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr, GetExpr, T, SetExpr
from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
from src.core.callable import PebbleCallable
from src.core.function import PebbleFunction
from src.core.instance import PebbleInstance
from src.core.klass import PebbleClass
from src.interpreter.environment import Environment
from src.interpreter.error import PebbleRuntimeError
from src.interpreter.exceptions import ReturnException, BreakException, ContinueException
from src.interpreter.globals import GlobalEnvironment
from src.pebble import Pebble
from src.token import TokenType, Token
class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
def __init__(self):
self.global_env: GlobalEnvironment = GlobalEnvironment()
self.env: Environment = self.global_env
self.locals: dict[Expr, int] = {}
def interpret(self, statements: list[Stmt]) -> None:
try:
for stmt in statements:
self.execute(stmt)
except PebbleRuntimeError as e:
Pebble.runtime_error(e)
def evaluate(self, expr: Expr) -> Any:
return expr.accept(self)
def execute(self, stmt: Stmt) -> None:
stmt.accept(self)
def execute_block(self, statements: list[Stmt], env: Environment) -> None:
previous_env: Environment = self.env
try:
self.env = env
for stmt in statements:
self.execute(stmt)
finally:
self.env = previous_env
def resolve(self, expr: Expr, depth: int) -> None:
self.locals[expr] = depth
def look_up_variable(self, name: Token, expr: Expr):
distance: int = self.locals.get(expr)
if distance is not None:
return self.env.get_at(distance, name.lexeme)
return self.global_env.get(name)
def visit_assign_expr(self, expr: AssignExpr) -> Any:
value: Any = self.evaluate(expr.value)
distance: int = self.locals.get(expr)
if distance is not None:
self.env.assign_at(distance, expr.name, value)
else:
self.global_env.assign(expr.name, value)
return value
def visit_logical_expr(self, expr: LogicalExpr) -> Any:
left: Any = self.evaluate(expr.left)
match expr.operator.type:
case TokenType.OR:
if self.is_truthy(left):
return left
case TokenType.AND:
if not self.is_truthy(left):
return left
case _:
# Unreachable
raise PebbleRuntimeError(expr.operator, f"Unknown logical operator")
return self.evaluate(expr.right)
def visit_set_expr(self, expr: SetExpr) -> Any:
obj: Any = self.evaluate(expr.object)
if not isinstance(obj, PebbleInstance):
raise PebbleRuntimeError(expr.name, "Only class instances have fields.")
value: Any = self.evaluate(expr.value)
obj.set(expr.name, value)
return value
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 | TokenType.MINUS_EQUAL:
self.check_number_operands(expr.operator, left, right)
return float(left) - float(right)
case TokenType.SLASH | TokenType.SLASH_EQUAL:
self.check_number_operands(expr.operator, left, right)
return float(left) / float(right)
case TokenType.STAR | TokenType.STAR_EQUAL:
self.check_number_operands(expr.operator, left, right)
return float(left) * float(right)
case TokenType.PLUS | TokenType.PLUS_EQUAL:
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_call_expr(self, expr: CallExpr) -> Any:
callee: Any = self.evaluate(expr.callee)
arguments: list[Any] = [
self.evaluate(arg)
for arg in expr.arguments
]
if not isinstance(callee, PebbleCallable):
raise PebbleRuntimeError(expr.paren, "Can only call functions and classes.")
function: PebbleCallable = callee
arity: int = function.arity()
if arity != -1 and len(arguments) != arity:
raise PebbleRuntimeError(expr.paren, f"Expected {arity} arguments but got {len(arguments)}.")
return function.call(self, arguments)
def visit_get_expr(self, expr: GetExpr) -> Any:
obj: Any = self.evaluate(expr.object)
if isinstance(obj, PebbleInstance):
return obj.get(expr.name)
raise PebbleRuntimeError(expr.name, "Only class instances have properties.")
def visit_grouping_expr(self, expr: GroupingExpr) -> Any:
return self.evaluate(expr.expression)
def visit_literal_expr(self, expr: LiteralExpr) -> Any:
return expr.value
def visit_variable_expr(self, expr: VariableExpr) -> Any:
return self.look_up_variable(expr.name, expr)
def visit_block_stmt(self, stmt: BlockStmt) -> None:
self.execute_block(stmt.statements, Environment(self.env))
def visit_class_stmt(self, stmt: ClassStmt) -> None:
self.env.define(stmt.name.lexeme, None)
klass: PebbleClass = PebbleClass(stmt.name.lexeme)
self.env.assign(stmt.name, klass)
def visit_expression_stmt(self, stmt: ExpressionStmt) -> None:
self.evaluate(stmt.expression)
def visit_function_stmt(self, stmt: FunctionStmt) -> None:
function: PebbleFunction = PebbleFunction(stmt, self.env)
self.env.define(stmt.name.lexeme, function)
def visit_if_stmt(self, stmt: IfStmt) -> None:
if self.is_truthy(self.evaluate(stmt.condition)):
self.execute(stmt.then_branch)
elif stmt.else_branch is not None:
self.execute(stmt.else_branch)
def visit_return_stmt(self, stmt: ReturnStmt) -> None:
value: Any = None
if stmt.value is not None:
value = self.evaluate(stmt.value)
raise ReturnException(value)
def visit_while_stmt(self, stmt: WhileStmt) -> None:
while self.is_truthy(self.evaluate(stmt.condition)):
try:
self.execute(stmt.body)
except BreakException:
break
except ContinueException:
pass
def visit_for_stmt(self, stmt: ForStmt) -> None:
previous_env: Environment = self.env
self.env = Environment(self.env)
start_value: float = 0
if stmt.start is not None:
start: Any = self.evaluate(stmt.start)
self.check_number_clause(stmt.start_token, start)
start_value = start
end_value: Optional[float] = None
inclusive: Optional[bool] = None
if stmt.end is not None:
end: Any = self.evaluate(stmt.end)
self.check_number_clause(stmt.end_token, end)
end_value = end
inclusive = stmt.end_token.type == TokenType.TO
step_value: float = 1
if stmt.step is not None:
step: Any = self.evaluate(stmt.step)
self.check_number_clause(stmt.step_token, step)
step_value = step
value: float = start_value
def condition() -> bool:
if end_value is None:
return False
match (step_value < 0, inclusive):
case (False, False):
return value < end_value
case (False, True):
return value <= end_value
case (True, False):
return value > end_value
case (True, True):
return value >= end_value
# Unreachable
return True
while condition():
self.env.define(stmt.variable.lexeme, value)
try:
self.execute(stmt.body)
except BreakException:
break
except ContinueException:
pass
value += step_value
self.env = previous_env
def visit_let_stmt(self, stmt: LetStmt) -> None:
value: Any = None
if stmt.initializer is not None:
value = self.evaluate(stmt.initializer)
self.env.define(stmt.name.lexeme, value)
def visit_break_stmt(self, stmt: BreakStmt) -> None:
raise BreakException()
def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
raise ContinueException()
@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, (int, float)):
return
raise PebbleRuntimeError(operator, "Operand must be a number.")
@staticmethod
def check_number_operands(operator: Token, left: Any, right: Any):
if isinstance(left, (int, float)) and isinstance(right, (int, float)):
return
raise PebbleRuntimeError(operator, "Operands must be numbers.")
@staticmethod
def check_number_clause(clause: Token, value: Any):
if isinstance(value, (int, float)):
return
raise PebbleRuntimeError(clause, "For loop clauses must be numbers.")
@staticmethod
def stringify(obj: Any) -> str:
if obj is None:
return "null"
if obj is True:
return "true"
if obj is False:
return "false"
if isinstance(obj, (int, float)):
if obj.is_integer():
obj = int(obj)
return str(obj)
return obj