From 5cfe15722087ee09fd36b1b779e873eb61ba9c2d Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Fri, 6 Feb 2026 20:59:03 +0100 Subject: [PATCH] feat: add basic class definition --- docs/grammar.bnf | 4 +++- examples/basic/17_class.peb | 11 +++++++++++ main.py | 2 +- src/ast/stmt.py | 13 +++++++++++++ src/core/klass.py | 6 ++++++ src/formatter.py | 11 ++++++++++- src/interpreter/interpreter.py | 8 +++++++- src/interpreter/resolver.py | 6 +++++- src/keyword.py | 1 + src/parser/parser.py | 16 ++++++++++++++-- src/token.py | 1 + 11 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 examples/basic/17_class.peb create mode 100644 src/core/klass.py diff --git a/docs/grammar.bnf b/docs/grammar.bnf index 75cd42e..6d8d6ae 100644 --- a/docs/grammar.bnf +++ b/docs/grammar.bnf @@ -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* <> ; -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 )? ; diff --git a/examples/basic/17_class.peb b/examples/basic/17_class.peb new file mode 100644 index 0000000..9275314 --- /dev/null +++ b/examples/basic/17_class.peb @@ -0,0 +1,11 @@ +class Breakfast { + cook() { + print("Frying some eggs") + } + + serve(who) { + print("Enjoy your breakfast, " + who + ".") + } +} + +print(Breakfast) \ No newline at end of file diff --git a/main.py b/main.py index f968363..c44e010 100644 --- a/main.py +++ b/main.py @@ -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) diff --git a/src/ast/stmt.py b/src/ast/stmt.py index 81401b0..84ddbdb 100644 --- a/src/ast/stmt.py +++ b/src/ast/stmt.py @@ -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 diff --git a/src/core/klass.py b/src/core/klass.py new file mode 100644 index 0000000..afb7fe9 --- /dev/null +++ b/src/core/klass.py @@ -0,0 +1,6 @@ +class PebbleClass: + def __init__(self, name: str): + self.name: str = name + + def __str__(self): + return self.name diff --git a/src/formatter.py b/src/formatter.py index 24032c5..7c56e90 100644 --- a/src/formatter.py +++ b/src/formatter.py @@ -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") diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py index 5c16dbd..92583ed 100644 --- a/src/interpreter/interpreter.py +++ b/src/interpreter/interpreter.py @@ -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) diff --git a/src/interpreter/resolver.py b/src/interpreter/resolver.py index ef231c8..c904760 100644 --- a/src/interpreter/resolver.py +++ b/src/interpreter/resolver.py @@ -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) diff --git a/src/keyword.py b/src/keyword.py index 1489a7c..d2df6fc 100644 --- a/src/keyword.py +++ b/src/keyword.py @@ -19,4 +19,5 @@ KEYWORDS: dict[str, TokenType] = { "return": TokenType.RETURN, "break": TokenType.BREAK, "continue": TokenType.CONTINUE, + "class": TokenType.CLASS, } diff --git a/src/parser/parser.py b/src/parser/parser.py index f8a7abf..e8dccc3 100644 --- a/src/parser/parser.py +++ b/src/parser/parser.py @@ -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.") diff --git a/src/token.py b/src/token.py index c32754d..57695fd 100644 --- a/src/token.py +++ b/src/token.py @@ -58,6 +58,7 @@ class TokenType(Enum): RETURN = auto() BREAK = auto() CONTINUE = auto() + CLASS = auto() # Misc COMMENT = auto()