feat: add this keyword

This commit is contained in:
2026-02-06 22:50:17 +01:00
parent c6f1e05f07
commit 0871c7875a
12 changed files with 83 additions and 9 deletions

View File

@@ -47,6 +47,7 @@
KW_BREAK="break" KW_BREAK="break"
KW_CONTINUE="continue" KW_CONTINUE="continue"
KW_CLASS="class" KW_CLASS="class"
KW_THIS="this"
WHITE_SPACE="regexp:\s+" WHITE_SPACE="regexp:\s+"
@@ -97,7 +98,7 @@ factor ::= unary ( ( OP_SLASH | OP_STAR ) unary )* ;
unary ::= ( OP_BANG | OP_MINUS ) unary | call ; unary ::= ( OP_BANG | OP_MINUS ) unary | call ;
call ::= primary ( PUNC_LPAREN arguments? PUNC_RPAREN | PUNC_DOT IDENTIFIER )* ; call ::= primary ( PUNC_LPAREN arguments? PUNC_RPAREN | PUNC_DOT IDENTIFIER )* ;
primary ::= KW_TRUE | KW_FALSE | KW_NULL | 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 ;
function ::= IDENTIFIER PUNC_LPAREN parameters? PUNC_RPAREN block ; function ::= IDENTIFIER PUNC_LPAREN parameters? PUNC_RPAREN block ;
parameters ::= IDENTIFIER ( PUNC_COMMA IDENTIFIER )* ; parameters ::= IDENTIFIER ( PUNC_COMMA IDENTIFIER )* ;

View File

@@ -0,0 +1,19 @@
class Person {
get_fullname() {
return this.firstname + " " + this.lastname
}
greet(person) {
print("Hello " + person.get_fullname())
}
}
let alice = Person()
alice.firstname = "Alice"
alice.lastname = "Foo"
let bob = Person()
bob.firstname = "Bob"
bob.lastname = "Bar"
alice.greet(bob)
bob.greet(alice)

View File

@@ -4,7 +4,7 @@ from src.pebble import Pebble
def main(): def main():
path: Path = Path("examples/basic/18_fields.peb") path: Path = Path("examples/basic/19_this.peb")
Pebble.run_file(path) Pebble.run_file(path)

View File

@@ -57,6 +57,10 @@ class Expr(ABC):
def visit_set_expr(self, expr: SetExpr) -> T: def visit_set_expr(self, expr: SetExpr) -> T:
... ...
@abstractmethod
def visit_this_expr(self, expr: ThisExpr) -> T:
...
@dataclass(frozen=True) @dataclass(frozen=True)
class AssignExpr(Expr): class AssignExpr(Expr):
@@ -147,3 +151,11 @@ class SetExpr(Expr):
def accept(self, visitor: Expr.Visitor[T]) -> T: def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_set_expr(self) return visitor.visit_set_expr(self)
@dataclass(frozen=True)
class ThisExpr(Expr):
keyword: Token
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_this_expr(self)

View File

@@ -8,6 +8,7 @@ from src.interpreter.environment import Environment
from src.interpreter.exceptions import ReturnException from src.interpreter.exceptions import ReturnException
if TYPE_CHECKING: if TYPE_CHECKING:
from src.core.instance import PebbleInstance
from src.interpreter.interpreter import Interpreter from src.interpreter.interpreter import Interpreter
@@ -32,3 +33,8 @@ class PebbleFunction(PebbleCallable):
def __str__(self): def __str__(self):
return f"<function {self.declaration.name.lexeme}>" return f"<function {self.declaration.name.lexeme}>"
def bind(self, instance: PebbleInstance):
env: Environment = Environment(self.closure)
env.define("this", instance)
return PebbleFunction(self.declaration, env)

View File

