diff --git a/docs/grammar.bnf b/docs/grammar.bnf index e010c95..ed909a4 100644 --- a/docs/grammar.bnf +++ b/docs/grammar.bnf @@ -90,7 +90,7 @@ continueStmt ::= KW_CONTINUE ; expression ::= assignment ; -assignment ::= ( call PUNC_DOT )? IDENTIFIER OP_EQUAL assignment | logic_or ; +assignment ::= ( call PUNC_DOT )? IDENTIFIER list_index* OP_EQUAL assignment | logic_or ; logic_or ::= logic_and ( KW_OR logic_and )* ; logic_and ::= equality ( KW_AND equality )* ; @@ -100,7 +100,7 @@ term ::= factor ( ( OP_MINUS | OP_PLUS ) factor )* ; factor ::= unary ( ( OP_SLASH | OP_STAR ) unary )* ; unary ::= ( OP_BANG | OP_MINUS ) unary | call ; -call ::= primary ( PUNC_LPAREN arguments? PUNC_RPAREN | PUNC_DOT IDENTIFIER )* ; +call ::= subscript ( PUNC_LPAREN arguments? PUNC_RPAREN | PUNC_DOT IDENTIFIER )* ; primary ::= KW_TRUE | KW_FALSE | KW_NULL | KW_THIS | NUMBER | STRING | IDENTIFIER | PUNC_LPAREN expression PUNC_RPAREN | KW_SUPER PUNC_DOT IDENTIFIER | list ; function ::= IDENTIFIER PUNC_LPAREN parameters? PUNC_RPAREN block ; @@ -108,6 +108,8 @@ parameters ::= IDENTIFIER ( PUNC_COMMA IDENTIFIER )* ; arguments ::= expression ( PUNC_COMMA expression )* ; list ::= PUNC_LBRACK list_items? PUNC_RBRACK ; list_items ::= logic_or (PUNC_COMMA logic_or)* PUNC_COMMA? ; +list_index ::= PUNC_LBRACK logic_or PUNC_RBRACK ; +subscript ::= primary list_index* ; NUMBER ::= DIGIT+ ( PUNC_DOT DIGIT+ ) ?; IDENTIFIER ::= ALPHA ( ALPHA | DIGIT | SYM_UNDERSCORE )* ; diff --git a/examples/basic/24_list.peb b/examples/basic/24_list.peb index 8d2f28e..12139de 100644 --- a/examples/basic/24_list.peb +++ b/examples/basic/24_list.peb @@ -4,3 +4,4 @@ let a = [ "c" ] print(a) +print(a[0]) \ No newline at end of file diff --git a/src/ast/expr.py b/src/ast/expr.py index 7bb17b5..6803261 100644 --- a/src/ast/expr.py +++ b/src/ast/expr.py @@ -38,6 +38,10 @@ class Expr(ABC): def visit_get_expr(self, expr: GetExpr) -> T: ... + @abstractmethod + def visit_subscript_get_expr(self, expr: SubscriptGetExpr) -> T: + ... + @abstractmethod def visit_grouping_expr(self, expr: GroupingExpr) -> T: ... @@ -126,6 +130,16 @@ class GetExpr(Expr): return visitor.visit_get_expr(self) +@dataclass(frozen=True) +class SubscriptGetExpr(Expr): + object: Expr + bracket: Token + index: Expr + + def accept(self, visitor: Expr.Visitor[T]) -> T: + return visitor.visit_subscript_get_expr(self) + + @dataclass(frozen=True) class GroupingExpr(Expr): expression: Expr diff --git a/src/core/cast.py b/src/core/cast.py new file mode 100644 index 0000000..a4c6c2f --- /dev/null +++ b/src/core/cast.py @@ -0,0 +1,30 @@ +from typing import Any + +from src.interpreter.error import PebbleRuntimeError +from src.token.token import Token + + +class Cast: + @staticmethod + def as_number(token: Token, value: Any) -> int | float: + if not isinstance(value, (int, float, bool)): + raise PebbleRuntimeError(token, "Expected number value.") + if isinstance(value, bool): + value = int(value) + return value + + @staticmethod + def as_int(token: Token, value: Any): + try: + number: int | float = Cast.as_number(token, value) + except PebbleRuntimeError: + raise PebbleRuntimeError(token, "Expected integer value.") + return int(number) + + @staticmethod + def as_float(token: Token, value: Any): + try: + number: int | float = Cast.as_number(token, value) + except PebbleRuntimeError: + raise PebbleRuntimeError(token, "Expected float value.") + return float(number) diff --git a/src/core/list.py b/src/core/list.py index c1da011..ae5e0c7 100644 --- a/src/core/list.py +++ b/src/core/list.py @@ -1,17 +1,21 @@ from typing import Optional, Any +from src.core.cast import Cast from src.core.format_spec.string_formatter import StringFormatter +from src.token.token import Token class PebbleList: def __init__(self, items: Optional[list[Any]] = None): self.items: list[Any] = items or [] - def get(self, index: Any): - return self.items[index] + def get(self, index: Any, bracket: Token): + idx: int = Cast.as_int(bracket, index) + return self.items[idx] - def set(self, index: Any, value: Any): - self.items[index] = value + def set(self, index: Any, value: Any, bracket: Token): + idx: int = Cast.as_int(bracket, index) + self.items[idx] = value def __str__(self): return "[" + ", ".join(map(lambda item: StringFormatter.stringify(item, True), self.items)) + "]" diff --git a/src/formatter.py b/src/formatter.py index 6ada4fa..9c5b768 100644 --- a/src/formatter.py +++ b/src/formatter.py @@ -2,7 +2,7 @@ from enum import Enum, auto from typing import Any from src.ast.expr import Expr, VariableExpr, LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, AssignExpr, LogicalExpr, \ - CallExpr, SetExpr, GetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr + CallExpr, SetExpr, GetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr, SubscriptGetExpr from src.ast.stmt import Stmt, LetStmt, IfStmt, ExpressionStmt, BlockStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.core.format_spec.spec import FormatSpec @@ -59,6 +59,9 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]): def visit_get_expr(self, expr: GetExpr) -> str: return f"{self.format(expr.object)}.{expr.name.lexeme}" + def visit_subscript_get_expr(self, expr: SubscriptGetExpr) -> str: + return f"{self.format(expr.object)}[{self.format(expr.index)}]" + 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 d38d944..3f1dc0e 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, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr + CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr, SubscriptGetExpr from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import CONSTRUCTOR_NAME @@ -175,6 +175,13 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]): return obj.get(expr.name) raise PebbleRuntimeError(expr.name, "Only class instances have properties.") + def visit_subscript_get_expr(self, expr: SubscriptGetExpr) -> Any: + obj: Any = self.evaluate(expr.object) + idx: Any = self.evaluate(expr.index) + if isinstance(obj, PebbleList): + return obj.get(idx, expr.bracket) + raise PebbleRuntimeError(expr.bracket, "Only lists can be indexed.") + def visit_grouping_expr(self, expr: GroupingExpr) -> Any: return self.evaluate(expr.expression) diff --git a/src/interpreter/resolver.py b/src/interpreter/resolver.py index 7e4f212..45cd22c 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, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr + AssignExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr, SubscriptGetExpr from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, IfStmt, FunctionStmt, \ ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import CONSTRUCTOR_NAME @@ -100,6 +100,10 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]): def visit_get_expr(self, expr: GetExpr) -> None: self.resolve(expr.object) + def visit_subscript_get_expr(self, expr: SubscriptGetExpr) -> None: + self.resolve(expr.object) + self.resolve(expr.index) + def visit_grouping_expr(self, expr: GroupingExpr) -> None: self.resolve(expr.expression) diff --git a/src/parser/parser.py b/src/parser/parser.py index 02b1567..03120dc 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, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr + CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr, SubscriptGetExpr from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import MAX_FUNCTION_ARGS @@ -334,7 +334,7 @@ class Parser: return self.call() def call(self) -> Expr: - expr: Expr = self.primary() + expr: Expr = self.subscript() while True: if self.match(TokenType.LEFT_PAREN): expr = self.finish_call(expr) @@ -358,6 +358,14 @@ class Parser: paren: Token = self.consume(TokenType.RIGHT_PAREN, "Expected ')' after arguments.") return CallExpr(callee, paren, arguments) + def subscript(self) -> Expr: + expr: Expr = self.primary() + while self.match(TokenType.LEFT_BRACKET): + idx: Expr = self.expression() + bracket: Token = self.consume(TokenType.RIGHT_BRACKET, "Unclosed list index") + expr = SubscriptGetExpr(expr, bracket, idx) + return expr + def primary(self) -> Expr: if self.match(TokenType.FALSE): return LiteralExpr(False) @@ -415,12 +423,11 @@ class Parser: return FStringExpr(start, parts, self.previous()) def list(self) -> Expr: - bracket: Token = self.previous() items: list[Expr] = [] while not self.check(TokenType.RIGHT_BRACKET) and not self.is_at_end(): items.append(self.expression()) if not self.check(TokenType.RIGHT_BRACKET): self.consume(TokenType.COMMA, "Expected ',' between list items") - self.consume(TokenType.RIGHT_BRACKET, "Unclosed list") + bracket: Token = self.consume(TokenType.RIGHT_BRACKET, "Unclosed list") return ListExpr(bracket, items)