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_BREAK="break"
KW_CONTINUE="continue"
KW_CLASS="class"
WHITE_SPACE="regexp:\s+"
@@ -57,8 +58,9 @@
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 ;
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():
path: Path = Path("examples/basic/16_break_continue.peb")
path: Path = Path("examples/basic/17_class.peb")
Pebble.run_file(path)

View File

@@ -21,6 +21,10 @@ class Stmt(ABC):
def visit_block_stmt(self, stmt: BlockStmt) -> T:
...
@abstractmethod
def visit_class_stmt(self, stmt: ClassStmt) -> T:
...
@abstractmethod
def visit_expression_stmt(self, stmt: ExpressionStmt) -> T:
...
@@ -66,6 +70,15 @@ class BlockStmt(Stmt):
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)
class ExpressionStmt(Stmt):
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, \
CallExpr
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]):
@@ -70,6 +70,15 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
res += self.indented("}\n")
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:
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, \
CallExpr
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.function import PebbleFunction
from src.core.klass import PebbleClass
from src.interpreter.environment import Environment
from src.interpreter.error import PebbleRuntimeError
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:
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:
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, \
AssignExpr
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.token import Token
@@ -107,6 +107,10 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
self.resolve(*stmt.statements)
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:
self.resolve(stmt.expression)

View File

@@ -19,4 +19,5 @@ KEYWORDS: dict[str, TokenType] = {
"return": TokenType.RETURN,
"break": TokenType.BREAK,
"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, \
CallExpr
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.parser.error import ParsingError
from src.pebble import Pebble
@@ -84,6 +84,8 @@ class Parser:
def declaration(self) -> Optional[Stmt]:
try:
if self.match(TokenType.CLASS):
return self.class_declaration()
if self.match(TokenType.FUN):
return self.function("function")
if self.match(TokenType.LET):
@@ -93,7 +95,17 @@ class Parser:
self.synchronize()
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
name: Token = self.consume(TokenType.IDENTIFIER, f"Expected {kind} name.")
self.consume(TokenType.LEFT_PAREN, f"Expected '(' after {kind} name.")

View File

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