@@ -24,7 +24,7 @@ class PebbleInstance:
except KeyError: except KeyError:
method: Optional[PebbleFunction] = self.klass.find_method(name.lexeme) method: Optional[PebbleFunction] = self.klass.find_method(name.lexeme)
if method is not None: if method is not None:
return method return method.bind(self)
raise PebbleRuntimeError(name, f"Undefined property '{name.lexeme}'.") raise PebbleRuntimeError(name, f"Undefined property '{name.lexeme}'.")
def set(self, name: Token, value: Any) -> None: def set(self, name: Token, value: Any) -> None:

View File

@@ -1,15 +1,22 @@
from enum import Enum, auto
from typing import Any from typing import Any
from src.ast.expr import Expr, VariableExpr, LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, AssignExpr, LogicalExpr, \ from src.ast.expr import Expr, VariableExpr, LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, AssignExpr, LogicalExpr, \
CallExpr, SetExpr, GetExpr CallExpr, SetExpr, GetExpr, ThisExpr, T
from src.ast.stmt import Stmt, LetStmt, IfStmt, ExpressionStmt, BlockStmt, WhileStmt, ForStmt, FunctionStmt, \ from src.ast.stmt import Stmt, LetStmt, IfStmt, ExpressionStmt, BlockStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
class ClassType(Enum):
NONE = auto()
CLASS = auto()
class Formatter(Expr.Visitor[str], Stmt.Visitor[str]): class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
def __init__(self, indent: int = 4): def __init__(self, indent: int = 4):
self.indent: int = indent self.indent: int = indent
self.level: int = 0 self.level: int = 0
self.current_class: ClassType = ClassType.NONE
def indented(self, text: str) -> str: def indented(self, text: str) -> str:
return " " * (self.level * self.indent) + text return " " * (self.level * self.indent) + text
@@ -36,6 +43,9 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
def visit_set_expr(self, expr: SetExpr) -> str: def visit_set_expr(self, expr: SetExpr) -> str:
return f"{self.format(expr.object)}.{expr.name.lexeme} = {self.format(expr.value)}" return f"{self.format(expr.object)}.{expr.name.lexeme} = {self.format(expr.value)}"
def visit_this_expr(self, expr: ThisExpr) -> str:
return expr.keyword.lexeme
def visit_unary_expr(self, expr: UnaryExpr) -> str: def visit_unary_expr(self, expr: UnaryExpr) -> str:
return f"{expr.operator.lexeme}{self.format(expr.right)}" return f"{expr.operator.lexeme}{self.format(expr.right)}"
@@ -79,8 +89,11 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
def visit_class_stmt(self, stmt: ClassStmt) -> str: def visit_class_stmt(self, stmt: ClassStmt) -> str:
res: str = self.indented(f"class {stmt.name.lexeme} {{\n") res: str = self.indented(f"class {stmt.name.lexeme} {{\n")
self.level += 1 self.level += 1
enclosing_class: ClassType = self.current_class
self.current_class = ClassType.CLASS
for method in stmt.methods: for method in stmt.methods:
res += self.format(method) res += self.format(method)
self.current_class = enclosing_class
self.level -= 1 self.level -= 1
res += self.indented("}\n") res += self.indented("}\n")
return res return res
@@ -89,12 +102,18 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
return self.indented(self.format(stmt.expression) + "\n") return self.indented(self.format(stmt.expression) + "\n")
def visit_function_stmt(self, stmt: FunctionStmt) -> str: def visit_function_stmt(self, stmt: FunctionStmt) -> str:
res: str = self.indented(f"fun {stmt.name.lexeme}") res: str = self.indented("")
if self.current_class != ClassType.CLASS:
res += "fun "
res += stmt.name.lexeme
res += f"({', '.join(param.lexeme for param in stmt.params)}) " res += f"({', '.join(param.lexeme for param in stmt.params)}) "
res += "{\n" res += "{\n"
self.level += 1 self.level += 1
enclosing_class: ClassType = self.current_class
self.current_class = ClassType.NONE
for sub_stmt in stmt.body: for sub_stmt in stmt.body:
res += self.format(sub_stmt) res += self.format(sub_stmt)
self.current_class = enclosing_class
self.level -= 1 self.level -= 1
res += self.indented("}\n") res += self.indented("}\n")
return res return res

