feat: add super keyword and parent method access

This commit is contained in:
2026-02-07 00:25:48 +01:00
parent 9e4855598f
commit 133d332f6a
9 changed files with 71 additions and 6 deletions

View File

@@ -48,6 +48,7 @@
KW_CONTINUE="continue"
KW_CLASS="class"
KW_THIS="this"
KW_SUPER="super"
WHITE_SPACE="regexp:\s+"
@@ -98,7 +99,7 @@ factor ::= unary ( ( OP_SLASH | OP_STAR ) unary )* ;
unary ::= ( OP_BANG | OP_MINUS ) unary | call ;
call ::= primary ( PUNC_LPAREN arguments? PUNC_RPAREN | PUNC_DOT IDENTIFIER )* ;
primary ::= KW_TRUE | KW_FALSE | KW_NULL | KW_THIS | NUMBER | STRING | IDENTIFIER | PUNC_LPAREN expression PUNC_RPAREN ;
primary ::= KW_TRUE | KW_FALSE | KW_NULL | KW_THIS | NUMBER | STRING | IDENTIFIER | PUNC_LPAREN expression PUNC_RPAREN | KW_SUPER PUNC_DOT IDENTIFIER ;
function ::= IDENTIFIER PUNC_LPAREN parameters? PUNC_RPAREN block ;
parameters ::= IDENTIFIER ( PUNC_COMMA IDENTIFIER )* ;

View File

@@ -6,6 +6,10 @@ class Employee {
has_responsibilities() {
return false
}
get_title() {
return "Employee"
}
}
class Manager < Employee {
@@ -22,6 +26,10 @@ class Boss < Manager {
get_salary() {
return 500
}
get_title() {
return super.get_title() + ", CEO"
}
}
let employee = Employee()
@@ -35,3 +43,7 @@ print(boss, boss.get_salary())
print(employee, employee.has_responsibilities())
print(manager, manager.has_responsibilities())
print(boss, boss.has_responsibilities())
print(employee, employee.get_title())
print(manager, manager.get_title())
print(boss, boss.get_title())

View File

@@ -61,6 +61,10 @@ class Expr(ABC):
def visit_this_expr(self, expr: ThisExpr) -> T:
...
@abstractmethod
def visit_super_expr(self, expr: SuperExpr) -> T:
...
@dataclass(frozen=True)
class AssignExpr(Expr):
@@ -159,3 +163,12 @@ class ThisExpr(Expr):
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_this_expr(self)
@dataclass(frozen=True)
class SuperExpr(Expr):
keyword: Token
method: Token
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_super_expr(self)

View File

@@ -2,7 +2,7 @@ from enum import Enum, auto
from typing import Any
from src.ast.expr import Expr, VariableExpr, LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, AssignExpr, LogicalExpr, \
CallExpr, SetExpr, GetExpr, ThisExpr, T
CallExpr, SetExpr, GetExpr, ThisExpr, T, SuperExpr
from src.ast.stmt import Stmt, LetStmt, IfStmt, ExpressionStmt, BlockStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
@@ -46,6 +46,9 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
def visit_this_expr(self, expr: ThisExpr) -> str:
return expr.keyword.lexeme
def visit_super_expr(self, expr: SuperExpr) -> str:
return f"{expr.keyword.lexeme}.{expr.method.lexeme}"
def visit_unary_expr(self, expr: UnaryExpr) -> str:
return f"{expr.operator.lexeme}{self.format(expr.right)}"

View File

@@ -1,7 +1,7 @@
from typing import Any, Optional
from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr, GetExpr, T, SetExpr, ThisExpr
CallExpr, GetExpr, T, SetExpr, ThisExpr, SuperExpr
from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
from src.consts import CONSTRUCTOR_NAME
@@ -91,6 +91,15 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
def visit_this_expr(self, expr: ThisExpr) -> Any:
return self.look_up_variable(expr.keyword, expr)
def visit_super_expr(self, expr: SuperExpr) -> Any:
distance: int = self.locals.get(expr)
superclass: PebbleClass = self.env.get_at(distance, "super")
obj: PebbleInstance = self.env.get_at(distance - 1, "this")
method: Optional[PebbleFunction] = superclass.find_method(expr.method.lexeme)
if method is None:
raise PebbleRuntimeError(expr.method, f"Undefined property '{expr.method.lexeme}'.")
return method.bind(obj)
def visit_binary_expr(self, expr: BinaryExpr) -> Any:
left: Any = self.evaluate(expr.left)
right: Any = self.evaluate(expr.right)
@@ -184,11 +193,20 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
raise PebbleRuntimeError(stmt.superclass.name, "Superclass must be a class.")
self.env.define(stmt.name.lexeme, None)
if stmt.superclass is not None:
self.env = Environment(self.env)
self.env.define("super", superclass)
methods: dict[str, PebbleFunction] = {}
for method in stmt.methods:
func: PebbleFunction = PebbleFunction(method, self.env, method.name.lexeme == CONSTRUCTOR_NAME)
methods[method.name.lexeme] = func
klass: PebbleClass = PebbleClass(stmt.name.lexeme, superclass, methods)
if superclass is not None:
self.env = self.env.enclosing
self.env.assign(stmt.name, klass)
def visit_expression_stmt(self, stmt: ExpressionStmt) -> None:

View File

@@ -4,7 +4,7 @@ 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
AssignExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, T
from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, IfStmt, FunctionStmt, \
ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt
from src.consts import CONSTRUCTOR_NAME
@@ -123,6 +123,9 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[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:
self.resolve_local(expr, expr.keyword)
def visit_block_stmt(self, stmt: BlockStmt) -> None:
self.begin_scope()
self.resolve(*stmt.statements)
@@ -139,6 +142,9 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
Pebble.token_error(stmt.superclass.name, "A class cannot inherit from itself.")
self.resolve(stmt.superclass)
self.begin_scope()
self.scopes[-1]["super"] = True
self.begin_scope()
self.scopes[-1]["this"] = True
@@ -149,6 +155,10 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
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:

View File

@@ -21,4 +21,5 @@ KEYWORDS: dict[str, TokenType] = {
"continue": TokenType.CONTINUE,
"class": TokenType.CLASS,
"this": TokenType.THIS,
"super": TokenType.SUPER,
}

View File

@@ -1,7 +1,7 @@
from typing import Optional
from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr, GetExpr, SetExpr, ThisExpr
CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr
from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
from src.consts import MAX_FUNCTION_ARGS
@@ -367,6 +367,12 @@ class Parser:
if self.match(TokenType.NUMBER, TokenType.STRING):
return LiteralExpr(self.previous().value)
if self.match(TokenType.SUPER):
keyword: Token = self.previous()
self.consume(TokenType.DOT, "Expected '.' after 'super'.")
method: Token = self.consume(TokenType.IDENTIFIER, "Expected superclass method name.")
return SuperExpr(keyword, method)
if self.match(TokenType.THIS):
return ThisExpr(self.previous())

View File

@@ -60,6 +60,7 @@ class TokenType(Enum):
CONTINUE = auto()
CLASS = auto()
THIS = auto()
SUPER = auto()
# Misc
COMMENT = auto()