diff --git a/include/pocketpy/common/str.h b/include/pocketpy/common/str.h index 14cd1d43..d64ab935 100644 --- a/include/pocketpy/common/str.h +++ b/include/pocketpy/common/str.h @@ -30,6 +30,7 @@ bool c11__sveq2(c11_sv a, const char* b); c11_string* c11_string__new(const char* data); c11_string* c11_string__new2(const char* data, int size); +c11_string* c11_string__new3(const char* fmt, ...); void c11_string__ctor(c11_string* self, const char* data); void c11_string__ctor2(c11_string* self, const char* data, int size); c11_string* c11_string__copy(c11_string* self); @@ -45,6 +46,7 @@ c11_sv c11_sv__slice(c11_sv sv, int start); c11_sv c11_sv__slice2(c11_sv sv, int start, int stop); c11_sv c11_sv__strip(c11_sv sv, c11_sv chars, bool left, bool right); int c11_sv__index(c11_sv self, char c); +int c11_sv__rindex(c11_sv self, char c); int c11_sv__index2(c11_sv self, c11_sv sub, int start); int c11_sv__count(c11_sv self, c11_sv sub); diff --git a/include/pocketpy/interpreter/vfs.h b/include/pocketpy/interpreter/vfs.h new file mode 100644 index 00000000..e80ac031 --- /dev/null +++ b/include/pocketpy/interpreter/vfs.h @@ -0,0 +1,44 @@ +#pragma once + +#include "pocketpy/common/vector.h" +#include "pocketpy/common/str.h" +#include "pocketpy/pocketpy.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct VfsEntry { + bool is_file; + + union { + struct { + int size; + unsigned char* data; + } _file; + + c11_vector _dir; + }; +} VfsEntry; + +#define SMALLMAP_T__HEADER +#define K c11_sv +#define V VfsEntry +#define NAME VfsDir +#define less(a, b) (c11_sv__cmp((a), (b)) < 0) +#define equal(a, b) (c11_sv__cmp((a), (b)) == 0) +#include "pocketpy/xmacros/smallmap.h" +#undef SMALLMAP_T__HEADER + +typedef struct Vfs { + VfsEntry root; +} Vfs; + +void Vfs__ctor(Vfs* self); +void Vfs__dtor(Vfs* self); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/pocketpy/interpreter/vm.h b/include/pocketpy/interpreter/vm.h index 32fc5b61..80d6f7b3 100644 --- a/include/pocketpy/interpreter/vm.h +++ b/include/pocketpy/interpreter/vm.h @@ -4,6 +4,7 @@ #include "pocketpy/pocketpy.h" #include "pocketpy/interpreter/gc.h" #include "pocketpy/interpreter/frame.h" +#include "pocketpy/interpreter/vfs.h" #ifdef __cplusplus extern "C" { @@ -39,7 +40,7 @@ typedef struct pk_VM { py_TValue main; // __main__ module void (*ceval_on_step)(Frame*, Bytecode); - unsigned char* (*import_file)(const char*); + unsigned char* (*import_file)(const char*, int*); void (*print)(const char*); py_TValue last_retval; @@ -48,6 +49,7 @@ typedef struct pk_VM { py_TValue reg[8]; // users' registers + Vfs __vfs; py_TValue* __curr_class; FuncDecl_ __dynamic_func_decl; py_TValue __vectorcall_buffer[PK_MAX_CO_VARNAMES]; @@ -124,6 +126,8 @@ py_Type pk_Exception__register(); py_TValue pk_builtins__register(); +void pk__add_module_pkpy(); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index 2b87083a..c4d62182 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -92,11 +92,8 @@ void py_newlistn(py_Ref, int n); void py_newdict(py_Ref); void py_newslice(py_Ref); void py_newnativefunc(py_Ref out, py_CFunction); -py_Name py_newfunction(py_Ref out, - const char* sig, - py_CFunction f, - const char* docstring, - int slots); +py_Name + py_newfunction(py_Ref out, const char* sig, py_CFunction f, const char* docstring, int slots); /************* Name Convertions *************/ py_Name py_name(const char*); @@ -262,12 +259,12 @@ py_StackRef py_pushtmp(); bool py_pushmethod(py_Name name); /************* Modules *************/ -py_TmpRef py_newmodule(const char* name, const char* package); -py_TmpRef py_getmodule(const char* name); +py_TmpRef py_newmodule(const char* path); +py_TmpRef py_getmodule(const char* path); /// Import a module. /// The result will be set to `py_retval()`. -bool py_import(const char* name) PY_RAISE; +bool py_import(const char* path) PY_RAISE; /************* Errors *************/ /// Raise an exception by name and message. Always returns false. @@ -286,6 +283,7 @@ bool py_checkexc(); #define RuntimeError(...) py_exception("RuntimeError", __VA_ARGS__) #define ValueError(...) py_exception("ValueError", __VA_ARGS__) #define IndexError(...) py_exception("IndexError", __VA_ARGS__) +#define ImportError(...) py_exception("ImportError", __VA_ARGS__) #define NotImplementedError() py_exception("NotImplementedError", "") #define AttributeError(self, n) \ py_exception("AttributeError", "'%t' object has no attribute '%n'", (self)->type, (n)) @@ -362,6 +360,11 @@ bool py_dict__contains(py_Ref self, py_Ref key); int py_dict__len(py_Ref self); bool py_dict__apply(py_Ref self, bool (*f)(py_Ref key, py_Ref val, void* ctx), void* ctx); +/************* Virtual File System *************/ +unsigned char* py_vfsread(const char* path, int* size); +bool py_vfswrite(const char* path, unsigned char* data, int size); +char** py_vfslist(const char* path, int* length); + /************* Others *************/ int py_replinput(char* buf); diff --git a/include/pocketpy/xmacros/magics.h b/include/pocketpy/xmacros/magics.h index 7e8730c1..51a1a2a7 100644 --- a/include/pocketpy/xmacros/magics.h +++ b/include/pocketpy/xmacros/magics.h @@ -56,6 +56,7 @@ MAGIC_METHOD(__exit__) MAGIC_METHOD(__name__) MAGIC_METHOD(__all__) MAGIC_METHOD(__package__) +MAGIC_METHOD(__module_is_pending__) MAGIC_METHOD(__path__) MAGIC_METHOD(__class__) MAGIC_METHOD(__abs__) diff --git a/include/typings/__builtins.pyi b/include/typings/pkpy.pyi similarity index 59% rename from include/typings/__builtins.pyi rename to include/typings/pkpy.pyi index c33a3f4a..7959752f 100644 --- a/include/typings/__builtins.pyi +++ b/include/typings/pkpy.pyi @@ -2,6 +2,3 @@ from typing import Any def next(iter) -> Any | StopIteration: ... - -def _enable_instance_dict(obj) -> None: - ... diff --git a/python/builtins.py b/python/builtins.py index 2338d4e4..c2e13214 100644 --- a/python/builtins.py +++ b/python/builtins.py @@ -1,4 +1,4 @@ -from __builtins import next as __builtins_next +from pkpy import next as __builtins_next def all(iterable): for i in iterable: diff --git a/python/collections.py b/python/collections.py index ded89cd1..8adffd76 100644 --- a/python/collections.py +++ b/python/collections.py @@ -1,4 +1,4 @@ -from __builtins import _enable_instance_dict +from pkpy import _enable_instance_dict from typing import Generic, TypeVar, Iterable T = TypeVar('T') diff --git a/python/functools.py b/python/functools.py index e8351c09..1758f966 100644 --- a/python/functools.py +++ b/python/functools.py @@ -1,4 +1,4 @@ -from __builtins import next +from pkpy import next class cache: def __init__(self, f): diff --git a/python/itertools.py b/python/itertools.py index 3a448643..202349ca 100644 --- a/python/itertools.py +++ b/python/itertools.py @@ -1,4 +1,4 @@ -from __builtins import next +from pkpy import next def zip_longest(a, b): a = iter(a) diff --git a/src/common/_generated.c b/src/common/_generated.c index 1ba06cee..147a3469 100644 --- a/src/common/_generated.c +++ b/src/common/_generated.c @@ -5,14 +5,14 @@ const char kPythonLibs__enum[] = "class Enum:\n def __init__(self, name, valu const char kPythonLibs__long[] = "# after v1.2.2, int is always 64-bit\nPyLong_SHIFT = 60//2 - 1\n\nPyLong_BASE = 2 ** PyLong_SHIFT\nPyLong_MASK = PyLong_BASE - 1\nPyLong_DECIMAL_SHIFT = 4\nPyLong_DECIMAL_BASE = 10 ** PyLong_DECIMAL_SHIFT\n\n##############################################################\n\ndef ulong_fromint(x: int):\n # return a list of digits and sign\n if x == 0: return [0], 1\n sign = 1 if x > 0 else -1\n if sign < 0: x = -x\n res = []\n while x:\n res.append(x & PyLong_MASK)\n x >>= PyLong_SHIFT\n return res, sign\n\ndef ulong_cmp(a: list, b: list) -> int:\n # return 1 if a>b, -1 if a len(b): return 1\n if len(a) < len(b): return -1\n for i in range(len(a)-1, -1, -1):\n if a[i] > b[i]: return 1\n if a[i] < b[i]: return -1\n return 0\n\ndef ulong_pad_(a: list, size: int):\n # pad leading zeros to have `size` digits\n delta = size - len(a)\n if delta > 0:\n a.extend([0] * delta)\n\ndef ulong_unpad_(a: list):\n # remove leading zeros\n while len(a)>1 and a[-1]==0:\n a.pop()\n\ndef ulong_add(a: list, b: list) -> list:\n res = [0] * max(len(a), len(b))\n ulong_pad_(a, len(res))\n ulong_pad_(b, len(res))\n carry = 0\n for i in range(len(res)):\n carry += a[i] + b[i]\n res[i] = carry & PyLong_MASK\n carry >>= PyLong_SHIFT\n if carry > 0:\n res.append(carry)\n return res\n\ndef ulong_inc_(a: list):\n a[0] += 1\n for i in range(len(a)):\n if a[i] < PyLong_BASE: break\n a[i] -= PyLong_BASE\n if i+1 == len(a):\n a.append(1)\n else:\n a[i+1] += 1\n \n\ndef ulong_sub(a: list, b: list) -> list:\n # a >= b\n res = []\n borrow = 0\n for i in range(len(b)):\n tmp = a[i] - b[i] - borrow\n if tmp < 0:\n tmp += PyLong_BASE\n borrow = 1\n else:\n borrow = 0\n res.append(tmp)\n for i in range(len(b), len(a)):\n tmp = a[i] - borrow\n if tmp < 0:\n tmp += PyLong_BASE\n borrow = 1\n else:\n borrow = 0\n res.append(tmp)\n ulong_unpad_(res)\n return res\n\ndef ulong_divmodi(a: list, b: int):\n # b > 0\n res = []\n carry = 0\n for i in range(len(a)-1, -1, -1):\n carry <<= PyLong_SHIFT\n carry += a[i]\n res.append(carry // b)\n carry %= b\n res.reverse()\n ulong_unpad_(res)\n return res, carry\n\n\ndef ulong_divmod(a: list, b: list):\n\n if ulong_cmp(a, b) < 0:\n return [0], a\n\n if len(b) == 1:\n q, r = ulong_divmodi(a, b[0])\n r, _ = ulong_fromint(r)\n return q, r\n\n max = (len(a) - len(b)) * PyLong_SHIFT + \x5c\n (a[-1].bit_length() - b[-1].bit_length())\n\n low = [0]\n\n high = (max // PyLong_SHIFT) * [0] + \x5c\n [(2**(max % PyLong_SHIFT)) & PyLong_MASK]\n\n while ulong_cmp(low, high) < 0:\n ulong_inc_(high)\n mid, r = ulong_divmodi(ulong_add(low, high), 2)\n if ulong_cmp(a, ulong_mul(b, mid)) >= 0:\n low = mid\n else:\n high = ulong_sub(mid, [1])\n\n q = [0] * (len(a) - len(b) + 1)\n while ulong_cmp(a, ulong_mul(b, low)) >= 0:\n q = ulong_add(q, low)\n a = ulong_sub(a, ulong_mul(b, low))\n ulong_unpad_(q)\n return q, a\n\ndef ulong_floordivi(a: list, b: int):\n # b > 0\n return ulong_divmodi(a, b)[0]\n\ndef ulong_muli(a: list, b: int):\n # b >= 0\n res = [0] * len(a)\n carry = 0\n for i in range(len(a)):\n carry += a[i] * b\n res[i] = carry & PyLong_MASK\n carry >>= PyLong_SHIFT\n if carry > 0:\n res.append(carry)\n return res\n\ndef ulong_mul(a: list, b: list):\n N = len(a) + len(b)\n # use grade-school multiplication\n res = [0] * N\n for i in range(len(a)):\n carry = 0\n for j in range(len(b)):\n carry += res[i+j] + a[i] * b[j]\n res[i+j] = carry & PyLong_MASK\n carry >>= PyLong_SHIFT\n res[i+len(b)] = carry\n ulong_unpad_(res)\n return res\n\ndef ulong_powi(a: list, b: int):\n # b >= 0\n if b == 0: return [1]\n res = [1]\n while b:\n if b & 1:\n res = ulong_mul(res, a)\n a = ulong_mul(a, a)\n b >>= 1\n return res\n\ndef ulong_repr(x: list) -> str:\n res = []\n while len(x)>1 or x[0]>0: # non-zero\n x, r = ulong_divmodi(x, PyLong_DECIMAL_BASE)\n res.append(str(r).zfill(PyLong_DECIMAL_SHIFT))\n res.reverse()\n s = ''.join(res)\n if len(s) == 0: return '0'\n if len(s) > 1: s = s.lstrip('0')\n return s\n\ndef ulong_fromstr(s: str):\n if s[-1] == 'L':\n s = s[:-1]\n res, base = [0], [1]\n if s[0] == '-':\n sign = -1\n s = s[1:]\n else:\n sign = 1\n s = s[::-1]\n for c in s:\n c = ord(c) - 48\n assert 0 <= c <= 9\n res = ulong_add(res, ulong_muli(base, c))\n base = ulong_muli(base, 10)\n return res, sign\n\nclass long:\n def __init__(self, x):\n if type(x) is tuple:\n self.digits, self.sign = x\n elif type(x) is int:\n self.digits, self.sign = ulong_fromint(x)\n elif type(x) is float:\n self.digits, self.sign = ulong_fromint(int(x))\n elif type(x) is str:\n self.digits, self.sign = ulong_fromstr(x)\n elif type(x) is long:\n self.digits, self.sign = x.digits.copy(), x.sign\n else:\n raise TypeError('expected int or str')\n \n def __len__(self):\n return len(self.digits)\n\n def __add__(self, other):\n if type(other) is int:\n other = long(other)\n elif type(other) is not long:\n return NotImplemented\n if self.sign == other.sign:\n return long((ulong_add(self.digits, other.digits), self.sign))\n else:\n cmp = ulong_cmp(self.digits, other.digits)\n if cmp == 0:\n return long(0)\n if cmp > 0:\n return long((ulong_sub(self.digits, other.digits), self.sign))\n else:\n return long((ulong_sub(other.digits, self.digits), other.sign))\n \n def __radd__(self, other):\n return self.__add__(other)\n \n def __sub__(self, other):\n if type(other) is int:\n other = long(other)\n elif type(other) is not long:\n return NotImplemented\n if self.sign != other.sign:\n return long((ulong_add(self.digits, other.digits), self.sign))\n cmp = ulong_cmp(self.digits, other.digits)\n if cmp == 0:\n return long(0)\n if cmp > 0:\n return long((ulong_sub(self.digits, other.digits), self.sign))\n else:\n return long((ulong_sub(other.digits, self.digits), -other.sign))\n \n def __rsub__(self, other):\n if type(other) is int:\n other = long(other)\n elif type(other) is not long:\n return NotImplemented\n return other.__sub__(self)\n \n def __mul__(self, other):\n if type(other) is int:\n return long((\n ulong_muli(self.digits, abs(other)),\n self.sign * (1 if other >= 0 else -1)\n ))\n elif type(other) is long:\n return long((\n ulong_mul(self.digits, other.digits),\n self.sign * other.sign\n ))\n return NotImplemented\n \n def __rmul__(self, other):\n return self.__mul__(other)\n \n #######################################################\n def __divmod__(self, other):\n if type(other) is int:\n assert self.sign == 1 and other > 0\n q, r = ulong_divmodi(self.digits, other)\n return long((q, 1)), r\n if type(other) is long:\n assert self.sign == 1 and other.sign == 1\n q, r = ulong_divmod(self.digits, other.digits)\n assert len(other)>1 or other.digits[0]>0\n return long((q, 1)), long((r, 1))\n raise NotImplementedError\n\n def __floordiv__(self, other):\n return self.__divmod__(other)[0]\n\n def __mod__(self, other):\n return self.__divmod__(other)[1]\n\n def __pow__(self, other: int):\n assert type(other) is int and other >= 0\n if self.sign == -1 and other & 1:\n sign = -1\n else:\n sign = 1\n return long((ulong_powi(self.digits, other), sign))\n \n def __lshift__(self, other: int):\n assert type(other) is int and other >= 0\n x = self.digits.copy()\n q, r = divmod(other, PyLong_SHIFT)\n x = [0]*q + x\n for _ in range(r): x = ulong_muli(x, 2)\n return long((x, self.sign))\n \n def __rshift__(self, other: int):\n assert type(other) is int and other >= 0\n x = self.digits.copy()\n q, r = divmod(other, PyLong_SHIFT)\n x = x[q:]\n if not x: return long(0)\n for _ in range(r): x = ulong_floordivi(x, 2)\n return long((x, self.sign))\n \n def __neg__(self):\n return long((self.digits, -self.sign))\n \n def __cmp__(self, other):\n if type(other) is int:\n other = long(other)\n elif type(other) is not long:\n return NotImplemented\n if self.sign > other.sign:\n return 1\n elif self.sign < other.sign:\n return -1\n else:\n return ulong_cmp(self.digits, other.digits)\n \n def __eq__(self, other):\n return self.__cmp__(other) == 0\n def __lt__(self, other):\n return self.__cmp__(other) < 0\n def __le__(self, other):\n return self.__cmp__(other) <= 0\n def __gt__(self, other):\n return self.__cmp__(other) > 0\n def __ge__(self, other):\n return self.__cmp__(other) >= 0\n \n def __repr__(self):\n prefix = '-' if self.sign < 0 else ''\n return prefix + ulong_repr(self.digits) + 'L'\n"; const char kPythonLibs__set[] = "class set:\n def __init__(self, iterable=None):\n iterable = iterable or []\n self._a = {}\n self.update(iterable)\n\n def add(self, elem):\n self._a[elem] = None\n \n def discard(self, elem):\n self._a.pop(elem, None)\n\n def remove(self, elem):\n del self._a[elem]\n \n def clear(self):\n self._a.clear()\n\n def update(self, other):\n for elem in other:\n self.add(elem)\n\n def __len__(self):\n return len(self._a)\n \n def copy(self):\n return set(self._a.keys())\n \n def __and__(self, other):\n return {elem for elem in self if elem in other}\n\n def __sub__(self, other):\n return {elem for elem in self if elem not in other}\n \n def __or__(self, other):\n ret = self.copy()\n ret.update(other)\n return ret\n\n def __xor__(self, other): \n _0 = self - other\n _1 = other - self\n return _0 | _1\n\n def union(self, other):\n return self | other\n\n def intersection(self, other):\n return self & other\n\n def difference(self, other):\n return self - other\n\n def symmetric_difference(self, other): \n return self ^ other\n \n def __eq__(self, other):\n if not isinstance(other, set):\n return NotImplemented\n return len(self ^ other) == 0\n\n def isdisjoint(self, other):\n return len(self & other) == 0\n \n def issubset(self, other):\n return len(self - other) == 0\n \n def issuperset(self, other):\n return len(other - self) == 0\n\n def __contains__(self, elem):\n return elem in self._a\n \n def __repr__(self):\n if len(self) == 0:\n return 'set()'\n return '{'+ ', '.join([repr(i) for i in self._a.keys()]) + '}'\n \n def __iter__(self):\n return iter(self._a.keys())"; const char kPythonLibs_bisect[] = "\"\"\"Bisection algorithms.\"\"\"\n\ndef insort_right(a, x, lo=0, hi=None):\n \"\"\"Insert item x in list a, and keep it sorted assuming a is sorted.\n\n If x is already in a, insert it to the right of the rightmost x.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n lo = bisect_right(a, x, lo, hi)\n a.insert(lo, x)\n\ndef bisect_right(a, x, lo=0, hi=None):\n \"\"\"Return the index where to insert item x in list a, assuming a is sorted.\n\n The return value i is such that all e in a[:i] have e <= x, and all e in\n a[i:] have e > x. So if x already appears in the list, a.insert(x) will\n insert just after the rightmost x already there.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n if lo < 0:\n raise ValueError('lo must be non-negative')\n if hi is None:\n hi = len(a)\n while lo < hi:\n mid = (lo+hi)//2\n if x < a[mid]: hi = mid\n else: lo = mid+1\n return lo\n\ndef insort_left(a, x, lo=0, hi=None):\n \"\"\"Insert item x in list a, and keep it sorted assuming a is sorted.\n\n If x is already in a, insert it to the left of the leftmost x.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n lo = bisect_left(a, x, lo, hi)\n a.insert(lo, x)\n\n\ndef bisect_left(a, x, lo=0, hi=None):\n \"\"\"Return the index where to insert item x in list a, assuming a is sorted.\n\n The return value i is such that all e in a[:i] have e < x, and all e in\n a[i:] have e >= x. So if x already appears in the list, a.insert(x) will\n insert just before the leftmost x already there.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n if lo < 0:\n raise ValueError('lo must be non-negative')\n if hi is None:\n hi = len(a)\n while lo < hi:\n mid = (lo+hi)//2\n if a[mid] < x: lo = mid+1\n else: hi = mid\n return lo\n\n# Create aliases\nbisect = bisect_right\ninsort = insort_right\n"; -const char kPythonLibs_builtins[] = "from __builtins import next as __builtins_next\n\ndef all(iterable):\n for i in iterable:\n if not i:\n return False\n return True\n\ndef any(iterable):\n for i in iterable:\n if i:\n return True\n return False\n\ndef enumerate(iterable, start=0):\n n = start\n for elem in iterable:\n yield n, elem\n n += 1\n\ndef sum(iterable):\n res = 0\n for i in iterable:\n res += i\n return res\n\ndef map(f, iterable):\n for i in iterable:\n yield f(i)\n\ndef filter(f, iterable):\n for i in iterable:\n if f(i):\n yield i\n\ndef zip(a, b):\n a = iter(a)\n b = iter(b)\n while True:\n ai = __builtins_next(a)\n bi = __builtins_next(b)\n if ai is StopIteration or bi is StopIteration:\n break\n yield ai, bi\n\ndef reversed(iterable):\n a = list(iterable)\n a.reverse()\n return a\n\ndef sorted(iterable, key=None, reverse=False):\n a = list(iterable)\n a.sort(key=key, reverse=reverse)\n return a\n\n##### str #####\ndef __format_string(self: str, *args, **kwargs) -> str:\n def tokenizeString(s: str):\n tokens = []\n L, R = 0,0\n \n mode = None\n curArg = 0\n # lookingForKword = False\n \n while(R int:\n n = 0\n for item in self:\n if item == x:\n n += 1\n return n\n \n def extend(self, iterable: Iterable[T]):\n for x in iterable:\n self.append(x)\n\n def extendleft(self, iterable: Iterable[T]):\n for x in iterable:\n self.appendleft(x)\n \n def pop(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n self._tail = (self._tail - 1 + self._capacity) % self._capacity\n return self._data[self._tail]\n \n def popleft(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n x = self._data[self._head]\n self._head = (self._head + 1) % self._capacity\n return x\n \n def clear(self):\n i = self._head\n while i != self._tail:\n self._data[i] = None\n i = (i + 1) % self._capacity\n self._head = 0\n self._tail = 0\n\n def rotate(self, n: int = 1):\n if len(self) == 0:\n return\n if n > 0:\n n = n % len(self)\n for _ in range(n):\n self.appendleft(self.pop())\n elif n < 0:\n n = -n % len(self)\n for _ in range(n):\n self.append(self.popleft())\n\n def __len__(self) -> int:\n return (self._tail - self._head + self._capacity) % self._capacity\n\n def __contains__(self, x: object) -> bool:\n for item in self:\n if item == x:\n return True\n return False\n \n def __iter__(self):\n i = self._head\n while i != self._tail:\n yield self._data[i]\n i = (i + 1) % self._capacity\n\n def __eq__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return False\n if len(self) != len(other):\n return False\n for x, y in zip(self, other):\n if x != y:\n return False\n return True\n \n def __repr__(self) -> str:\n return f\"deque({list(self)!r})\"\n\n"; +const char kPythonLibs_collections[] = "from pkpy import _enable_instance_dict\nfrom typing import Generic, TypeVar, Iterable\n\nT = TypeVar('T')\n\ndef Counter(iterable: Iterable[T]):\n a: dict[T, int] = {}\n for x in iterable:\n if x in a:\n a[x] += 1\n else:\n a[x] = 1\n return a\n\n\nclass defaultdict(dict):\n def __init__(self, default_factory, *args):\n super().__init__(*args)\n _enable_instance_dict(self)\n self.default_factory = default_factory\n\n def __missing__(self, key):\n self[key] = self.default_factory()\n return self[key]\n\n def __repr__(self) -> str:\n return f\"defaultdict({self.default_factory}, {super().__repr__()})\"\n\n def copy(self):\n return defaultdict(self.default_factory, self)\n\n\nclass deque(Generic[T]):\n _data: list[T]\n _head: int\n _tail: int\n _capacity: int\n\n def __init__(self, iterable: Iterable[T] = None):\n self._data = [None] * 8 # initial capacity\n self._head = 0\n self._tail = 0\n self._capacity = len(self._data)\n\n if iterable is not None:\n self.extend(iterable)\n\n def __resize_2x(self):\n backup = list(self)\n self._capacity *= 2\n self._head = 0\n self._tail = len(backup)\n self._data.clear()\n self._data.extend(backup)\n self._data.extend([None] * (self._capacity - len(backup)))\n\n def append(self, x: T):\n self._data[self._tail] = x\n self._tail = (self._tail + 1) % self._capacity\n if (self._tail + 1) % self._capacity == self._head:\n self.__resize_2x()\n\n def appendleft(self, x: T):\n self._head = (self._head - 1 + self._capacity) % self._capacity\n self._data[self._head] = x\n if (self._tail + 1) % self._capacity == self._head:\n self.__resize_2x()\n\n def copy(self):\n return deque(self)\n \n def count(self, x: T) -> int:\n n = 0\n for item in self:\n if item == x:\n n += 1\n return n\n \n def extend(self, iterable: Iterable[T]):\n for x in iterable:\n self.append(x)\n\n def extendleft(self, iterable: Iterable[T]):\n for x in iterable:\n self.appendleft(x)\n \n def pop(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n self._tail = (self._tail - 1 + self._capacity) % self._capacity\n return self._data[self._tail]\n \n def popleft(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n x = self._data[self._head]\n self._head = (self._head + 1) % self._capacity\n return x\n \n def clear(self):\n i = self._head\n while i != self._tail:\n self._data[i] = None\n i = (i + 1) % self._capacity\n self._head = 0\n self._tail = 0\n\n def rotate(self, n: int = 1):\n if len(self) == 0:\n return\n if n > 0:\n n = n % len(self)\n for _ in range(n):\n self.appendleft(self.pop())\n elif n < 0:\n n = -n % len(self)\n for _ in range(n):\n self.append(self.popleft())\n\n def __len__(self) -> int:\n return (self._tail - self._head + self._capacity) % self._capacity\n\n def __contains__(self, x: object) -> bool:\n for item in self:\n if item == x:\n return True\n return False\n \n def __iter__(self):\n i = self._head\n while i != self._tail:\n yield self._data[i]\n i = (i + 1) % self._capacity\n\n def __eq__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return False\n if len(self) != len(other):\n return False\n for x, y in zip(self, other):\n if x != y:\n return False\n return True\n \n def __repr__(self) -> str:\n return f\"deque({list(self)!r})\"\n\n"; const char kPythonLibs_colorsys[] = "\"\"\"Conversion functions between RGB and other color systems.\n\nThis modules provides two functions for each color system ABC:\n\n rgb_to_abc(r, g, b) --> a, b, c\n abc_to_rgb(a, b, c) --> r, g, b\n\nAll inputs and outputs are triples of floats in the range [0.0...1.0]\n(with the exception of I and Q, which covers a slightly larger range).\nInputs outside the valid range may cause exceptions or invalid outputs.\n\nSupported color systems:\nRGB: Red, Green, Blue components\nYIQ: Luminance, Chrominance (used by composite video signals)\nHLS: Hue, Luminance, Saturation\nHSV: Hue, Saturation, Value\n\"\"\"\n\n# References:\n# http://en.wikipedia.org/wiki/YIQ\n# http://en.wikipedia.org/wiki/HLS_color_space\n# http://en.wikipedia.org/wiki/HSV_color_space\n\n__all__ = [\"rgb_to_yiq\",\"yiq_to_rgb\",\"rgb_to_hls\",\"hls_to_rgb\",\n \"rgb_to_hsv\",\"hsv_to_rgb\"]\n\n# Some floating point constants\n\nONE_THIRD = 1.0/3.0\nONE_SIXTH = 1.0/6.0\nTWO_THIRD = 2.0/3.0\n\n# YIQ: used by composite video signals (linear combinations of RGB)\n# Y: perceived grey level (0.0 == black, 1.0 == white)\n# I, Q: color components\n#\n# There are a great many versions of the constants used in these formulae.\n# The ones in this library uses constants from the FCC version of NTSC.\n\ndef rgb_to_yiq(r, g, b):\n y = 0.30*r + 0.59*g + 0.11*b\n i = 0.74*(r-y) - 0.27*(b-y)\n q = 0.48*(r-y) + 0.41*(b-y)\n return (y, i, q)\n\ndef yiq_to_rgb(y, i, q):\n # r = y + (0.27*q + 0.41*i) / (0.74*0.41 + 0.27*0.48)\n # b = y + (0.74*q - 0.48*i) / (0.74*0.41 + 0.27*0.48)\n # g = y - (0.30*(r-y) + 0.11*(b-y)) / 0.59\n\n r = y + 0.9468822170900693*i + 0.6235565819861433*q\n g = y - 0.27478764629897834*i - 0.6356910791873801*q\n b = y - 1.1085450346420322*i + 1.7090069284064666*q\n\n if r < 0.0:\n r = 0.0\n if g < 0.0:\n g = 0.0\n if b < 0.0:\n b = 0.0\n if r > 1.0:\n r = 1.0\n if g > 1.0:\n g = 1.0\n if b > 1.0:\n b = 1.0\n return (r, g, b)\n\n\n# HLS: Hue, Luminance, Saturation\n# H: position in the spectrum\n# L: color lightness\n# S: color saturation\n\ndef rgb_to_hls(r, g, b):\n maxc = max(r, g, b)\n minc = min(r, g, b)\n sumc = (maxc+minc)\n rangec = (maxc-minc)\n l = sumc/2.0\n if minc == maxc:\n return 0.0, l, 0.0\n if l <= 0.5:\n s = rangec / sumc\n else:\n s = rangec / (2.0-maxc-minc) # Not always 2.0-sumc: gh-106498.\n rc = (maxc-r) / rangec\n gc = (maxc-g) / rangec\n bc = (maxc-b) / rangec\n if r == maxc:\n h = bc-gc\n elif g == maxc:\n h = 2.0+rc-bc\n else:\n h = 4.0+gc-rc\n # h = (h/6.0) % 1.0\n h = h / 6.0\n h = h - int(h)\n return h, l, s\n\ndef hls_to_rgb(h, l, s):\n if s == 0.0:\n return l, l, l\n if l <= 0.5:\n m2 = l * (1.0+s)\n else:\n m2 = l+s-(l*s)\n m1 = 2.0*l - m2\n return (_v(m1, m2, h+ONE_THIRD), _v(m1, m2, h), _v(m1, m2, h-ONE_THIRD))\n\ndef _v(m1, m2, hue):\n # hue = hue % 1.0\n hue = hue - int(hue)\n if hue < ONE_SIXTH:\n return m1 + (m2-m1)*hue*6.0\n if hue < 0.5:\n return m2\n if hue < TWO_THIRD:\n return m1 + (m2-m1)*(TWO_THIRD-hue)*6.0\n return m1\n\n\n# HSV: Hue, Saturation, Value\n# H: position in the spectrum\n# S: color saturation (\"purity\")\n# V: color brightness\n\ndef rgb_to_hsv(r, g, b):\n maxc = max(r, g, b)\n minc = min(r, g, b)\n rangec = (maxc-minc)\n v = maxc\n if minc == maxc:\n return 0.0, 0.0, v\n s = rangec / maxc\n rc = (maxc-r) / rangec\n gc = (maxc-g) / rangec\n bc = (maxc-b) / rangec\n if r == maxc:\n h = bc-gc\n elif g == maxc:\n h = 2.0+rc-bc\n else:\n h = 4.0+gc-rc\n # h = (h/6.0) % 1.0\n h = h / 6.0\n h = h - int(h)\n return h, s, v\n\ndef hsv_to_rgb(h, s, v):\n if s == 0.0:\n return v, v, v\n i = int(h*6.0) # XXX assume int() truncates!\n f = (h*6.0) - i\n p = v*(1.0 - s)\n q = v*(1.0 - s*f)\n t = v*(1.0 - s*(1.0-f))\n i = i%6\n if i == 0:\n return v, t, p\n if i == 1:\n return q, v, p\n if i == 2:\n return p, v, t\n if i == 3:\n return p, q, v\n if i == 4:\n return t, p, v\n if i == 5:\n return v, p, q\n # Cannot get here"; const char kPythonLibs_datetime[] = "from time import localtime\n\nclass timedelta:\n def __init__(self, days=0, seconds=0):\n self.days = days\n self.seconds = seconds\n\n def __repr__(self):\n return f\"datetime.timedelta(days={self.days}, seconds={self.seconds})\"\n\n def __eq__(self, other: 'timedelta') -> bool:\n if type(other) is not timedelta:\n return NotImplemented\n return (self.days, self.seconds) == (other.days, other.seconds)\n\n def __lt__(self, other: 'timedelta') -> bool:\n if type(other) is not timedelta:\n return NotImplemented\n return (self.days, self.seconds) < (other.days, other.seconds)\n\n def __le__(self, other: 'timedelta') -> bool:\n if type(other) is not timedelta:\n return NotImplemented\n return (self.days, self.seconds) <= (other.days, other.seconds)\n\n def __gt__(self, other: 'timedelta') -> bool:\n if type(other) is not timedelta:\n return NotImplemented\n return (self.days, self.seconds) > (other.days, other.seconds)\n\n def __ge__(self, other: 'timedelta') -> bool:\n if type(other) is not timedelta:\n return NotImplemented\n return (self.days, self.seconds) >= (other.days, other.seconds)\n\n\nclass date:\n def __init__(self, year: int, month: int, day: int):\n self.year = year\n self.month = month\n self.day = day\n\n @staticmethod\n def today():\n t = localtime()\n return date(t.tm_year, t.tm_mon, t.tm_mday)\n\n def __eq__(self, other: 'date') -> bool:\n if type(other) is not date:\n return NotImplemented\n return (self.year, self.month, self.day) == (other.year, other.month, other.day)\n\n def __lt__(self, other: 'date') -> bool:\n if type(other) is not date:\n return NotImplemented\n return (self.year, self.month, self.day) < (other.year, other.month, other.day)\n\n def __le__(self, other: 'date') -> bool:\n if type(other) is not date:\n return NotImplemented\n return (self.year, self.month, self.day) <= (other.year, other.month, other.day)\n\n def __gt__(self, other: 'date') -> bool:\n if type(other) is not date:\n return NotImplemented\n return (self.year, self.month, self.day) > (other.year, other.month, other.day)\n\n def __ge__(self, other: 'date') -> bool:\n if type(other) is not date:\n return NotImplemented\n return (self.year, self.month, self.day) >= (other.year, other.month, other.day)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02}\"\n\n def __repr__(self):\n return f\"datetime.date({self.year}, {self.month}, {self.day})\"\n\n\nclass datetime(date):\n def __init__(self, year: int, month: int, day: int, hour: int, minute: int, second: int):\n super().__init__(year, month, day)\n # Validate and set hour, minute, and second\n if not 0 <= hour <= 23:\n raise ValueError(\"Hour must be between 0 and 23\")\n self.hour = hour\n if not 0 <= minute <= 59:\n raise ValueError(\"Minute must be between 0 and 59\")\n self.minute = minute\n if not 0 <= second <= 59:\n raise ValueError(\"Second must be between 0 and 59\")\n self.second = second\n\n def date(self) -> date:\n return date(self.year, self.month, self.day)\n\n @staticmethod\n def now():\n t = localtime()\n tm_sec = t.tm_sec\n if tm_sec == 60:\n tm_sec = 59\n return datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, tm_sec)\n\n def __str__(self):\n return f\"{self.year}-{self.month:02}-{self.day:02} {self.hour:02}:{self.minute:02}:{self.second:02}\"\n\n def __repr__(self):\n return f\"datetime.datetime({self.year}, {self.month}, {self.day}, {self.hour}, {self.minute}, {self.second})\"\n\n def __eq__(self, other) -> bool:\n if type(other) is not datetime:\n return NotImplemented\n return (self.year, self.month, self.day, self.hour, self.minute, self.second) ==\x5c\n (other.year, other.month, other.day,\n other.hour, other.minute, other.second)\n\n def __lt__(self, other) -> bool:\n if type(other) is not datetime:\n return NotImplemented\n return (self.year, self.month, self.day, self.hour, self.minute, self.second) <\x5c\n (other.year, other.month, other.day,\n other.hour, other.minute, other.second)\n\n def __le__(self, other) -> bool:\n if type(other) is not datetime:\n return NotImplemented\n return (self.year, self.month, self.day, self.hour, self.minute, self.second) <=\x5c\n (other.year, other.month, other.day,\n other.hour, other.minute, other.second)\n\n def __gt__(self, other) -> bool:\n if type(other) is not datetime:\n return NotImplemented\n return (self.year, self.month, self.day, self.hour, self.minute, self.second) >\x5c\n (other.year, other.month, other.day,\n other.hour, other.minute, other.second)\n\n def __ge__(self, other) -> bool:\n if type(other) is not datetime:\n return NotImplemented\n return (self.year, self.month, self.day, self.hour, self.minute, self.second) >=\x5c\n (other.year, other.month, other.day,\n other.hour, other.minute, other.second)\n\n def timestamp(self) -> float:\n raise NotImplementedError\n\n"; -const char kPythonLibs_functools[] = "from __builtins import next\n\nclass cache:\n def __init__(self, f):\n self.f = f\n self.cache = {}\n\n def __call__(self, *args):\n if args not in self.cache:\n self.cache[args] = self.f(*args)\n return self.cache[args]\n \ndef reduce(function, sequence, initial=...):\n it = iter(sequence)\n if initial is ...:\n value = next(it)\n if value is StopIteration:\n raise TypeError(\"reduce() of empty iterable with no initial value\")\n else:\n value = initial\n for element in it:\n value = function(value, element)\n return value\n\nclass partial:\n def __init__(self, f, *args, **kwargs):\n self.f = f\n if not callable(f):\n raise TypeError(\"the first argument must be callable\")\n self.args = args\n self.kwargs = kwargs\n\n def __call__(self, *args, **kwargs):\n kwargs.update(self.kwargs)\n return self.f(*self.args, *args, **kwargs)\n\n"; +const char kPythonLibs_functools[] = "from pkpy import next\n\nclass cache:\n def __init__(self, f):\n self.f = f\n self.cache = {}\n\n def __call__(self, *args):\n if args not in self.cache:\n self.cache[args] = self.f(*args)\n return self.cache[args]\n \ndef reduce(function, sequence, initial=...):\n it = iter(sequence)\n if initial is ...:\n value = next(it)\n if value is StopIteration:\n raise TypeError(\"reduce() of empty iterable with no initial value\")\n else:\n value = initial\n for element in it:\n value = function(value, element)\n return value\n\nclass partial:\n def __init__(self, f, *args, **kwargs):\n self.f = f\n if not callable(f):\n raise TypeError(\"the first argument must be callable\")\n self.args = args\n self.kwargs = kwargs\n\n def __call__(self, *args, **kwargs):\n kwargs.update(self.kwargs)\n return self.f(*self.args, *args, **kwargs)\n\n"; const char kPythonLibs_heapq[] = "# Heap queue algorithm (a.k.a. priority queue)\ndef heappush(heap, item):\n \"\"\"Push item onto heap, maintaining the heap invariant.\"\"\"\n heap.append(item)\n _siftdown(heap, 0, len(heap)-1)\n\ndef heappop(heap):\n \"\"\"Pop the smallest item off the heap, maintaining the heap invariant.\"\"\"\n lastelt = heap.pop() # raises appropriate IndexError if heap is empty\n if heap:\n returnitem = heap[0]\n heap[0] = lastelt\n _siftup(heap, 0)\n return returnitem\n return lastelt\n\ndef heapreplace(heap, item):\n \"\"\"Pop and return the current smallest value, and add the new item.\n\n This is more efficient than heappop() followed by heappush(), and can be\n more appropriate when using a fixed-size heap. Note that the value\n returned may be larger than item! That constrains reasonable uses of\n this routine unless written as part of a conditional replacement:\n\n if item > heap[0]:\n item = heapreplace(heap, item)\n \"\"\"\n returnitem = heap[0] # raises appropriate IndexError if heap is empty\n heap[0] = item\n _siftup(heap, 0)\n return returnitem\n\ndef heappushpop(heap, item):\n \"\"\"Fast version of a heappush followed by a heappop.\"\"\"\n if heap and heap[0] < item:\n item, heap[0] = heap[0], item\n _siftup(heap, 0)\n return item\n\ndef heapify(x):\n \"\"\"Transform list into a heap, in-place, in O(len(x)) time.\"\"\"\n n = len(x)\n # Transform bottom-up. The largest index there's any point to looking at\n # is the largest with a child index in-range, so must have 2*i + 1 < n,\n # or i < (n-1)/2. If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so\n # j-1 is the largest, which is n//2 - 1. If n is odd = 2*j+1, this is\n # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1.\n for i in reversed(range(n//2)):\n _siftup(x, i)\n\n# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos\n# is the index of a leaf with a possibly out-of-order value. Restore the\n# heap invariant.\ndef _siftdown(heap, startpos, pos):\n newitem = heap[pos]\n # Follow the path to the root, moving parents down until finding a place\n # newitem fits.\n while pos > startpos:\n parentpos = (pos - 1) >> 1\n parent = heap[parentpos]\n if newitem < parent:\n heap[pos] = parent\n pos = parentpos\n continue\n break\n heap[pos] = newitem\n\ndef _siftup(heap, pos):\n endpos = len(heap)\n startpos = pos\n newitem = heap[pos]\n # Bubble up the smaller child until hitting a leaf.\n childpos = 2*pos + 1 # leftmost child position\n while childpos < endpos:\n # Set childpos to index of smaller child.\n rightpos = childpos + 1\n if rightpos < endpos and not heap[childpos] < heap[rightpos]:\n childpos = rightpos\n # Move the smaller child up.\n heap[pos] = heap[childpos]\n pos = childpos\n childpos = 2*pos + 1\n # The leaf at pos is empty now. Put newitem there, and bubble it up\n # to its final resting place (by sifting its parents down).\n heap[pos] = newitem\n _siftdown(heap, startpos, pos)"; -const char kPythonLibs_itertools[] = "from __builtins import next\n\ndef zip_longest(a, b):\n a = iter(a)\n b = iter(b)\n while True:\n ai = next(a)\n bi = next(b)\n if ai is StopIteration and bi is StopIteration:\n break\n if ai is StopIteration:\n ai = None\n if bi is StopIteration:\n bi = None\n yield ai, bi\n"; +const char kPythonLibs_itertools[] = "from pkpy import next\n\ndef zip_longest(a, b):\n a = iter(a)\n b = iter(b)\n while True:\n ai = next(a)\n bi = next(b)\n if ai is StopIteration and bi is StopIteration:\n break\n if ai is StopIteration:\n ai = None\n if bi is StopIteration:\n bi = None\n yield ai, bi\n"; 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\nfrom c import struct\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.__path__ + _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 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!\"\"\")"; diff --git a/src/common/str.c b/src/common/str.c index 1fafaf30..17d84710 100644 --- a/src/common/str.c +++ b/src/common/str.c @@ -16,6 +16,17 @@ c11_string* c11_string__new2(const char* data, int size) { return retval; } +c11_string* c11_string__new3(const char* fmt, ...) { + c11_sbuf buf; + c11_sbuf__ctor(&buf); + va_list args; + va_start(args, fmt); + // c11_sbuf__write_vfmt(&buf, fmt, args); + pk_vsprintf(&buf, fmt, args); + va_end(args); + return c11_sbuf__submit(&buf); +} + void c11_string__ctor(c11_string* self, const char* data) { c11_string__ctor2(self, data, strlen(data)); } @@ -124,6 +135,13 @@ int c11_sv__index(c11_sv self, char c) { return -1; } +int c11_sv__rindex(c11_sv self, char c) { + for(int i = self.size - 1; i >= 0; i--) { + if(self.data[i] == c) return i; + } + return -1; +} + int c11_sv__index2(c11_sv self, c11_sv sub, int start) { if(sub.size == 0) return start; int max_end = self.size - sub.size; diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 460d00e9..3ca3ddd6 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -755,6 +755,47 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { } } //////// + case OP_IMPORT_PATH: { + py_Ref path_object = c11__at(py_TValue, &frame->co->consts, byte.arg); + bool ok = py_import(py_tostr(path_object)); + if(!ok) goto __ERROR; + PUSH(py_retval()); + DISPATCH(); + } + case OP_POP_IMPORT_STAR: { + // [module] + pk_NameDict* dict = PyObject__dict(TOP()->_obj); + py_Ref all = pk_NameDict__try_get(dict, __all__); + if(all) { + int length; + py_TValue* p = pk_arrayview(all, &length); + if(!p) { + TypeError("'__all__' must be a list or tuple, got '%t'", all->type); + goto __ERROR; + } + for(int i = 0; i < length; i++) { + py_Name name = py_namev(py_tosv(p + i)); + py_Ref value = pk_NameDict__try_get(dict, name); + if(value == NULL) { + ImportError("cannot import name '%n'", name); + goto __ERROR; + } else { + py_setdict(&frame->module, name, value); + } + } + } else { + for(int i = 0; i < dict->count; i++) { + pk_NameDict_KV* kv = c11__at(pk_NameDict_KV, dict, i); + if(!kv->key) continue; + c11_sv name = py_name2sv(kv->key); + if(name.size == 0 || name.data[0] == '_') continue; + py_setdict(&frame->module, kv->key, &kv->value); + } + } + POP(); + DISPATCH(); + } + //////// case OP_UNPACK_SEQUENCE: { if(!stack_unpack_sequence(self, byte.arg)) goto __ERROR; DISPATCH(); @@ -907,8 +948,9 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) { pk_print_stack(self, frame, (Bytecode){0}); py_BaseException__set_lineno(&self->curr_exception, Frame__lineno(frame), frame->co); __ERROR_RE_RAISE: - do {} while(0); - //printf("error.op: %s, line: %d\n", pk_opname(byte.op), Frame__lineno(frame)); + do { + } while(0); + // printf("error.op: %s, line: %d\n", pk_opname(byte.op), Frame__lineno(frame)); int lineno = py_BaseException__get_lineno(&self->curr_exception, frame->co); py_BaseException__stpush(&self->curr_exception, frame->co->src, diff --git a/src/interpreter/vfs.c b/src/interpreter/vfs.c new file mode 100644 index 00000000..c708fe6c --- /dev/null +++ b/src/interpreter/vfs.c @@ -0,0 +1,131 @@ +#include "pocketpy/interpreter/vfs.h" +#include "pocketpy/interpreter/vm.h" + +#define SMALLMAP_T__SOURCE +#define K c11_sv +#define V VfsEntry +#define NAME VfsDir +#define less(a, b) (c11_sv__cmp((a), (b)) < 0) +#define equal(a, b) (c11_sv__cmp((a), (b)) == 0) +#include "pocketpy/xmacros/smallmap.h" +#undef SMALLMAP_T__SOURCE + +static VfsEntry* Vfs__get(const char* path) { + c11_vector /*T=c11_sv*/ cpnts = c11_sv__split((c11_sv){path, strlen(path)}, '/'); + + VfsEntry* root = &pk_current_vm->__vfs.root; + for(int i = 0; i < cpnts.count; i++) { + c11_sv cpnt = c11__getitem(c11_sv, &cpnts, i); + VfsEntry* entry = VfsDir__try_get(&root->_dir, cpnt); + if(entry == NULL) { + c11_vector__dtor(&cpnts); + return NULL; + } + if(entry->is_file) { + VfsEntry* retval = i == cpnts.count - 1 ? entry : NULL; + c11_vector__dtor(&cpnts); + return retval; + } else { + root = entry; + } + } + c11_vector__dtor(&cpnts); + return root; +} + +static void VfsDir__delete_recursively(VfsDir* self) { + for(int i = 0; i < self->count; i++) { + VfsDir_KV* kv = c11__at(VfsDir_KV, self, i); + free((char*)kv->key.data); + if(kv->value.is_file) { + free(kv->value._file.data); + } else { + VfsDir__delete_recursively(&kv->value._dir); + } + } + VfsDir__dtor(self); +} + +void Vfs__ctor(Vfs* self) { + self->root.is_file = false; + VfsDir__ctor(&self->root._dir); +} + +void Vfs__dtor(Vfs* self) { VfsDir__delete_recursively(&self->root._dir); } + +unsigned char* py_vfsread(const char* path, int* size) { + VfsEntry* entry = Vfs__get(path); + if(entry == NULL || !entry->is_file) return NULL; + *size = entry->_file.size; + unsigned char* retval = malloc(*size); + memcpy(retval, entry->_file.data, *size); + return retval; +} + +static void VfsDir__dupset(VfsDir* self, c11_sv key, VfsEntry value) { + char* p = malloc(key.size); + memcpy(p, key.data, key.size); + VfsDir__set(self, (c11_sv){p, key.size}, value); +} + +bool py_vfswrite(const char* path, unsigned char* data, int size) { + c11_vector /*T=c11_sv*/ cpnts = c11_sv__split((c11_sv){path, strlen(path)}, '/'); + VfsEntry* root = &pk_current_vm->__vfs.root; + for(int i = 0; i < cpnts.count; i++) { + c11_sv cpnt = c11__getitem(c11_sv, &cpnts, i); + VfsEntry* entry = VfsDir__try_get(&root->_dir, cpnt); + if(entry == NULL) { + if(i == cpnts.count - 1) { + // create file + VfsEntry entry = { + .is_file = true, + ._file.size = size, + ._file.data = data, + }; + VfsDir__dupset(&root->_dir, cpnt, entry); + c11_vector__dtor(&cpnts); + return true; + } else { + // create missing directory + VfsEntry entry = { + .is_file = false, + }; + VfsDir__ctor(&entry._dir); + VfsDir__dupset(&root->_dir, cpnt, entry); + } + } else { + if(i == cpnts.count - 1) { + if(!entry->is_file) break; + // update file + free(entry->_file.data); + entry->_file.size = size; + entry->_file.data = data; + c11_vector__dtor(&cpnts); + return true; + } else { + if(entry->is_file) break; + root = entry; + } + } + } + c11_vector__dtor(&cpnts); + return false; +} + +char** py_vfslist(const char* path, int* length) { + VfsEntry* entry = Vfs__get(path); + if(entry == NULL || entry->is_file) return NULL; + *length = 0; + char** ret = malloc(sizeof(char*) * entry->_dir.count); + for(int i = 0; i < entry->_dir.count; i++) { + VfsDir_KV* child = c11__at(VfsDir_KV, &entry->_dir, i); + if(child->value.is_file) { + int size = child->key.size; + ret[i] = malloc(size + 1); + memcpy(ret[i], child->key.data, size); + ret[i][size] = '\0'; + (*length)++; + } + } + return ret; +} \ No newline at end of file diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index a1aff509..5e846e86 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -3,13 +3,10 @@ #include "pocketpy/common/sstream.h" #include "pocketpy/common/utils.h" #include "pocketpy/objects/base.h" +#include "pocketpy/common/_generated.h" #include "pocketpy/pocketpy.h" -#include -#include -#include - -static unsigned char* pk_default_import_file(const char* path) { return NULL; } +static unsigned char* pk_default_import_file(const char* path, int* size) { return NULL; } static void pk_default_print(const char* data) { printf("%s", data); } @@ -39,6 +36,13 @@ static void pk_TypeInfo__ctor(pk_TypeInfo* self, static void pk_TypeInfo__dtor(pk_TypeInfo* self) { c11_vector__dtor(&self->annotated_fields); } +static void save_site_package_to_vfs(const char* name, const char* source) { + char buf[512]; + snprintf(buf, sizeof(buf), "site-packages/%s", name); + bool ok = py_vfswrite(buf, (unsigned char*)source, strlen(source) + 1); + if(!ok) c11__abort("failed to save '%s' to vfs", name); +} + void pk_VM__ctor(pk_VM* self) { self->top_frame = NULL; @@ -56,6 +60,7 @@ void pk_VM__ctor(pk_VM* self) { self->curr_exception = *py_NIL; self->is_stopiteration = false; + Vfs__ctor(&self->__vfs); self->__curr_class = NULL; self->__dynamic_func_decl = NULL; @@ -175,7 +180,23 @@ void pk_VM__ctor(pk_VM* self) { py_newnotimplemented(&tmp); py_setdict(&self->builtins, py_name("NotImplemented"), &tmp); - self->main = *py_newmodule("__main__", NULL); + // add modules + pk__add_module_pkpy(); + + self->main = *py_newmodule("__main__"); + + save_site_package_to_vfs("bisect.py", kPythonLibs_bisect); + save_site_package_to_vfs("cmath.py", kPythonLibs_cmath); + save_site_package_to_vfs("collections.py", kPythonLibs_collections); + save_site_package_to_vfs("colorsys.py", kPythonLibs_colorsys); + save_site_package_to_vfs("datetime.py", kPythonLibs_datetime); + save_site_package_to_vfs("functools.py", kPythonLibs_functools); + save_site_package_to_vfs("heapq.py", kPythonLibs_heapq); + save_site_package_to_vfs("itertools.py", kPythonLibs_itertools); + save_site_package_to_vfs("operator.py", kPythonLibs_operator); + save_site_package_to_vfs("pickle.py", kPythonLibs_pickle); + save_site_package_to_vfs("this.py", kPythonLibs_this); + save_site_package_to_vfs("typing.py", kPythonLibs_typing); } void pk_VM__dtor(pk_VM* self) { @@ -188,6 +209,7 @@ void pk_VM__dtor(pk_VM* self) { c11__foreach(pk_TypeInfo, &self->types, ti) pk_TypeInfo__dtor(ti); c11_vector__dtor(&self->types); ValueStack__clear(&self->stack); + Vfs__dtor(&self->__vfs); } void pk_VM__push_frame(pk_VM* self, Frame* frame) { @@ -559,7 +581,7 @@ void pk_ManagedHeap__mark(pk_ManagedHeap* self) { } void pk_print_stack(pk_VM* self, Frame* frame, Bytecode byte) { - return; + // return; if(frame == NULL) return; py_TValue* sp = self->stack.sp; diff --git a/src/public/internal.c b/src/public/internal.c index 03eb16a5..cc16b862 100644 --- a/src/public/internal.c +++ b/src/public/internal.c @@ -52,9 +52,9 @@ void py_finalize() { pk_MemoryPools__finalize(); } -void py_switchvm(int index){ +void py_switchvm(int index) { if(index < 0 || index >= 16) c11__abort("invalid vm index"); - if(!pk_all_vm[index]){ + if(!pk_all_vm[index]) { pk_all_vm[index] = malloc(sizeof(pk_VM)); pk_VM__ctor(pk_all_vm[index]); } @@ -123,13 +123,9 @@ static void disassemble(CodeObject* co) { for(int j = 0; j < padding; j++) c11_sbuf__write_char(&ss, ' '); - // _opcode_argstr(this, i, byte, co); do { if(Bytecode__is_forward_jump(&byte)) { - c11_sbuf__write_int(&ss, (int16_t)byte.arg); - c11_sbuf__write_cstr(&ss, " (to "); - c11_sbuf__write_int(&ss, (int16_t)byte.arg + i); - c11_sbuf__write_char(&ss, ')'); + pk_sprintf(&ss, "%d (to %d)", (int16_t)byte.arg, (int16_t)byte.arg + i); break; } @@ -138,12 +134,8 @@ static void disassemble(CodeObject* co) { case OP_LOAD_CONST: case OP_FORMAT_STRING: case OP_IMPORT_PATH: { - py_Ref tmp = c11__at(py_TValue, &co->consts, byte.arg); - c11_sbuf__write_cstr(&ss, " ("); - // here we need to use py_repr, however this function is not ready yet - c11_sbuf__write_cstr(&ss, "type)); - c11_sbuf__write_cstr(&ss, "'>)"); + py_Ref path = c11__at(py_TValue, &co->consts, byte.arg); + pk_sprintf(&ss, " (%q)", py_tosv(path)); break; } case OP_LOAD_NAME: @@ -158,32 +150,24 @@ static void disassemble(CodeObject* co) { case OP_GOTO: case OP_DELETE_GLOBAL: case OP_STORE_CLASS_ATTR: { - c11_sbuf__write_cstr(&ss, " ("); - c11_sbuf__write_cstr(&ss, py_name2str(byte.arg)); - c11_sbuf__write_char(&ss, ')'); + pk_sprintf(&ss, " (%n)", byte.arg); break; } case OP_LOAD_FAST: case OP_STORE_FAST: case OP_DELETE_FAST: { py_Name name = c11__getitem(py_Name, &co->varnames, byte.arg); - c11_sbuf__write_cstr(&ss, " ("); - c11_sbuf__write_cstr(&ss, py_name2str(name)); - c11_sbuf__write_char(&ss, ')'); + pk_sprintf(&ss, " (%n)", name); break; } case OP_LOAD_FUNCTION: { const FuncDecl* decl = c11__getitem(FuncDecl*, &co->func_decls, byte.arg); - c11_sbuf__write_cstr(&ss, " ("); - c11_sbuf__write_cstr(&ss, decl->code.name->data); - c11_sbuf__write_char(&ss, ')'); + pk_sprintf(&ss, " (%s)", decl->code.name->data); break; } case OP_BINARY_OP: { py_Name name = byte.arg & 0xFF; - c11_sbuf__write_cstr(&ss, " ("); - c11_sbuf__write_cstr(&ss, py_name2str(name)); - c11_sbuf__write_char(&ss, ')'); + pk_sprintf(&ss, " (%n)", name); break; } } diff --git a/src/public/modules.c b/src/public/modules.c index 2ed73157..e9125e76 100644 --- a/src/public/modules.c +++ b/src/public/modules.c @@ -5,12 +5,12 @@ #include "pocketpy/common/sstream.h" #include "pocketpy/interpreter/vm.h" -py_Ref py_getmodule(const char* name) { +py_Ref py_getmodule(const char* path) { pk_VM* vm = pk_current_vm; - return pk_NameDict__try_get(&vm->modules, py_name(name)); + return pk_NameDict__try_get(&vm->modules, py_name(path)); } -py_Ref py_newmodule(const char* name, const char* package) { +py_Ref py_newmodule(const char* path) { pk_ManagedHeap* heap = &pk_current_vm->heap; PyObject* obj = pk_ManagedHeap__new(heap, tp_module, -1, 0); @@ -23,33 +23,106 @@ py_Ref py_newmodule(const char* name, const char* package) { ._obj = obj, }; - py_newstr(r1, name); - py_setdict(r0, __name__, r1); - - package = package ? package : ""; - - py_newstr(r1, package); - py_setdict(r0, __package__, r1); - - // convert to fullname - if(package[0] != '\0') { - // package.name - char buf[256]; - snprintf(buf, sizeof(buf), "%s.%s", package, name); - name = buf; + int last_dot = c11_sv__rindex((c11_sv){path, strlen(path)}, '.'); + if(last_dot == -1) { + py_newstr(r1, path); + py_setdict(r0, __name__, r1); + py_newstr(r1, ""); + py_setdict(r0, __package__, r1); + } else { + const char* start = path + last_dot + 1; + py_newstr(r1, start); + py_setdict(r0, __name__, r1); + py_newstrn(r1, path, last_dot); + py_setdict(r0, __package__, r1); } - py_newstr(r1, name); + py_newstr(r1, path); py_setdict(r0, __path__, r1); // we do not allow override in order to avoid memory leak // it is because Module objects are not garbage collected - bool exists = pk_NameDict__contains(&pk_current_vm->modules, py_name(name)); - if(exists) c11__abort("module '%s' already exists", name); - pk_NameDict__set(&pk_current_vm->modules, py_name(name), *r0); + py_Name path_name = py_name(path); + bool exists = pk_NameDict__contains(&pk_current_vm->modules, path_name); + if(exists) c11__abort("module '%s' already exists", path); + pk_NameDict__set(&pk_current_vm->modules, path_name, *r0); py_shrink(2); - return py_getmodule(name); + return py_getmodule(path); +} + +bool py_import(const char* path_cstr) { + pk_VM* vm = pk_current_vm; + c11_sv path = {path_cstr, strlen(path_cstr)}; + if(path.size == 0) return ValueError("empty module name"); + + if(path.data[0] == '.') { + // try relative import + py_Ref package = py_getdict(&vm->top_frame->module, __package__); + c11_sv package_sv = py_tosv(package); + if(package_sv.size == 0) return ImportError("relative import %q with no known parent package", path); + c11_string* new_path = c11_string__new3("%v.%v", package_sv, path); + bool ok = py_import(new_path->data); + c11_string__delete(new_path); + return ok; + } + + assert(path.data[0] != '.' && path.data[path.size - 1] != '.'); + + // check existing module + py_TmpRef ext_mod = py_getmodule(path.data); + if(ext_mod) { + py_Ref is_pending = py_getdict(ext_mod, __module_is_pending__); + if(is_pending) return ImportError("circular import detected"); + py_assign(py_retval(), ext_mod); + return true; + } + + // vector path_cpnts = path.split('.'); + // // check circular import + // if(__import_context.pending.size() > 128) { ImportError("maximum recursion depth exceeded + // while importing"); } + + // try import + c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP); + + c11_string* filename = c11_string__new3("site-packages/%s.py", slashed_path->data); + int size; + unsigned char* data = py_vfsread(filename->data, &size); + if(data != NULL) goto __SUCCESS; + + c11_string__delete(filename); + filename = c11_string__new3("site-packages/%s/__init__.py", slashed_path->data); + data = py_vfsread(filename->data, &size); + if(data != NULL) goto __SUCCESS; + + c11_string__delete(filename); + filename = c11_string__new3("%s.py", slashed_path->data); + data = vm->import_file(slashed_path->data, &size); + if(data != NULL) goto __SUCCESS; + + c11_string__delete(filename); + filename = c11_string__new3("%s/__init__.py", slashed_path->data); + data = vm->import_file(slashed_path->data, &size); + if(data != NULL) goto __SUCCESS; + + c11_string__delete(filename); + c11_string__delete(slashed_path); + return ImportError("module %q not found", path); + +__SUCCESS: + py_push(py_newmodule(path_cstr)); + py_Ref mod = py_peek(-1); + py_setdict(mod, __module_is_pending__, py_True); + bool ok = py_exec((const char*)data, filename->data, EXEC_MODE, mod); + py_deldict(mod, __module_is_pending__); + py_assign(py_retval(), mod); + py_pop(); + + c11_string__delete(filename); + c11_string__delete(slashed_path); + free(data); + return ok; } ////////////////////////// @@ -224,7 +297,7 @@ static bool _py_builtins__eval(int argc, py_Ref argv) { } py_TValue pk_builtins__register() { - py_Ref builtins = py_newmodule("builtins", NULL); + py_Ref builtins = py_newmodule("builtins"); py_bindfunc(builtins, "repr", _py_builtins__repr); py_bindfunc(builtins, "exit", _py_builtins__exit); py_bindfunc(builtins, "len", _py_builtins__len); diff --git a/src/public/pkpy.c b/src/public/pkpy.c new file mode 100644 index 00000000..96dab19f --- /dev/null +++ b/src/public/pkpy.c @@ -0,0 +1,21 @@ +#include "pocketpy/pocketpy.h" + +#include "pocketpy/common/utils.h" +#include "pocketpy/objects/object.h" +#include "pocketpy/common/sstream.h" +#include "pocketpy/interpreter/vm.h" + +static bool _py_pkpy__next(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + int res = py_next(argv); + if(res == -1) return false; + if(res) return true; + py_assign(py_retval(), py_tpobject(tp_StopIteration)); + return true; +} + +void pk__add_module_pkpy() { + py_Ref mod = py_newmodule("pkpy"); + + py_bindfunc(mod, "next", _py_pkpy__next); +} \ No newline at end of file diff --git a/tests/29_iter.py b/tests/29_iter.py index c18e95fc..501900ea 100644 --- a/tests/29_iter.py +++ b/tests/29_iter.py @@ -1,4 +1,4 @@ -from __builtins import next +from pkpy import next a = [1, 2, 3] a = iter(a)