diff --git a/docs/grammar.bnf b/docs/grammar.bnf index e2d45b4..e010c95 100644 --- a/docs/grammar.bnf +++ b/docs/grammar.bnf @@ -6,6 +6,8 @@ PUNC_RPAREN=")" PUNC_LBRACE="{" PUNC_RBRACE="}" + PUNC_LBRACK="[" + PUNC_RBRACK="]" PUNC_COMMA="," PUNC_DOT="." PUNC_SEMICOLON=";" @@ -99,11 +101,13 @@ factor ::= unary ( ( OP_SLASH | OP_STAR ) unary )* ; unary ::= ( OP_BANG | OP_MINUS ) unary | call ; call ::= primary ( 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 ; +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 ; 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? ; 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 new file mode 100644 index 0000000..8d2f28e --- /dev/null +++ b/examples/basic/24_list.peb @@ -0,0 +1,6 @@ +let a = [ + "a", + "b", + "c" +] +print(a) diff --git a/src/ast/expr.py b/src/ast/expr.py index e01dddd..7bb17b5 100644 --- a/src/ast/expr.py +++ b/src/ast/expr.py @@ -54,6 +54,10 @@ class Expr(ABC): def visit_fstring_embed_expr(self, expr: FStringEmbedExpr) -> T: ... + @abstractmethod + def visit_list_expr(self, expr: ListExpr) -> T: + ... + @abstractmethod def visit_variable_expr(self, expr: VariableExpr) -> T: ... @@ -159,6 +163,15 @@ class FStringEmbedExpr(Expr): return visitor.visit_fstring_embed_expr(self) +@dataclass(frozen=True) +class ListExpr(Expr): + bracket: Token + items: list[Expr] + + def accept(self, visitor: Expr.Visitor[T]) -> T: + return visitor.visit_list_expr(self) + + @dataclass(frozen=True) class VariableExpr(Expr): name: Token diff --git a/src/core/format_spec/string_formatter.py b/src/core/format_spec/string_formatter.py index f6aabd4..936144f 100644 --- a/src/core/format_spec/string_formatter.py +++ b/src/core/format_spec/string_formatter.py @@ -8,7 +8,7 @@ from src.interpreter.error import PebbleRuntimeError class StringFormatter: @staticmethod - def stringify(obj: Any): + def stringify(obj: Any, quote_str: bool = False): if obj is None: return "null" if obj is True: @@ -19,6 +19,8 @@ class StringFormatter: if obj.is_integer(): obj = int(obj) return str(obj) + if isinstance(obj, str) and quote_str: + return '"' + obj + '"' return obj @staticmethod diff --git a/src/core/list.py b/src/core/list.py new file mode 100644 index 0000000..c1da011 --- /dev/null +++ b/src/core/list.py @@ -0,0 +1,17 @@ +from typing import Optional, Any + +from src.core.format_spec.string_formatter import StringFormatter + + +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 set(self, index: Any, value: Any): + self.items[index] = 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 dff9c81..6ada4fa 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 + CallExpr, SetExpr, GetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr 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 @@ -95,6 +95,9 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]): res += "}" return res + def visit_list_expr(self, expr: ListExpr) -> str: + return "[" + ", ".join(map(self.format, expr.items)) + "]" + def visit_variable_expr(self, expr: VariableExpr) -> str: return expr.name.lexeme diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py index 3f700cf..d38d944 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 + CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import CONSTRUCTOR_NAME @@ -10,6 +10,7 @@ from src.core.format_spec.string_formatter import StringFormatter from src.core.function import PebbleFunction from src.core.instance import PebbleInstance from src.core.klass import PebbleClass +from src.core.list import PebbleList from src.interpreter.environment import Environment from src.interpreter.error import PebbleRuntimeError from src.interpreter.exceptions import ReturnException, BreakException, ContinueException @@ -192,6 +193,12 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]): return self.stringify(value) return StringFormatter().format(value, expr.spec) + def visit_list_expr(self, expr: ListExpr) -> Any: + items: list[Any] = [] + for item in expr.items: + items.append(self.evaluate(item)) + return PebbleList(items) + def visit_variable_expr(self, expr: VariableExpr) -> Any: return self.look_up_variable(expr.name, expr) diff --git a/src/interpreter/resolver.py b/src/interpreter/resolver.py index 54c2592..7e4f212 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 + AssignExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, IfStmt, FunctionStmt, \ ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import CONSTRUCTOR_NAME @@ -113,6 +113,10 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]): def visit_fstring_embed_expr(self, expr: FStringEmbedExpr) -> None: self.resolve(expr.expression) + def visit_list_expr(self, expr: ListExpr) -> None: + for item in expr.items: + self.resolve(item) + def visit_variable_expr(self, expr: VariableExpr) -> None: if len(self.scopes) != 0 and self.scopes[-1].get(expr.name.lexeme) is False: Pebble.token_error(expr.name, "Variable is not initialized.") diff --git a/src/parser/parser.py b/src/parser/parser.py index 282ae19..02b1567 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 + CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr, ListExpr from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import MAX_FUNCTION_ARGS @@ -369,6 +369,9 @@ class Parser: if self.match(TokenType.FSTRING_START): return self.fstring() + if self.match(TokenType.LEFT_BRACKET): + return self.list() + if self.match(TokenType.NUMBER, TokenType.STRING): return LiteralExpr(self.previous().value) @@ -410,3 +413,14 @@ class Parser: self.consume(TokenType.FSTRING_END, "Unclosed f-string") 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") + return ListExpr(bracket, items) diff --git a/src/token/lexer.py b/src/token/lexer.py index 5f8598c..cc3061d 100644 --- a/src/token/lexer.py +++ b/src/token/lexer.py @@ -93,6 +93,10 @@ class Lexer: self.add_token(TokenType.LEFT_BRACE) case "}": self.add_token(TokenType.RIGHT_BRACE) + case "[": + self.add_token(TokenType.LEFT_BRACKET) + case "]": + self.add_token(TokenType.RIGHT_BRACKET) case ",": self.add_token(TokenType.COMMA) case ".": diff --git a/src/token/token.py b/src/token/token.py index b8880f5..b7c7f56 100644 --- a/src/token/token.py +++ b/src/token/token.py @@ -11,6 +11,8 @@ class TokenType(Enum): RIGHT_PAREN = auto() LEFT_BRACE = auto() RIGHT_BRACE = auto() + LEFT_BRACKET = auto() + RIGHT_BRACKET = auto() COMMA = auto() DOT = auto() SEMICOLON = auto()