feat: add instance field setter

This commit is contained in:
2026-02-06 21:38:27 +01:00
parent b06bce2558
commit e070492f69
9 changed files with 64 additions and 17 deletions

View File

@@ -86,7 +86,7 @@ continueStmt ::= KW_CONTINUE ;
expression ::= assignment ; expression ::= assignment ;
assignment ::= IDENTIFIER OP_EQUAL assignment | logic_or ; assignment ::= ( call PUNC_DOT )? IDENTIFIER OP_EQUAL assignment | logic_or ;
logic_or ::= logic_and ( KW_OR logic_and )* ; logic_or ::= logic_and ( KW_OR logic_and )* ;
logic_and ::= equality ( KW_AND equality )* ; logic_and ::= equality ( KW_AND equality )* ;

View File

@@ -0,0 +1,11 @@
class Person {}
fun greet(person) {
print("Hello " + person.firstname + " " + person.lastname)
}
let bob = Person()
bob.firstname = "Bob"
bob.lastname = "Builder"
greet(bob)

View File

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

View File

@@ -53,6 +53,10 @@ class Expr(ABC):
def visit_logical_expr(self, expr: LogicalExpr) -> T: def visit_logical_expr(self, expr: LogicalExpr) -> T:
... ...
@abstractmethod
def visit_set_expr(self, expr: SetExpr) -> T:
...
@dataclass(frozen=True) @dataclass(frozen=True)
class AssignExpr(Expr): class AssignExpr(Expr):
@@ -133,3 +137,13 @@ class LogicalExpr(Expr):
def accept(self, visitor: Expr.Visitor[T]) -> T: def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_logical_expr(self) return visitor.visit_logical_expr(self)
@dataclass(frozen=True)
class SetExpr(Expr):
object: Expr
name: Token
value: Expr
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_set_expr(self)

View File

@@ -22,3 +22,6 @@ class PebbleInstance:
return self.fields[name.lexeme] return self.fields[name.lexeme]
except KeyError: except KeyError:
raise PebbleRuntimeError(name, f"Undefined property '{name.lexeme}'.") raise PebbleRuntimeError(name, f"Undefined property '{name.lexeme}'.")
def set(self, name: Token, value: Any) -> None:
self.fields[name.lexeme] = value

View File

@@ -1,7 +1,7 @@
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 CallExpr, SetExpr, GetExpr
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
@@ -33,12 +33,18 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
def visit_logical_expr(self, expr: LogicalExpr) -> str: def visit_logical_expr(self, expr: LogicalExpr) -> str:
return f"{self.format(expr.left)} {expr.operator.lexeme} {self.format(expr.right)}" return f"{self.format(expr.left)} {expr.operator.lexeme} {self.format(expr.right)}"
def visit_set_expr(self, expr: SetExpr) -> str:
return f"{self.format(expr.object)}.{expr.name.lexeme} = {self.format(expr.value)}"
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)}"
def visit_call_expr(self, expr: CallExpr) -> str: def visit_call_expr(self, expr: CallExpr) -> str:
return f"{self.format(expr.callee)}({', '.join(self.format(arg) for arg in expr.arguments)})" return f"{self.format(expr.callee)}({', '.join(self.format(arg) for arg in expr.arguments)})"
def visit_get_expr(self, expr: GetExpr) -> str:
return f"{self.format(expr.object)}.{expr.name.lexeme}"
def visit_grouping_expr(self, expr: GroupingExpr) -> str: def visit_grouping_expr(self, expr: GroupingExpr) -> str:
return f"({self.format(expr.expression)})" return f"({self.format(expr.expression)})"

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 CallExpr, GetExpr, T, SetExpr
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
@@ -77,6 +77,16 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
raise PebbleRuntimeError(expr.operator, f"Unknown logical operator") raise PebbleRuntimeError(expr.operator, f"Unknown logical operator")
return self.evaluate(expr.right) return self.evaluate(expr.right)
def visit_set_expr(self, expr: SetExpr) -> Any:
obj: Any = self.evaluate(expr.object)
if not isinstance(obj, PebbleInstance):
raise PebbleRuntimeError(expr.name, "Only class instances have fields.")
value: Any = self.evaluate(expr.value)
obj.set(expr.name, value)
return value
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 AssignExpr, GetExpr, SetExpr
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
@@ -105,6 +105,10 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
self.resolve(expr.left) self.resolve(expr.left)
self.resolve(expr.right) self.resolve(expr.right)
def visit_set_expr(self, expr: SetExpr) -> None:
self.resolve(expr.value)
self.resolve(expr.object)
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)

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 CallExpr, GetExpr, SetExpr
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
@@ -257,19 +257,18 @@ class Parser:
TokenType.SLASH_EQUAL): TokenType.SLASH_EQUAL):
operator: Token = self.previous() operator: Token = self.previous()
value: Expr = self.assignment() value: Expr = self.assignment()
if isinstance(expr, VariableExpr): if operator.type != TokenType.EQUAL:
name: Token = expr.name value = BinaryExpr(
if operator.type == TokenType.EQUAL: expr,
return AssignExpr(name, value)
else:
return AssignExpr(
name,
BinaryExpr(
VariableExpr(name),
operator, operator,
value value
) )
) if isinstance(expr, VariableExpr):
name: Token = expr.name
return AssignExpr(name, value)
elif isinstance(expr, GetExpr):
return SetExpr(expr.object, expr.name, value)
self.error(operator, "Invalid assignment target.") self.error(operator, "Invalid assignment target.")
return expr return expr