240 lines
8.2 KiB
Python
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")
|