feat: add list subscript get

This commit is contained in:
2026-02-08 21:36:43 +01:00
parent 802558d681
commit 983f886397
9 changed files with 85 additions and 13 deletions

View File

@@ -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 )* ;

View File

@@ -4,3 +4,4 @@ let a = [
"c"
]
print(a)
print(a[0])

View File

@@ -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

30
src/core/cast.py Normal file
View File

@@ -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)

View File

@@ -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)) + "]"

View File

@@ -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)})"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)