feat: add methods and properties on builtin types

added push method on lists and length property
This commit is contained in:
2026-02-09 01:45:09 +01:00
parent 0d5c678932
commit fb7723406c
4 changed files with 103 additions and 3 deletions

View File

@@ -21,4 +21,9 @@ print(l[1][2])
l[1][2] = "three"
print(l)
print(l[1][2])
print(l[1][2])
print(a.length)
a.push("d")
print(a.length)
print(a)

76
src/core/builtin_type.py Normal file
View 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

View File

@@ -1,11 +1,17 @@
from typing import Optional, Any
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:
class PebbleList(metaclass=BuiltinType):
def __init__(self, items: Optional[list[Any]] = None):
self.items: list[Any] = items or []
@@ -19,3 +25,13 @@ class PebbleList:
def __str__(self):
return "[" + ", ".join(map(lambda item: StringFormatter.stringify(item, True), self.items)) + "]"
# 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)

View File

@@ -6,6 +6,7 @@ from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr,
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
@@ -182,6 +183,8 @@ 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: