feat: add super keyword and parent method access
This commit is contained in:
@@ -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 )* ;
|
||||
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
|
||||
@@ -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)}"
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -21,4 +21,5 @@ KEYWORDS: dict[str, TokenType] = {
|
||||
"continue": TokenType.CONTINUE,
|
||||
"class": TokenType.CLASS,
|
||||
"this": TokenType.THIS,
|
||||
"super": TokenType.SUPER,
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ class TokenType(Enum):
|
||||
CONTINUE = auto()
|
||||
CLASS = auto()
|
||||
THIS = auto()
|
||||
SUPER = auto()
|
||||
|
||||
# Misc
|
||||
COMMENT = auto()
|
||||
|
||||
Reference in New Issue
Block a user