Files
pebble/src/interpreter/resolver.py
2026-02-08 19:37:48 +01:00

240 lines
8.2 KiB
Python

from __future__ import annotations
from enum import Enum, auto
from typing import TYPE_CHECKING
from src.ast.expr import Expr, LogicalExpr, VariableExpr, LiteralExpr, GroupingExpr, CallExpr, UnaryExpr, BinaryExpr, \
AssignExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr
from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, IfStmt, FunctionStmt, \
ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt
from src.consts import CONSTRUCTOR_NAME
from src.pebble import Pebble
from src.token.token import Token
if TYPE_CHECKING:
from src.interpreter.interpreter import Interpreter
class FunctionType(Enum):
NONE = auto()
FUNCTION = auto()
INITIALIZER = auto()
METHOD = auto()
class LoopType(Enum):
NONE = auto()
WHILE = auto()
FOR = auto()
class ClassType(Enum):
NONE = auto()
CLASS = auto()
SUBCLASS = auto()
class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
def __init__(self, interpreter: Interpreter):
self.interpreter: Interpreter = interpreter
self.scopes: list[dict[str, bool]] = []
self.current_func: FunctionType = FunctionType.NONE
self.current_loop: LoopType = LoopType.NONE
self.current_class: ClassType = ClassType.NONE
def resolve(self, *objects: Expr | Stmt) -> None:
for obj in objects:
obj.accept(self)
def begin_scope(self) -> None:
self.scopes.append({})
def end_scope(self) -> None:
self.scopes.pop()
def declare(self, name: Token) -> None:
if len(self.scopes) == 0:
return
scope: dict[str, bool] = self.scopes[-1]
if name.lexeme in scope:
Pebble.token_error(name, "A variable with this name is already declared in this scope.")
scope[name.lexeme] = False
def define(self, name: Token) -> None:
if len(self.scopes) == 0:
return
self.scopes[-1][name.lexeme] = True
def resolve_local(self, expr: Expr, name: Token) -> None:
for i, scope in enumerate(reversed(self.scopes)):
if name.lexeme in scope:
self.interpreter.resolve(expr, i)
def resolve_function(self, function: FunctionStmt, type: FunctionType) -> None:
enclosing_func: FunctionType = self.current_func
self.current_func = type
self.begin_scope()
for param in function.params:
self.declare(param)
self.define(param)
self.resolve(*function.body)
self.end_scope()
self.current_func = enclosing_func
def visit_assign_expr(self, expr: AssignExpr) -> None:
self.resolve(expr.value)
self.resolve_local(expr, expr.name)
def visit_binary_expr(self, expr: BinaryExpr) -> None:
self.resolve(expr.left)
self.resolve(expr.right)
def visit_unary_expr(self, expr: UnaryExpr) -> None:
self.resolve(expr.right)
def visit_call_expr(self, expr: CallExpr) -> None:
self.resolve(expr.callee)
for arg in expr.arguments:
self.resolve(arg)
def visit_get_expr(self, expr: GetExpr) -> None:
self.resolve(expr.object)
def visit_grouping_expr(self, expr: GroupingExpr) -> None:
self.resolve(expr.expression)
def visit_literal_expr(self, expr: LiteralExpr) -> None:
pass
def visit_fstring_expr(self, expr: FStringExpr) -> None:
for part in expr.parts:
self.resolve(part)
def visit_fstring_embed_expr(self, expr: FStringEmbedExpr) -> None:
self.resolve(expr.expression)
def visit_list_expr(self, expr: ListExpr) -> None:
for item in expr.items:
self.resolve(item)
def visit_variable_expr(self, expr: VariableExpr) -> None:
if len(self.scopes) != 0 and self.scopes[-1].get(expr.name.lexeme) is False:
Pebble.token_error(expr.name, "Variable is not initialized.")
self.resolve_local(expr, expr.name)
def visit_logical_expr(self, expr: LogicalExpr) -> None:
self.resolve(expr.left)
self.resolve(expr.right)
def visit_set_expr(self, expr: SetExpr) -> None:
self.resolve(expr.value)
self.resolve(expr.object)
def visit_this_expr(self, expr: ThisExpr) -> None:
if self.current_class == ClassType.NONE:
Pebble.token_error(expr.keyword, "Cannot use 'this' outside of a class.")
self.resolve_local(expr, expr.keyword)
def visit_super_expr(self, expr: SuperExpr) -> None:
if self.current_class == ClassType.NONE:
Pebble.token_error(expr.keyword, "Cannot use 'super' outside of a class.")
elif self.current_class != ClassType.SUBCLASS:
Pebble.token_error(expr.keyword, "Cannot use 'super' in a class with no superclass.")
self.resolve_local(expr, expr.keyword)
def visit_block_stmt(self, stmt: BlockStmt) -> None:
self.begin_scope()
self.resolve(*stmt.statements)
self.end_scope()
def visit_class_stmt(self, stmt: ClassStmt) -> None:
enclosing_class: ClassType = self.current_class
self.current_class = ClassType.CLASS
self.declare(stmt.name)
self.define(stmt.name)
if stmt.superclass is not None:
if stmt.name.lexeme == stmt.superclass.name.lexeme:
Pebble.token_error(stmt.superclass.name, "A class cannot inherit from itself.")
self.current_class = ClassType.SUBCLASS
self.resolve(stmt.superclass)
self.begin_scope()
self.scopes[-1]["super"] = True
self.begin_scope()
self.scopes[-1]["this"] = True
for method in stmt.methods:
declaration: FunctionType = FunctionType.METHOD
if method.name.lexeme == CONSTRUCTOR_NAME:
declaration = FunctionType.INITIALIZER
self.resolve_function(method, declaration)
self.end_scope()
if stmt.superclass is not None:
self.end_scope()
self.current_class = enclosing_class
def visit_expression_stmt(self, stmt: ExpressionStmt) -> None:
self.resolve(stmt.expression)
def visit_function_stmt(self, stmt: FunctionStmt) -> None:
self.declare(stmt.name)
self.define(stmt.name)
self.resolve_function(stmt, FunctionType.FUNCTION)
def visit_if_stmt(self, stmt: IfStmt) -> None:
self.resolve(stmt.condition)
self.resolve(stmt.then_branch)
if stmt.else_branch is not None:
self.resolve(stmt.else_branch)
def visit_return_stmt(self, stmt: ReturnStmt) -> None:
if self.current_func == FunctionType.NONE:
Pebble.token_error(stmt.keyword, "Cannot return from top-level scope.")
if stmt.value is not None:
if self.current_func == FunctionType.INITIALIZER:
Pebble.token_error(stmt.keyword, "Cannot return a value from an initializer.")
self.resolve(stmt.value)
def visit_let_stmt(self, stmt: LetStmt) -> None:
self.declare(stmt.name)
if stmt.initializer is not None:
self.resolve(stmt.initializer)
self.define(stmt.name)
def visit_while_stmt(self, stmt: WhileStmt) -> None:
enclosing_loop: LoopType = self.current_loop
self.current_loop = LoopType.WHILE
self.resolve(stmt.condition)
self.resolve(stmt.body)
self.current_loop = enclosing_loop
def visit_for_stmt(self, stmt: ForStmt) -> None:
enclosing_loop: LoopType = self.current_loop
self.current_loop = LoopType.FOR
self.begin_scope()
self.declare(stmt.variable)
self.define(stmt.variable)
if stmt.start is not None:
self.resolve(stmt.start)
if stmt.end is not None:
self.resolve(stmt.end)
if stmt.step is not None:
self.resolve(stmt.step)
self.resolve(stmt.body)
self.end_scope()
self.current_loop = enclosing_loop
def visit_break_stmt(self, stmt: BreakStmt) -> None:
if self.current_loop == LoopType.NONE:
Pebble.token_error(stmt.keyword, "Cannot break outside a loop")
def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
if self.current_loop == LoopType.NONE:
Pebble.token_error(stmt.keyword, "Cannot continue outside a loop")