Compare commits
5 Commits
main
...
feat/lists
| Author | SHA1 | Date | |
|---|---|---|---|
|
fe747656d8
|
|||
|
fb7723406c
|
|||
|
0d5c678932
|
|||
|
983f886397
|
|||
|
802558d681
|
@@ -6,6 +6,8 @@
|
||||
PUNC_RPAREN=")"
|
||||
PUNC_LBRACE="{"
|
||||
PUNC_RBRACE="}"
|
||||
PUNC_LBRACK="["
|
||||
PUNC_RBRACK="]"
|
||||
PUNC_COMMA=","
|
||||
PUNC_DOT="."
|
||||
PUNC_SEMICOLON=";"
|
||||
@@ -88,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 )* ;
|
||||
@@ -98,12 +100,16 @@ 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 )* ;
|
||||
primary ::= KW_TRUE | KW_FALSE | KW_NULL | KW_THIS | NUMBER | STRING | IDENTIFIER | PUNC_LPAREN expression PUNC_RPAREN | KW_SUPER 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 ;
|
||||
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 )* ;
|
||||
|
||||
29
examples/basic/24_list.peb
Normal file
29
examples/basic/24_list.peb
Normal file
@@ -0,0 +1,29 @@
|
||||
let a = [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
]
|
||||
print(a)
|
||||
print(a[0])
|
||||
|
||||
a[1] = "B"
|
||||
|
||||
print(a)
|
||||
|
||||
let l = [
|
||||
["a", "b", "c"],
|
||||
["1", "2", "3"]
|
||||
]
|
||||
|
||||
print(l)
|
||||
print(l[1][2])
|
||||
|
||||
l[1][2] = "three"
|
||||
|
||||
print(l)
|
||||
print(l[1][2])
|
||||
|
||||
print(a.length)
|
||||
a.push("d")
|
||||
print(a.length)
|
||||
print(a)
|
||||
@@ -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:
|
||||
...
|
||||
@@ -54,6 +58,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:
|
||||
...
|
||||
@@ -66,6 +74,10 @@ class Expr(ABC):
|
||||
def visit_set_expr(self, expr: SetExpr) -> T:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def visit_subscript_set_expr(self, expr: SubscriptSetExpr) -> T:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def visit_this_expr(self, expr: ThisExpr) -> T:
|
||||
...
|
||||
@@ -122,6 +134,16 @@ class GetExpr(Expr):
|
||||
return visitor.visit_get_expr(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SubscriptGetExpr(Expr):
|
||||
object: Expr
|
||||
index: Expr
|
||||
bracket: Token
|
||||
|
||||
def accept(self, visitor: Expr.Visitor[T]) -> T:
|
||||
return visitor.visit_subscript_get_expr(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GroupingExpr(Expr):
|
||||
expression: Expr
|
||||
@@ -159,6 +181,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
|
||||
@@ -187,6 +218,17 @@ class SetExpr(Expr):
|
||||
return visitor.visit_set_expr(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SubscriptSetExpr(Expr):
|
||||
object: Expr
|
||||
index: Expr
|
||||
bracket: Token
|
||||
value: Expr
|
||||
|
||||
def accept(self, visitor: Expr.Visitor[T]) -> T:
|
||||
return visitor.visit_subscript_set_expr(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ThisExpr(Expr):
|
||||
keyword: Token
|
||||
|
||||
76
src/core/builtin_type.py
Normal file
76
src/core/builtin_type.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Any, Optional
|
||||
|
||||
from src.core.callable import PebbleCallable, make_builtin
|
||||
from src.interpreter.error import PebbleRuntimeError
|
||||
from src.token.token import Token
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExposedMeta:
|
||||
name: str
|
||||
nargs: int = 0
|
||||
|
||||
|
||||
Method = Callable[..., Any]
|
||||
Exposable = Method | property
|
||||
|
||||
|
||||
def exposed(func: Exposable = None, /, *, name: Optional[str] = None, nargs: int = 0):
|
||||
"""
|
||||
Decorator to mark methods/properties as exposed to the interpreter
|
||||
:param func: the method/property to expose
|
||||
:param name: the exposed name in the language. If None, the Python name is used
|
||||
:param nargs: the number of arguments (for methods only, will raise an error if a value != 0 is passed for a property)
|
||||
:return: the decorated method/property
|
||||
"""
|
||||
def wrap(f: Exposable):
|
||||
if isinstance(f, property) and nargs != 0:
|
||||
raise ValueError("Properties cannot accept arguments")
|
||||
|
||||
target: Exposable = f.fget if isinstance(f, property) else f
|
||||
target._exposed = ExposedMeta(
|
||||
name=name or target.__name__,
|
||||
nargs=nargs
|
||||
)
|
||||
return f
|
||||
return wrap if func is None else wrap(func)
|
||||
|
||||
|
||||
def _get_exposed_meta(attr: Any) -> Optional[ExposedMeta]:
|
||||
target: Exposable = attr.fget if isinstance(attr, property) else attr
|
||||
return getattr(target, "_exposed", None)
|
||||
|
||||
|
||||
class BuiltinType(type):
|
||||
def __new__(mcs, cls_name, bases, namespace):
|
||||
exposed_props: dict[str, property] = {}
|
||||
exposed_methods: dict[str, tuple[Method, int]] = {}
|
||||
for attr_name, attr_value in namespace.items():
|
||||
meta: Optional[ExposedMeta] = _get_exposed_meta(attr_value)
|
||||
if meta is None:
|
||||
continue
|
||||
if isinstance(attr_value, property):
|
||||
exposed_props[meta.name] = attr_value
|
||||
elif callable(attr_value):
|
||||
exposed_methods[meta.name] = (attr_value, meta.nargs)
|
||||
|
||||
namespace["exposed_props"] = exposed_props
|
||||
namespace["exposed_methods"] = exposed_methods
|
||||
namespace["get_exposed"] = mcs._make_get_exposed()
|
||||
return super().__new__(mcs, cls_name, bases, namespace)
|
||||
|
||||
@staticmethod
|
||||
def _make_get_exposed() -> Callable:
|
||||
def get_exposed(self, name: Token) -> Any:
|
||||
name_str: str = name.lexeme
|
||||
cls_prop: Optional[property] = self.__class__.exposed_props.get(name_str)
|
||||
if cls_prop is not None:
|
||||
return getattr(self, cls_prop.__name__)
|
||||
|
||||
cls_method: Optional[tuple[PebbleCallable, int]] = self.__class__.exposed_methods.get(name_str)
|
||||
if cls_method is not None:
|
||||
return make_builtin(getattr(self, cls_method[0].__name__), cls_method[1])
|
||||
|
||||
raise PebbleRuntimeError(name, f"Undefined property '{name_str}'.")
|
||||
return get_exposed
|
||||
30
src/core/cast.py
Normal file
30
src/core/cast.py
Normal 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)
|
||||
@@ -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,7 +19,9 @@ class StringFormatter:
|
||||
if obj.is_integer():
|
||||
obj = int(obj)
|
||||
return str(obj)
|
||||
return obj
|
||||
if isinstance(obj, str) and quote_str:
|
||||
return '"' + obj + '"'
|
||||
return str(obj)
|
||||
|
||||
@staticmethod
|
||||
def assert_type(token: Token, obj: Any, expected_type: type | tuple[type, ...]):
|
||||
|
||||
43
src/core/list.py
Normal file
43
src/core/list.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Any, TYPE_CHECKING
|
||||
|
||||
from src.core.builtin_type import BuiltinType, exposed
|
||||
from src.core.cast import Cast
|
||||
from src.core.format_spec.string_formatter import StringFormatter
|
||||
from src.token.token import Token
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.interpreter.interpreter import Interpreter
|
||||
|
||||
|
||||
class PebbleList(metaclass=BuiltinType):
|
||||
def __init__(self, items: Optional[list[Any]] = None):
|
||||
self.items: list[Any] = items or []
|
||||
self._stringify_visited: bool = False
|
||||
|
||||
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, bracket: Token):
|
||||
idx: int = Cast.as_int(bracket, index)
|
||||
self.items[idx] = value
|
||||
|
||||
def __str__(self):
|
||||
if self._stringify_visited:
|
||||
return "[...]"
|
||||
self._stringify_visited = True
|
||||
res: str = "[" + ", ".join(map(lambda item: StringFormatter.stringify(item, True), self.items)) + "]"
|
||||
self._stringify_visited = False
|
||||
return res
|
||||
|
||||
# Exposed methods
|
||||
@exposed(nargs=1)
|
||||
def push(self, interpreter: Interpreter, args: list[Any]) -> None:
|
||||
self.items.append(args[0])
|
||||
|
||||
@exposed
|
||||
@property
|
||||
def length(self) -> int:
|
||||
return len(self.items)
|
||||
@@ -2,7 +2,8 @@ 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, SubscriptGetExpr, \
|
||||
SubscriptSetExpr
|
||||
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
|
||||
@@ -44,6 +45,9 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
|
||||
def visit_set_expr(self, expr: SetExpr) -> str:
|
||||
return f"{self.format(expr.object)}.{expr.name.lexeme} = {self.format(expr.value)}"
|
||||
|
||||
def visit_subscript_set_expr(self, expr: SubscriptSetExpr) -> str:
|
||||
return f"{self.format(expr.object)}[{self.format(expr.index)}] = {self.format(expr.value)}"
|
||||
|
||||
def visit_this_expr(self, expr: ThisExpr) -> str:
|
||||
return expr.keyword.lexeme
|
||||
|
||||
@@ -59,6 +63,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)})"
|
||||
|
||||
@@ -95,6 +102,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
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
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, SubscriptGetExpr, \
|
||||
SubscriptSetExpr
|
||||
from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
|
||||
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
|
||||
from src.consts import CONSTRUCTOR_NAME
|
||||
from src.core.builtin_type import BuiltinType
|
||||
from src.core.callable import PebbleCallable
|
||||
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
|
||||
@@ -89,6 +92,14 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
|
||||
obj.set(expr.name, value)
|
||||
return value
|
||||
|
||||
def visit_subscript_set_expr(self, expr: SubscriptSetExpr) -> Any:
|
||||
obj: Any = self.evaluate(expr.object)
|
||||
if not isinstance(obj, PebbleList):
|
||||
raise PebbleRuntimeError(expr.bracket, "Only lists can be indexed.")
|
||||
value: Any = self.evaluate(expr.value)
|
||||
idx: Any = self.evaluate(expr.index)
|
||||
obj.set(idx, value, expr.bracket)
|
||||
|
||||
def visit_this_expr(self, expr: ThisExpr) -> Any:
|
||||
return self.look_up_variable(expr.keyword, expr)
|
||||
|
||||
@@ -172,8 +183,17 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
|
||||
obj: Any = self.evaluate(expr.object)
|
||||
if isinstance(obj, PebbleInstance):
|
||||
return obj.get(expr.name)
|
||||
if isinstance(obj.__class__, BuiltinType):
|
||||
return obj.get_exposed(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)
|
||||
if not isinstance(obj, PebbleList):
|
||||
raise PebbleRuntimeError(expr.bracket, "Only lists can be indexed.")
|
||||
idx: Any = self.evaluate(expr.index)
|
||||
return obj.get(idx, expr.bracket)
|
||||
|
||||
def visit_grouping_expr(self, expr: GroupingExpr) -> Any:
|
||||
return self.evaluate(expr.expression)
|
||||
|
||||
@@ -192,6 +212,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)
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ 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, SubscriptGetExpr, \
|
||||
SubscriptSetExpr
|
||||
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 +101,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)
|
||||
|
||||
@@ -113,6 +118,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.")
|
||||
@@ -126,6 +135,11 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
|
||||
self.resolve(expr.value)
|
||||
self.resolve(expr.object)
|
||||
|
||||
def visit_subscript_set_expr(self, expr: SubscriptSetExpr) -> None:
|
||||
self.resolve(expr.value)
|
||||
self.resolve(expr.object)
|
||||
self.resolve(expr.index)
|
||||
|
||||
def visit_this_expr(self, expr: ThisExpr) -> None:
|
||||
if self.current_class == ClassType.NONE:
|
||||
Pebble.token_error(expr.keyword, "Cannot use 'this' outside of a class.")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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, SubscriptGetExpr, \
|
||||
SubscriptSetExpr
|
||||
from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
|
||||
ReturnStmt, BreakStmt, ContinueStmt, ClassStmt
|
||||
from src.consts import MAX_FUNCTION_ARGS
|
||||
@@ -275,6 +276,9 @@ class Parser:
|
||||
|
||||
elif isinstance(expr, GetExpr):
|
||||
return SetExpr(expr.object, expr.name, value)
|
||||
|
||||
elif isinstance(expr, SubscriptGetExpr):
|
||||
return SubscriptSetExpr(expr.object, expr.index, expr.bracket, value)
|
||||
self.error(operator, "Invalid assignment target.")
|
||||
return expr
|
||||
|
||||
@@ -334,7 +338,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 +362,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, idx, bracket)
|
||||
return expr
|
||||
|
||||
def primary(self) -> Expr:
|
||||
if self.match(TokenType.FALSE):
|
||||
return LiteralExpr(False)
|
||||
@@ -369,6 +381,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 +425,13 @@ class Parser:
|
||||
|
||||
self.consume(TokenType.FSTRING_END, "Unclosed f-string")
|
||||
return FStringExpr(start, parts, self.previous())
|
||||
|
||||
def list(self) -> Expr:
|
||||
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")
|
||||
|
||||
bracket: Token = self.consume(TokenType.RIGHT_BRACKET, "Unclosed list")
|
||||
return ListExpr(bracket, items)
|
||||
|
||||
@@ -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 ".":
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user