diff --git a/include/typings/linalg.pyi b/include/typings/linalg.pyi index 5c2aac97..a2106ef4 100644 --- a/include/typings/linalg.pyi +++ b/include/typings/linalg.pyi @@ -1,4 +1,4 @@ -from typing import overload +from typing import overload, Iterator class _vecF[T]: ONE: T @@ -17,6 +17,9 @@ class _vecF[T]: def length_squared(self) -> float: ... def normalize(self) -> T: ... + # dummy iter for unpacking + def __iter__(self) -> Iterator[float]: ... + class _vecI[T]: ONE: T ZERO: T @@ -32,6 +35,9 @@ class _vecI[T]: def dot(self, other: T) -> int: ... + # dummy iter for unpacking + def __iter__(self) -> Iterator[int]: ... + class vec2(_vecF['vec2']): LEFT: vec2 diff --git a/python/typing.py b/python/typing.py index 3d424576..ff162a2a 100644 --- a/python/typing.py +++ b/python/typing.py @@ -30,6 +30,7 @@ LiteralString = _PLACEHOLDER Iterable = _PLACEHOLDER Generator = _PLACEHOLDER +Iterator = _PLACEHOLDER Hashable = _PLACEHOLDER diff --git a/src/common/_generated.c b/src/common/_generated.c index d3497025..1da614f2 100644 --- a/src/common/_generated.c +++ b/src/common/_generated.c @@ -12,7 +12,7 @@ const char kPythonLibs_heapq[] = "# Heap queue algorithm (a.k.a. priority queue) const char kPythonLibs_operator[] = "# https://docs.python.org/3/library/operator.html#mapping-operators-to-functions\n\ndef le(a, b): return a <= b\ndef lt(a, b): return a < b\ndef ge(a, b): return a >= b\ndef gt(a, b): return a > b\ndef eq(a, b): return a == b\ndef ne(a, b): return a != b\n\ndef and_(a, b): return a & b\ndef or_(a, b): return a | b\ndef xor(a, b): return a ^ b\ndef invert(a): return ~a\ndef lshift(a, b): return a << b\ndef rshift(a, b): return a >> b\n\ndef is_(a, b): return a is b\ndef is_not(a, b): return a is not b\ndef not_(a): return not a\ndef truth(a): return bool(a)\ndef contains(a, b): return b in a\n\ndef add(a, b): return a + b\ndef sub(a, b): return a - b\ndef mul(a, b): return a * b\ndef truediv(a, b): return a / b\ndef floordiv(a, b): return a // b\ndef mod(a, b): return a % b\ndef pow(a, b): return a ** b\ndef neg(a): return -a\ndef matmul(a, b): return a @ b\n\ndef getitem(a, b): return a[b]\ndef setitem(a, b, c): a[b] = c\ndef delitem(a, b): del a[b]\n\ndef iadd(a, b): a += b; return a\ndef isub(a, b): a -= b; return a\ndef imul(a, b): a *= b; return a\ndef itruediv(a, b): a /= b; return a\ndef ifloordiv(a, b): a //= b; return a\ndef imod(a, b): a %= b; return a\n# def ipow(a, b): a **= b; return a\n# def imatmul(a, b): a @= b; return a\ndef iand(a, b): a &= b; return a\ndef ior(a, b): a |= b; return a\ndef ixor(a, b): a ^= b; return a\ndef ilshift(a, b): a <<= b; return a\ndef irshift(a, b): a >>= b; return a\n"; const char kPythonLibs_pickle[] = "import json\nimport builtins\n\n_BASIC_TYPES = [int, float, str, bool, type(None)]\n_MOD_T_SEP = \"@\"\n\ndef _find_class(path: str):\n if _MOD_T_SEP not in path:\n return builtins.__dict__[path]\n modpath, name = path.split(_MOD_T_SEP)\n return __import__(modpath).__dict__[name]\n\nclass _Pickler:\n def __init__(self, obj) -> None:\n self.obj = obj\n self.raw_memo = {} # id -> int\n self.memo = [] # int -> object\n\n @staticmethod\n def _type_id(t: type):\n assert type(t) is type\n name = t.__name__\n mod = t.__module__\n if mod is not None:\n name = mod + _MOD_T_SEP + name\n return name\n\n def wrap(self, o):\n o_t = type(o)\n if o_t in _BASIC_TYPES:\n return o\n if o_t is type:\n return [\"type\", self._type_id(o)]\n\n index = self.raw_memo.get(id(o), None)\n if index is not None:\n return [index]\n \n ret = []\n index = len(self.memo)\n self.memo.append(ret)\n self.raw_memo[id(o)] = index\n\n if o_t is tuple:\n ret.append(\"tuple\")\n ret.append([self.wrap(i) for i in o])\n return [index]\n if o_t is bytes:\n ret.append(\"bytes\")\n ret.append([o[j] for j in range(len(o))])\n return [index]\n if o_t is list:\n ret.append(\"list\")\n ret.append([self.wrap(i) for i in o])\n return [index]\n if o_t is dict:\n ret.append(\"dict\")\n ret.append([[self.wrap(k), self.wrap(v)] for k,v in o.items()])\n return [index]\n \n _0 = self._type_id(o_t)\n\n if getattr(o_t, '__struct__', False):\n ret.append(_0)\n ret.append(o.tostruct().hex())\n return [index]\n\n if hasattr(o, \"__getnewargs__\"):\n _1 = o.__getnewargs__() # an iterable\n _1 = [self.wrap(i) for i in _1]\n else:\n _1 = None\n\n if o.__dict__ is None:\n _2 = None\n else:\n _2 = {k: self.wrap(v) for k,v in o.__dict__.items()}\n\n ret.append(_0) # type id\n ret.append(_1) # newargs\n ret.append(_2) # state\n return [index]\n \n def run_pipe(self):\n o = self.wrap(self.obj)\n return [o, self.memo]\n\n\n\nclass _Unpickler:\n def __init__(self, obj, memo: list) -> None:\n self.obj = obj\n self.memo = memo\n self._unwrapped = [None] * len(memo)\n\n def tag(self, index, o):\n assert self._unwrapped[index] is None\n self._unwrapped[index] = o\n\n def unwrap(self, o, index=None):\n if type(o) in _BASIC_TYPES:\n return o\n assert type(o) is list\n\n if o[0] == \"type\":\n return _find_class(o[1])\n\n # reference\n if type(o[0]) is int:\n assert index is None # index should be None\n index = o[0]\n if self._unwrapped[index] is None:\n o = self.memo[index]\n assert type(o) is list\n assert type(o[0]) is str\n self.unwrap(o, index)\n assert self._unwrapped[index] is not None\n return self._unwrapped[index]\n \n # concrete reference type\n if o[0] == \"tuple\":\n ret = tuple([self.unwrap(i) for i in o[1]])\n self.tag(index, ret)\n return ret\n if o[0] == \"bytes\":\n ret = bytes(o[1])\n self.tag(index, ret)\n return ret\n if o[0] == \"list\":\n ret = []\n self.tag(index, ret)\n for i in o[1]:\n ret.append(self.unwrap(i))\n return ret\n if o[0] == \"dict\":\n ret = {}\n self.tag(index, ret)\n for k,v in o[1]:\n ret[self.unwrap(k)] = self.unwrap(v)\n return ret\n \n # generic object\n cls = _find_class(o[0])\n # if getattr(cls, '__struct__', False):\n if False:\n inst = cls.fromstruct(struct.fromhex(o[1]))\n self.tag(index, inst)\n return inst\n else:\n _, newargs, state = o\n # create uninitialized instance\n new_f = getattr(cls, \"__new__\")\n if newargs is not None:\n newargs = [self.unwrap(i) for i in newargs]\n inst = new_f(cls, *newargs)\n else:\n inst = new_f(cls)\n self.tag(index, inst)\n # restore state\n if state is not None:\n for k,v in state.items():\n setattr(inst, k, self.unwrap(v))\n return inst\n\n def run_pipe(self):\n return self.unwrap(self.obj)\n\n\ndef _wrap(o):\n return _Pickler(o).run_pipe()\n\ndef _unwrap(packed: list):\n return _Unpickler(*packed).run_pipe()\n\ndef dumps(o) -> bytes:\n o = _wrap(o)\n return json.dumps(o).encode()\n\ndef loads(b) -> object:\n assert type(b) is bytes\n o = json.loads(b.decode())\n return _unwrap(o)"; const char kPythonLibs_this[] = "print(\"\"\"The Zen of Python, by Tim Peters\n\nBeautiful is better than ugly.\nExplicit is better than implicit.\nSimple is better than complex.\nComplex is better than complicated.\nFlat is better than nested.\nSparse is better than dense.\nReadability counts.\nSpecial cases aren't special enough to break the rules.\nAlthough practicality beats purity.\nErrors should never pass silently.\nUnless explicitly silenced.\nIn the face of ambiguity, refuse the temptation to guess.\nThere should be one-- and preferably only one --obvious way to do it.\nAlthough that way may not be obvious at first unless you're Dutch.\nNow is better than never.\nAlthough never is often better than *right* now.\nIf the implementation is hard to explain, it's a bad idea.\nIf the implementation is easy to explain, it may be a good idea.\nNamespaces are one honking great idea -- let's do more of those!\"\"\")"; -const char kPythonLibs_typing[] = "class _Placeholder:\n def __init__(self, *args, **kwargs):\n pass\n def __getitem__(self, *args):\n return self\n def __call__(self, *args, **kwargs):\n return self\n def __and__(self, other):\n return self\n def __or__(self, other):\n return self\n def __xor__(self, other):\n return self\n\n\n_PLACEHOLDER = _Placeholder()\n\nList = _PLACEHOLDER\nDict = _PLACEHOLDER\nTuple = _PLACEHOLDER\nSet = _PLACEHOLDER\nAny = _PLACEHOLDER\nUnion = _PLACEHOLDER\nOptional = _PLACEHOLDER\nCallable = _PLACEHOLDER\nType = _PLACEHOLDER\n\nLiteral = _PLACEHOLDER\nLiteralString = _PLACEHOLDER\n\nIterable = _PLACEHOLDER\nGenerator = _PLACEHOLDER\n\nHashable = _PLACEHOLDER\n\nTypeVar = _PLACEHOLDER\nSelf = _PLACEHOLDER\n\nProtocol = object\nGeneric = object\n\nTYPE_CHECKING = False\n\n# decorators\noverload = lambda x: x\nfinal = lambda x: x\n"; +const char kPythonLibs_typing[] = "class _Placeholder:\n def __init__(self, *args, **kwargs):\n pass\n def __getitem__(self, *args):\n return self\n def __call__(self, *args, **kwargs):\n return self\n def __and__(self, other):\n return self\n def __or__(self, other):\n return self\n def __xor__(self, other):\n return self\n\n\n_PLACEHOLDER = _Placeholder()\n\nList = _PLACEHOLDER\nDict = _PLACEHOLDER\nTuple = _PLACEHOLDER\nSet = _PLACEHOLDER\nAny = _PLACEHOLDER\nUnion = _PLACEHOLDER\nOptional = _PLACEHOLDER\nCallable = _PLACEHOLDER\nType = _PLACEHOLDER\n\nLiteral = _PLACEHOLDER\nLiteralString = _PLACEHOLDER\n\nIterable = _PLACEHOLDER\nGenerator = _PLACEHOLDER\nIterator = _PLACEHOLDER\n\nHashable = _PLACEHOLDER\n\nTypeVar = _PLACEHOLDER\nSelf = _PLACEHOLDER\n\nProtocol = object\nGeneric = object\n\nTYPE_CHECKING = False\n\n# decorators\noverload = lambda x: x\nfinal = lambda x: x\n"; const char* load_kPythonLib(const char* name) { if (strchr(name, '.') != NULL) return NULL; diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 50fdd7a2..a37c95b1 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -8,7 +8,6 @@ #include "pocketpy/objects/error.h" #include -static bool stack_unpack_sequence(VM* self, uint16_t arg); static bool stack_format_object(VM* self, c11_sv spec); #define CHECK_RETURN_FROM_EXCEPT_OR_FINALLY() \ @@ -839,7 +838,71 @@ FrameResult VM__run_top_frame(VM* self) { } //////// case OP_UNPACK_SEQUENCE: { - if(!stack_unpack_sequence(self, byte.arg)) goto __ERROR; + py_TValue* p; + int length; + + switch(TOP()->type) { + case tp_tuple: { + length = py_tuple_len(TOP()); + p = py_tuple_data(TOP()); + break; + } + case tp_list: { + length = py_list_len(TOP()); + p = py_list_data(TOP()); + break; + } + case tp_vec2i: { + length = 2; + if(byte.arg != length) break; + c11_vec2i val = py_tovec2i(TOP()); + POP(); + py_newint(SP()++, val.x); + py_newint(SP()++, val.y); + DISPATCH(); + } + case tp_vec2: { + length = 2; + if(byte.arg != length) break; + c11_vec2 val = py_tovec2(TOP()); + POP(); + py_newfloat(SP()++, val.x); + py_newfloat(SP()++, val.y); + DISPATCH(); + } + case tp_vec3i: { + length = 3; + if(byte.arg != length) break; + c11_vec3i val = py_tovec3i(TOP()); + POP(); + py_newint(SP()++, val.x); + py_newint(SP()++, val.y); + py_newint(SP()++, val.z); + DISPATCH(); + } + case tp_vec3: { + length = 3; + if(byte.arg != length) break; + c11_vec3 val = py_tovec3(TOP()); + POP(); + py_newfloat(SP()++, val.x); + py_newfloat(SP()++, val.y); + py_newfloat(SP()++, val.z); + DISPATCH(); + } + default: { + TypeError("expected list or tuple to unpack, got %t", TOP()->type); + goto __ERROR; + } + } + if(length != byte.arg) { + ValueError("expected %d values to unpack, got %d", byte.arg, length); + goto __ERROR; + } + POP(); + for(int i = 0; i < length; i++) { + PUSH(p + i); + } DISPATCH(); } case OP_UNPACK_EX: { @@ -1141,18 +1204,6 @@ bool py_binaryop(py_Ref lhs, py_Ref rhs, py_Name op, py_Name rop) { return ok; } -static bool stack_unpack_sequence(VM* self, uint16_t arg) { - py_TValue* p; - int length = pk_arrayview(TOP(), &p); - if(length == -1) return TypeError("expected list or tuple to unpack, got %t", TOP()->type); - if(length != arg) return ValueError("expected %d values to unpack, got %d", arg, length); - POP(); - for(int i = 0; i < length; i++) { - PUSH(p + i); - } - return true; -} - static bool stack_format_object(VM* self, c11_sv spec) { // format TOS via `spec` inplace // spec: '!r:.2f', '.2f' diff --git a/src/public/py_array.c b/src/public/py_array.c index 23f7eada..478333e7 100644 --- a/src/public/py_array.c +++ b/src/public/py_array.c @@ -1,9 +1,6 @@ #include "pocketpy/pocketpy.h" - -#include "pocketpy/common/utils.h" #include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" -#include "pocketpy/common/sstream.h" typedef struct array_iterator { py_TValue* p; diff --git a/tests/80_linalg.py b/tests/80_linalg.py index dbc91b22..97a1bdb2 100644 --- a/tests/80_linalg.py +++ b/tests/80_linalg.py @@ -398,3 +398,12 @@ assert vec2(vec2i.LEFT) == vec2(-1, 0) assert vec2(vec2i.RIGHT) == vec2(1, 0) assert vec3(vec3i.ONE) == vec3(1, 1, 1) assert vec3(vec3i.ZERO) == vec3(0, 0, 0) + +x, y = vec2i(1, 2) +assert x == 1 and y == 2 +x, y, z = vec3i(1, 2, 3) +assert x == 1 and y == 2 and z == 3 +x, y = vec2(3.0, 4.0) +assert x == 3.0 and y == 4.0 +x, y, z = vec3(1.0, 2.0, 3.0) +assert x == 1.0 and y == 2.0 and z == 3.0