View File

@@ -1,7 +1,7 @@
from typing import Any, Optional from typing import Any, Optional
from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr, \ from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr, GetExpr, T, SetExpr CallExpr, GetExpr, T, SetExpr, ThisExpr
from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
from src.core.callable import PebbleCallable from src.core.callable import PebbleCallable
@@ -87,6 +87,9 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
obj.set(expr.name, value) obj.set(expr.name, value)
return value return value
def visit_this_expr(self, expr: ThisExpr) -> Any:
return self.look_up_variable(expr.keyword, expr)
def visit_binary_expr(self, expr: BinaryExpr) -> Any: def visit_binary_expr(self, expr: BinaryExpr) -> Any:
left: Any = self.evaluate(expr.left) left: Any = self.evaluate(expr.left)
right: Any = self.evaluate(expr.right) right: Any = self.evaluate(expr.right)

View File

@@ -4,7 +4,7 @@ from enum import Enum, auto
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from src.ast.expr import Expr, LogicalExpr, VariableExpr, LiteralExpr, GroupingExpr, CallExpr, UnaryExpr, BinaryExpr, \ from src.ast.expr import Expr, LogicalExpr, VariableExpr, LiteralExpr, GroupingExpr, CallExpr, UnaryExpr, BinaryExpr, \
AssignExpr, GetExpr, SetExpr AssignExpr, GetExpr, SetExpr, ThisExpr
from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, IfStmt, FunctionStmt, \ from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, IfStmt, FunctionStmt, \
ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt
from src.pebble import Pebble from src.pebble import Pebble
@@ -110,6 +110,9 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
self.resolve(expr.value) self.resolve(expr.value)
self.resolve(expr.object) self.resolve(expr.object)
def visit_this_expr(self, expr: ThisExpr) -> None:
self.resolve_local(expr, expr.keyword)
def visit_block_stmt(self, stmt: BlockStmt) -> None: def visit_block_stmt(self, stmt: BlockStmt) -> None:
self.begin_scope() self.begin_scope()
self.resolve(*stmt.statements) self.resolve(*stmt.statements)
@@ -117,9 +120,15 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
def visit_class_stmt(self, stmt: ClassStmt) -> None: def visit_class_stmt(self, stmt: ClassStmt) -> None:
self.declare(stmt.name) self.declare(stmt.name)
self.define(stmt.name)
self.begin_scope()
self.scopes[-1]["this"] = True
for method in stmt.methods: for method in stmt.methods:
self.resolve_function(method, FunctionType.METHOD) self.resolve_function(method, FunctionType.METHOD)
self.define(stmt.name)
self.end_scope()
def visit_expression_stmt(self, stmt: ExpressionStmt) -> None: def visit_expression_stmt(self, stmt: ExpressionStmt) -> None:
self.resolve(stmt.expression) self.resolve(stmt.expression)

View File

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

View File

@@ -1,7 +1,7 @@
from typing import Optional from typing import Optional
from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr, LogicalExpr, \ from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr, GetExpr, SetExpr CallExpr, GetExpr, SetExpr, ThisExpr
from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
from src.consts import MAX_FUNCTION_ARGS from src.consts import MAX_FUNCTION_ARGS
@@ -363,6 +363,9 @@ class Parser:
if self.match(TokenType.NUMBER, TokenType.STRING): if self.match(TokenType.NUMBER, TokenType.STRING):
return LiteralExpr(self.previous().value) return LiteralExpr(self.previous().value)
if self.match(TokenType.THIS):
return ThisExpr(self.previous())
if self.match(TokenType.IDENTIFIER): if self.match(TokenType.IDENTIFIER):
return VariableExpr(self.previous()) return VariableExpr(self.previous())

View File

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