diff --git a/docs/grammar.bnf b/docs/grammar.bnf index 9d4f175..ede3967 100644 --- a/docs/grammar.bnf +++ b/docs/grammar.bnf @@ -86,7 +86,7 @@ continueStmt ::= KW_CONTINUE ; 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_and ::= equality ( KW_AND equality )* ; diff --git a/examples/basic/18_fields.peb b/examples/basic/18_fields.peb new file mode 100644 index 0000000..99fb1c2 --- /dev/null +++ b/examples/basic/18_fields.peb @@ -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) diff --git a/main.py b/main.py index c44e010..f0421da 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from src.pebble import Pebble def main(): - path: Path = Path("examples/basic/17_class.peb") + path: Path = Path("examples/basic/18_fields.peb") Pebble.run_file(path) diff --git a/src/ast/expr.py b/src/ast/expr.py index b5ee0d7..d2e7209 100644 --- a/src/ast/expr.py +++ b/src/ast/expr.py @@ -53,6 +53,10 @@ class Expr(ABC): def visit_logical_expr(self, expr: LogicalExpr) -> T: ... + @abstractmethod + def visit_set_expr(self, expr: SetExpr) -> T: + ... + @dataclass(frozen=True) class AssignExpr(Expr): @@ -133,3 +137,13 @@ class LogicalExpr(Expr): def accept(self, visitor: Expr.Visitor[T]) -> T: 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) diff --git a/src/core/instance.py b/src/core/instance.py index 195309a..67a818f 100644 --- a/src/core/instance.py +++ b/src/core/instance.py @@ -22,3 +22,6 @@ class PebbleInstance: return self.fields[name.lexeme] except KeyError: raise PebbleRuntimeError(name, f"Undefined property '{name.lexeme}'.") + + def set(self, name: Token, value: Any) -> None: + self.fields[name.lexeme] = value diff --git a/src/formatter.py b/src/formatter.py index 7c56e90..b2ca8e6 100644 --- a/src/formatter.py +++ b/src/formatter.py @@ -1,7 +1,7 @@ from typing import Any 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, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt @@ -33,12 +33,18 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]): def visit_logical_expr(self, expr: LogicalExpr) -> str: 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: return f"{expr.operator.lexeme}{self.format(expr.right)}" def visit_call_expr(self, expr: CallExpr) -> str: 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: return f"({self.format(expr.expression)})" diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py index 7a453d5..46ed2d1 100644 --- a/src/interpreter/interpreter.py +++ b/src/interpreter/interpreter.py @@ -1,7 +1,7 @@ from typing import Any, Optional 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, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt 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") 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: left: Any = self.evaluate(expr.left) right: Any = self.evaluate(expr.right) diff --git a/src/interpreter/resolver.py b/src/interpreter/resolver.py index 8b8b5a1..0e1cc1c 100644 --- a/src/interpreter/resolver.py +++ b/src/interpreter/resolver.py @@ -4,7 +4,7 @@ from enum import Enum, auto from typing import TYPE_CHECKING 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, \ ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt from src.pebble import Pebble @@ -105,6 +105,10 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]): self.resolve(expr.left) 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: self.begin_scope() self.resolve(*stmt.statements) diff --git a/src/parser/parser.py b/src/parser/parser.py index b78fdb1..cc0ada6 100644 --- a/src/parser/parser.py +++ b/src/parser/parser.py @@ -1,7 +1,7 @@ from typing import Optional 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, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import MAX_FUNCTION_ARGS @@ -257,19 +257,18 @@ class Parser: TokenType.SLASH_EQUAL): operator: Token = self.previous() value: Expr = self.assignment() + if operator.type != TokenType.EQUAL: + value = BinaryExpr( + expr, + operator, + value + ) if isinstance(expr, VariableExpr): name: Token = expr.name - if operator.type == TokenType.EQUAL: - return AssignExpr(name, value) - else: - return AssignExpr( - name, - BinaryExpr( - VariableExpr(name), - operator, - value - ) - ) + return AssignExpr(name, value) + + elif isinstance(expr, GetExpr): + return SetExpr(expr.object, expr.name, value) self.error(operator, "Invalid assignment target.") return expr