316 lines
11 KiB
Python
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
|