feat: add methods and properties on builtin types
added push method on lists and length property
This commit is contained in:
@@ -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
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
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user