feat: add basic class definition

This commit is contained in:
2026-02-06 20:59:03 +01:00
parent dde5453e75
commit 5cfe157220
11 changed files with 72 additions and 7 deletions

View File

@@ -46,6 +46,7 @@
KW_RETURN="return" KW_RETURN="return"
KW_BREAK="break" KW_BREAK="break"
KW_CONTINUE="continue" KW_CONTINUE="continue"
KW_CLASS="class"
WHITE_SPACE="regexp:\s+" WHITE_SPACE="regexp:\s+"
@@ -57,8 +58,9 @@
root ::= declaration* <<eof>> ; root ::= declaration* <<eof>> ;
declaration ::= funDecl | varDecl | statement ; declaration ::= classDecl | funDecl | varDecl | statement ;
classDecl ::= KW_CLASS IDENTIFIER PUNC_LBRACE function* PUNC_RBRACE ;
funDecl ::= KW_FUN function ; funDecl ::= KW_FUN function ;
varDecl ::= KW_LET IDENTIFIER ( OP_EQUAL expression )? ; varDecl ::= KW_LET IDENTIFIER ( OP_EQUAL expression )? ;

View File

@@ -0,0 +1,11 @@
class Breakfast {
cook() {
print("Frying some eggs")
}
serve(who) {
print("Enjoy your breakfast, " + who + ".")
}
}
print(Breakfast)

View File

@@ -4,7 +4,7 @@ from src.pebble import Pebble
def main(): def main():
path: Path = Path("examples/basic/16_break_continue.peb") path: Path = Path("examples/basic/17_class.peb")
Pebble.run_file(path) Pebble.run_file(path)

View File

@@ -21,6 +21,10 @@ class Stmt(ABC):
def visit_block_stmt(self, stmt: BlockStmt) -> T: def visit_block_stmt(self, stmt: BlockStmt) -> T:
... ...
@abstractmethod
def visit_class_stmt(self, stmt: ClassStmt) -> T:
...
@abstractmethod @abstractmethod
def visit_expression_stmt(self, stmt: ExpressionStmt) -> T: def visit_expression_stmt(self, stmt: ExpressionStmt) -> T:
... ...
@@ -66,6 +70,15 @@ class BlockStmt(Stmt):
return visitor.visit_block_stmt(self) return visitor.visit_block_stmt(self)
@dataclass(frozen=True)
class ClassStmt(Stmt):
name: Token
methods: list[FunctionStmt]
def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_class_stmt(self)
@dataclass(frozen=True) @dataclass(frozen=True)
class ExpressionStmt(Stmt): class ExpressionStmt(Stmt):
expression: Expr expression: Expr

6
src/core/klass.py Normal file
View File

@@ -0,0 +1,6 @@
class PebbleClass:
def __init__(self, name: str):
self.name: str = name
def __str__(self):
return self.name

View File

@@ -3,7 +3,7 @@ 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 CallExpr
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 ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
class Formatter(Expr.Visitor[str], Stmt.Visitor[str]): class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
@@ -70,6 +70,15 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
res += self.indented("}\n") res += self.indented("}\n")
return res return res
def visit_class_stmt(self, stmt: ClassStmt) -> str:
res: str = self.indented(f"class {stmt.name.lexeme} {{\n")
self.level += 1
for method in stmt.methods:
res += self.format(method)
self.level -= 1
res += self.indented("}\n")
return res
def visit_expression_stmt(self, stmt: ExpressionStmt) -> str: def visit_expression_stmt(self, stmt: ExpressionStmt) -> str:
return self.indented(self.format(stmt.expression) + "\n") return self.indented(self.format(stmt.expression) + "\n")

View File

@@ -3,9 +3,10 @@ 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 CallExpr
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 ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
from src.core.callable import PebbleCallable from src.core.callable import PebbleCallable
from src.core.function import PebbleFunction from src.core.function import PebbleFunction
from src.core.klass import PebbleClass
from src.interpreter.environment import Environment from src.interpreter.environment import Environment
from src.interpreter.error import PebbleRuntimeError from src.interpreter.error import PebbleRuntimeError
from src.interpreter.exceptions import ReturnException, BreakException, ContinueException from src.interpreter.exceptions import ReturnException, BreakException, ContinueException
@@ -154,6 +155,11 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
def visit_block_stmt(self, stmt: BlockStmt) -> None: def visit_block_stmt(self, stmt: BlockStmt) -> None:
self.execute_block(stmt.statements, Environment(self.env)) self.execute_block(stmt.statements, Environment(self.env))
def visit_class_stmt(self, stmt: ClassStmt) -> None:
self.env.define(stmt.name.lexeme, None)
klass: PebbleClass = PebbleClass(stmt.name.lexeme)
self.env.assign(stmt.name, klass)
def visit_expression_stmt(self, stmt: ExpressionStmt) -> None: def visit_expression_stmt(self, stmt: ExpressionStmt) -> None:
self.evaluate(stmt.expression) self.evaluate(stmt.expression)

View File

@@ -6,7 +6,7 @@ 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 AssignExpr
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 ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt
from src.pebble import Pebble from src.pebble import Pebble
from src.token import Token from src.token import Token
@@ -107,6 +107,10 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
self.resolve(*stmt.statements) self.resolve(*stmt.statements)
self.end_scope() self.end_scope()
def visit_class_stmt(self, stmt: ClassStmt) -> None:
self.declare(stmt.name)
self.define(stmt.name)
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

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

View File

@@ -3,7 +3,7 @@ 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 CallExpr
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 ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
from src.consts import MAX_FUNCTION_ARGS from src.consts import MAX_FUNCTION_ARGS
from src.parser.error import ParsingError from src.parser.error import ParsingError
from src.pebble import Pebble from src.pebble import Pebble
@@ -84,6 +84,8 @@ class Parser:
def declaration(self) -> Optional[Stmt]: def declaration(self) -> Optional[Stmt]:
try: try:
if self.match(TokenType.CLASS):
return self.class_declaration()
if self.match(TokenType.FUN): if self.match(TokenType.FUN):
return self.function("function") return self.function("function")
if self.match(TokenType.LET): if self.match(TokenType.LET):
@@ -93,7 +95,17 @@ class Parser:
self.synchronize() self.synchronize()
return None return None
def function(self, kind: str) -> Stmt: def class_declaration(self) -> Stmt:
name: Token = self.consume(TokenType.IDENTIFIER, "Expected class name.")
self.consume(TokenType.LEFT_BRACE, "Expected '{' before class body.")
methods: list[FunctionStmt] = []
while not self.check(TokenType.RIGHT_BRACE) and not self.is_at_end():
methods.append(self.function("method"))
self.consume(TokenType.RIGHT_BRACE, "Expected '}' after class body.")
return ClassStmt(name, methods)
def function(self, kind: str) -> FunctionStmt:
# TODO: allow anonymous/lambda functions # TODO: allow anonymous/lambda functions
name: Token = self.consume(TokenType.IDENTIFIER, f"Expected {kind} name.") name: Token = self.consume(TokenType.IDENTIFIER, f"Expected {kind} name.")
self.consume(TokenType.LEFT_PAREN, f"Expected '(' after {kind} name.") self.consume(TokenType.LEFT_PAREN, f"Expected '(' after {kind} name.")

View File

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