feat: add instance field getter

This commit is contained in:
2026-02-06 21:25:24 +01:00
parent 5b2fd5d60e
commit b06bce2558
6 changed files with 41 additions and 5 deletions

View File

@@ -96,7 +96,7 @@ term ::= factor ( ( OP_MINUS | OP_PLUS ) factor )* ;
factor ::= unary ( ( OP_SLASH | OP_STAR ) unary )* ; 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 )* ; 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 | NUMBER | STRING | IDENTIFIER | PUNC_LPAREN expression PUNC_RPAREN ;
function ::= IDENTIFIER PUNC_LPAREN parameters? PUNC_RPAREN block ; function ::= IDENTIFIER PUNC_LPAREN parameters? PUNC_RPAREN block ;

View File

@@ -33,6 +33,10 @@ class Expr(ABC):
def visit_call_expr(self, expr: CallExpr) -> T: def visit_call_expr(self, expr: CallExpr) -> T:
... ...
@abstractmethod
def visit_get_expr(self, expr: GetExpr) -> T:
...
@abstractmethod @abstractmethod
def visit_grouping_expr(self, expr: GroupingExpr) -> T: def visit_grouping_expr(self, expr: GroupingExpr) -> T:
... ...
@@ -88,6 +92,15 @@ class CallExpr(Expr):
return visitor.visit_call_expr(self) return visitor.visit_call_expr(self)
@dataclass(frozen=True)
class GetExpr(Expr):
object: Expr
name: Token
def accept(self, visitor: Expr.Visitor[T]) -> T:
return visitor.visit_get_expr(self)
@dataclass(frozen=True) @dataclass(frozen=True)
class GroupingExpr(Expr): class GroupingExpr(Expr):
expression: Expr expression: Expr

View File

@@ -1,6 +1,9 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Any
from src.interpreter.error import PebbleRuntimeError
from src.token import Token
if TYPE_CHECKING: if TYPE_CHECKING:
from src.core.klass import PebbleClass from src.core.klass import PebbleClass
@@ -9,6 +12,13 @@ if TYPE_CHECKING:
class PebbleInstance: class PebbleInstance:
def __init__(self, klass: PebbleClass): def __init__(self, klass: PebbleClass):
self.klass: PebbleClass = klass self.klass: PebbleClass = klass
self.fields: dict[str, Any] = {}
def __str__(self): def __str__(self):
return f"<instance of {self.klass}>" return f"<instance of {self.klass}>"
def get(self, name: Token) -> Any:
try:
return self.fields[name.lexeme]
except KeyError:
raise PebbleRuntimeError(name, f"Undefined property '{name.lexeme}'.")

View File

@@ -1,11 +1,12 @@
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 CallExpr, GetExpr, T
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
from src.core.function import PebbleFunction from src.core.function import PebbleFunction
from src.core.instance import PebbleInstance
from src.core.klass import PebbleClass 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
@@ -143,6 +144,12 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
raise PebbleRuntimeError(expr.paren, f"Expected {arity} arguments but got {len(arguments)}.") raise PebbleRuntimeError(expr.paren, f"Expected {arity} arguments but got {len(arguments)}.")
return function.call(self, arguments) return function.call(self, arguments)
def visit_get_expr(self, expr: GetExpr) -> Any:
obj: Any = self.evaluate(expr.object)
if isinstance(obj, PebbleInstance):
return obj.get(expr.name)
raise PebbleRuntimeError(expr.name, "Only class instances have properties.")
def visit_grouping_expr(self, expr: GroupingExpr) -> Any: def visit_grouping_expr(self, expr: GroupingExpr) -> Any:
return self.evaluate(expr.expression) return self.evaluate(expr.expression)

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 AssignExpr, GetExpr
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
@@ -87,6 +87,9 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
for arg in expr.arguments: for arg in expr.arguments:
self.resolve(arg) self.resolve(arg)
def visit_get_expr(self, expr: GetExpr) -> None:
self.resolve(expr.object)
def visit_grouping_expr(self, expr: GroupingExpr) -> None: def visit_grouping_expr(self, expr: GroupingExpr) -> None:
self.resolve(expr.expression) self.resolve(expr.expression)

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 CallExpr, GetExpr
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
@@ -333,6 +333,9 @@ class Parser:
while True: while True:
if self.match(TokenType.LEFT_PAREN): if self.match(TokenType.LEFT_PAREN):
expr = self.finish_call(expr) expr = self.finish_call(expr)
elif self.match(TokenType.DOT):
name: Token = self.consume(TokenType.IDENTIFIER, "Expected property name after '.'.")
expr = GetExpr(expr, name)
else: else:
break break
return expr return expr