diff --git a/amalgamate.py b/amalgamate.py index a0f4b5ab..9ce6f0da 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -9,7 +9,7 @@ pipeline = [ ["config.h", "export.h", "_generated.h", "common.h", "memory.h", "vector.h", "str.h", "tuplelist.h", "namedict.h", "error.h", "any.h"], ["obj.h", "dict.h", "codeobject.h", "frame.h", "profiler.h"], ["gc.h", "vm.h", "ceval.h", "lexer.h", "expr.h", "compiler.h", "repl.h"], - ["cffi.h", "bindings.h", "iter.h", "base64.h", "csv.h", "collections.h", "array2d.h", "dataclasses.h", "random.h", "linalg.h", "easing.h", "io.h", "modules.h"], + ["cffi.h", "bindings.h", "iter.h", "base64.h", "csv.h", "array2d.h", "dataclasses.h", "random.h", "linalg.h", "easing.h", "io.h", "modules.h"], ["pocketpy.h", "pocketpy_c.h"] ] diff --git a/include/pocketpy/collections.h b/include/pocketpy/collections.h deleted file mode 100644 index 35f856f9..00000000 --- a/include/pocketpy/collections.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "bindings.h" - -namespace pkpy -{ - void add_module_collections(VM *vm); -} // namespace pkpy \ No newline at end of file diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index 89e11d07..53d31630 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -12,7 +12,6 @@ #include "vm.h" #include "random.h" #include "bindings.h" -#include "collections.h" #include "csv.h" #include "dataclasses.h" #include "array2d.h" diff --git a/python/collections.py b/python/collections.py index 15d3acf8..ded89cd1 100644 --- a/python/collections.py +++ b/python/collections.py @@ -1,5 +1,10 @@ -def Counter(iterable): - a = {} +from __builtins import _enable_instance_dict +from typing import Generic, TypeVar, Iterable + +T = TypeVar('T') + +def Counter(iterable: Iterable[T]): + a: dict[T, int] = {} for x in iterable: if x in a: a[x] += 1 @@ -7,7 +12,6 @@ def Counter(iterable): a[x] = 1 return a -from __builtins import _enable_instance_dict class defaultdict(dict): def __init__(self, default_factory, *args): @@ -25,3 +29,119 @@ class defaultdict(dict): def copy(self): return defaultdict(self.default_factory, self) + +class deque(Generic[T]): + _data: list[T] + _head: int + _tail: int + _capacity: int + + def __init__(self, iterable: Iterable[T] = None): + self._data = [None] * 8 # initial capacity + self._head = 0 + self._tail = 0 + self._capacity = len(self._data) + + if iterable is not None: + self.extend(iterable) + + def __resize_2x(self): + backup = list(self) + self._capacity *= 2 + self._head = 0 + self._tail = len(backup) + self._data.clear() + self._data.extend(backup) + self._data.extend([None] * (self._capacity - len(backup))) + + def append(self, x: T): + self._data[self._tail] = x + self._tail = (self._tail + 1) % self._capacity + if (self._tail + 1) % self._capacity == self._head: + self.__resize_2x() + + def appendleft(self, x: T): + self._head = (self._head - 1 + self._capacity) % self._capacity + self._data[self._head] = x + if (self._tail + 1) % self._capacity == self._head: + self.__resize_2x() + + def copy(self): + return deque(self) + + def count(self, x: T) -> int: + n = 0 + for item in self: + if item == x: + n += 1 + return n + + def extend(self, iterable: Iterable[T]): + for x in iterable: + self.append(x) + + def extendleft(self, iterable: Iterable[T]): + for x in iterable: + self.appendleft(x) + + def pop(self) -> T: + if self._head == self._tail: + raise IndexError("pop from an empty deque") + self._tail = (self._tail - 1 + self._capacity) % self._capacity + return self._data[self._tail] + + def popleft(self) -> T: + if self._head == self._tail: + raise IndexError("pop from an empty deque") + x = self._data[self._head] + self._head = (self._head + 1) % self._capacity + return x + + def clear(self): + i = self._head + while i != self._tail: + self._data[i] = None + i = (i + 1) % self._capacity + self._head = 0 + self._tail = 0 + + def rotate(self, n: int = 1): + if len(self) == 0: + return + if n > 0: + n = n % len(self) + for _ in range(n): + self.appendleft(self.pop()) + elif n < 0: + n = -n % len(self) + for _ in range(n): + self.append(self.popleft()) + + def __len__(self) -> int: + return (self._tail - self._head + self._capacity) % self._capacity + + def __contains__(self, x: object) -> bool: + for item in self: + if item == x: + return True + return False + + def __iter__(self): + i = self._head + while i != self._tail: + yield self._data[i] + i = (i + 1) % self._capacity + + def __eq__(self, other: object) -> bool: + if not isinstance(other, deque): + return False + if len(self) != len(other): + return False + for x, y in zip(self, other): + if x != y: + return False + return True + + def __repr__(self) -> str: + return f"deque({list(self)!r})" + diff --git a/src/_generated.cpp b/src/_generated.cpp index 2a26bbd5..8cf7cebd 100644 --- a/src/_generated.cpp +++ b/src/_generated.cpp @@ -8,7 +8,7 @@ namespace pkpy{ 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\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_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"; diff --git a/src/collections.cpp b/src/collections.cpp deleted file mode 100644 index 7fc6232f..00000000 --- a/src/collections.cpp +++ /dev/null @@ -1,548 +0,0 @@ -#include "pocketpy/collections.h" - -namespace pkpy -{ - struct PyDequeIter // Iterator for the deque type - { - PyVar ref; - bool is_reversed; - std::deque::iterator begin, end, current; - std::deque::reverse_iterator rbegin, rend, rcurrent; - PyDequeIter(PyVar ref, std::deque::iterator begin, std::deque::iterator end) - : ref(ref), begin(begin), end(end), current(begin) - { - this->is_reversed = false; - } - PyDequeIter(PyVar ref, std::deque::reverse_iterator rbegin, std::deque::reverse_iterator rend) - : ref(ref), rbegin(rbegin), rend(rend), rcurrent(rbegin) - { - this->is_reversed = true; - } - void _gc_mark(VM* vm) const { vm->obj_gc_mark(ref); } - static void _register(VM *vm, PyObject* mod, PyObject* type); - }; - void PyDequeIter::_register(VM *vm, PyObject* mod, PyObject* type) - { - vm->bind__iter__(type->as(), [](VM *vm, PyVar obj) - { return obj; }); - vm->bind__next__(type->as(), [](VM *vm, PyVar obj) -> unsigned - { - PyDequeIter& self = _CAST(PyDequeIter&, obj); - if(self.is_reversed){ - if(self.rcurrent == self.rend) return 0; - vm->s_data.push(*self.rcurrent); - ++self.rcurrent; - return 1; - } - else{ - if(self.current == self.end) return 0; - vm->s_data.push(*self.current); - ++self.current; - return 1; - } }); - } - struct PyDeque - { - PyDeque(VM *vm, PyVar iterable, PyVar maxlen); // constructor - // PyDeque members - std::deque dequeItems; - int maxlen = -1; // -1 means unbounded - bool bounded = false; // if true, maxlen is not -1 - void insertObj(bool front, bool back, int index, PyVar item); // insert at index, used purely for internal purposes: append, appendleft, insert methods - PyVar popObj(bool front, bool back, PyVar item, VM *vm); // pop at index, used purely for internal purposes: pop, popleft, remove methods - int findIndex(VM *vm, PyVar obj, int start, int stop); // find the index of the given object in the deque - // Special methods - static void _register(VM *vm, PyObject* mod, PyObject* type); // register the type - void _gc_mark(VM*) const; // needed for container types, mark all objects in the deque for gc - }; - void PyDeque::_register(VM *vm, PyObject* mod, PyObject* type) - { - vm->bind(type, "__new__(cls, iterable=None, maxlen=None)", - [](VM *vm, ArgsView args) - { - Type cls_t = PK_OBJ_GET(Type, args[0]); - PyVar iterable = args[1]; - PyVar maxlen = args[2]; - return vm->new_object(cls_t, vm, iterable, maxlen); - }); - // gets the item at the given index, if index is negative, it will be treated as index + len(deque) - // if the index is out of range, IndexError will be thrown --> required for [] operator - vm->bind__getitem__(type->as(), [](VM *vm, PyVar _0, PyVar _1) - { - PyDeque &self = _CAST(PyDeque &, _0); - i64 index = CAST(i64, _1); - index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index - return self.dequeItems[index]; - }); - // sets the item at the given index, if index is negative, it will be treated as index + len(deque) - // if the index is out of range, IndexError will be thrown --> required for [] operator - vm->bind__setitem__(type->as(), [](VM *vm, PyVar _0, PyVar _1, PyVar _2) - { - PyDeque &self = _CAST(PyDeque&, _0); - i64 index = CAST(i64, _1); - index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index - self.dequeItems[index] = _2; - }); - // erases the item at the given index, if index is negative, it will be treated as index + len(deque) - // if the index is out of range, IndexError will be thrown --> required for [] operator - vm->bind__delitem__(type->as(), [](VM *vm, PyVar _0, PyVar _1) - { - PyDeque &self = _CAST(PyDeque&, _0); - i64 index = CAST(i64, _1); - index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index - self.dequeItems.erase(self.dequeItems.begin() + index); - }); - - vm->bind__len__(type->as(), [](VM *vm, PyVar _0) - { - PyDeque &self = _CAST(PyDeque&, _0); - return (i64)self.dequeItems.size(); - }); - - vm->bind__iter__(type->as(), [](VM *vm, PyVar _0) - { - PyDeque &self = _CAST(PyDeque &, _0); - return vm->new_user_object(_0, self.dequeItems.begin(), self.dequeItems.end()); - }); - - vm->bind__repr__(type->as(), [](VM *vm, PyVar _0) -> Str - { - if(vm->_repr_recursion_set.count(_0)) return "[...]"; - const PyDeque &self = _CAST(PyDeque&, _0); - SStream ss; - ss << "deque(["; - vm->_repr_recursion_set.insert(_0); - for (auto it = self.dequeItems.begin(); it != self.dequeItems.end(); ++it) - { - ss << vm->py_repr(*it); - if (it != self.dequeItems.end() - 1) ss << ", "; - } - vm->_repr_recursion_set.erase(_0); - self.bounded ? ss << "], maxlen=" << self.maxlen << ")" : ss << "])"; - return ss.str(); - }); - - // enables comparison between two deques, == and != are supported - vm->bind__eq__(type->as(), [](VM *vm, PyVar _0, PyVar _1) - { - const PyDeque &self = _CAST(PyDeque&, _0); - if(!vm->is_user_type(_0)) return vm->NotImplemented; - const PyDeque &other = _CAST(PyDeque&, _1); - if (self.dequeItems.size() != other.dequeItems.size()) return vm->False; - for (int i = 0; i < self.dequeItems.size(); i++){ - if (vm->py_ne(self.dequeItems[i], other.dequeItems[i])) return vm->False; - } - return vm->True; - }); - - // clear the deque - vm->bind(type, "clear(self) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - self.dequeItems.clear(); - return vm->None; - }); - // extend the deque with the given iterable - vm->bind(type, "extend(self, iterable) -> None", - [](VM *vm, ArgsView args) - { - auto _lock = vm->heap.gc_scope_lock(); // locking the heap - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar it = vm->py_iter(args[1]); // strong ref - PyVar obj = vm->py_next(it); - while (obj != vm->StopIteration) - { - self.insertObj(false, true, -1, obj); - obj = vm->py_next(it); - } - return vm->None; - }); - // append at the end of the deque - vm->bind(type, "append(self, item) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar item = args[1]; - self.insertObj(false, true, -1, item); - return vm->None; - }); - // append at the beginning of the deque - vm->bind(type, "appendleft(self, item) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar item = args[1]; - self.insertObj(true, false, -1, item); - return vm->None; - }); - // pop from the end of the deque - vm->bind(type, "pop(self) -> PyObject", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - if (self.dequeItems.empty()) - { - vm->IndexError("pop from an empty deque"); - return vm->None; - } - return self.popObj(false, true, nullptr, vm); - }); - // pop from the beginning of the deque - vm->bind(type, "popleft(self) -> PyObject", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - if (self.dequeItems.empty()) - { - vm->IndexError("pop from an empty deque"); - return vm->None; - } - return self.popObj(true, false, nullptr, vm); - }); - // shallow copy of the deque - vm->bind(type, "copy(self) -> deque", - [](VM *vm, ArgsView args) - { - auto _lock = vm->heap.gc_scope_lock(); // locking the heap - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar newDequeObj = vm->new_user_object(vm, vm->None, vm->None); // create the empty deque - PyDeque &newDeque = _CAST(PyDeque &, newDequeObj); // cast it to PyDeque so we can use its methods - for (auto it = self.dequeItems.begin(); it != self.dequeItems.end(); ++it) - newDeque.insertObj(false, true, -1, *it); - return newDequeObj; - }); - // NEW: counts the number of occurrences of the given object in the deque - vm->bind(type, "count(self, obj) -> int", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar obj = args[1]; - int cnt = 0, sz = self.dequeItems.size(); - for (auto it = self.dequeItems.begin(); it != self.dequeItems.end(); ++it) - { - if (vm->py_eq((*it), obj)) - cnt++; - if (sz != self.dequeItems.size())// mutating the deque during iteration is not allowed - vm->RuntimeError("deque mutated during iteration"); - } - return VAR(cnt); - }); - // NEW: extends the deque from the left - vm->bind(type, "extendleft(self, iterable) -> None", - [](VM *vm, ArgsView args) - { - auto _lock = vm->heap.gc_scope_lock(); - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar it = vm->py_iter(args[1]); // strong ref - PyVar obj = vm->py_next(it); - while (obj != vm->StopIteration) - { - self.insertObj(true, false, -1, obj); - obj = vm->py_next(it); - } - return vm->None; - }); - // NEW: returns the index of the given object in the deque - vm->bind(type, "index(self, obj, start=None, stop=None) -> int", - [](VM *vm, ArgsView args) - { - // Return the position of x in the deque (at or after index start and before index stop). Returns the first match or raises ValueError if not found. - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar obj = args[1]; - int start = CAST_DEFAULT(int, args[2], 0); - int stop = CAST_DEFAULT(int, args[3], self.dequeItems.size()); - int index = self.findIndex(vm, obj, start, stop); - if (index < 0) vm->ValueError(vm->py_repr(obj) + " is not in deque"); - return VAR(index); - }); - // NEW: returns the index of the given object in the deque - vm->bind(type, "__contains__(self, obj) -> bool", - [](VM *vm, ArgsView args) - { - // Return the position of x in the deque (at or after index start and before index stop). Returns the first match or raises ValueError if not found. - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar obj = args[1]; - int start = 0, stop = self.dequeItems.size(); // default values - int index = self.findIndex(vm, obj, start, stop); - if (index != -1) - return VAR(true); - return VAR(false); - }); - // NEW: inserts the given object at the given index - vm->bind(type, "insert(self, index, obj) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - int index = CAST(int, args[1]); - PyVar obj = args[2]; - if (self.bounded && self.dequeItems.size() == self.maxlen) - vm->IndexError("deque already at its maximum size"); - else - self.insertObj(false, false, index, obj); // this index shouldn't be fixed using vm->normalized_index, pass as is - return vm->None; - }); - // NEW: removes the first occurrence of the given object from the deque - vm->bind(type, "remove(self, obj) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - PyVar obj = args[1]; - PyVar removed = self.popObj(false, false, obj, vm); - if (removed == nullptr) - vm->ValueError(vm->py_repr(obj) + " is not in list"); - return vm->None; - }); - // NEW: reverses the deque - vm->bind(type, "reverse(self) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - if (self.dequeItems.empty() || self.dequeItems.size() == 1) - return vm->None; // handle trivial cases - int sz = self.dequeItems.size(); - for (int i = 0; i < sz / 2; i++) - { - PyVar tmp = self.dequeItems[i]; - self.dequeItems[i] = self.dequeItems[sz - i - 1]; // swapping - self.dequeItems[sz - i - 1] = tmp; - } - return vm->None; - }); - // NEW: rotates the deque by n steps - vm->bind(type, "rotate(self, n=1) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - int n = CAST(int, args[1]); - - if (n != 0 && !self.dequeItems.empty()) // trivial case - { - PyVar tmp; // holds the object to be rotated - int direction = n > 0 ? 1 : -1; - n = abs(n); - n = n % self.dequeItems.size(); // make sure n is in range - while (n--) - { - if (direction == 1) - { - tmp = self.dequeItems.back(); - self.dequeItems.pop_back(); - self.dequeItems.push_front(tmp); - } - else - { - tmp = self.dequeItems.front(); - self.dequeItems.pop_front(); - self.dequeItems.push_back(tmp); - } - } - } - return vm->None; - }); - // NEW: getter and setter of property `maxlen` - vm->bind_property( - type, "maxlen: int", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - if (self.bounded) - return VAR(self.maxlen); - return vm->None; - }, - [](VM *vm, ArgsView args) - { - vm->AttributeError("attribute 'maxlen' of 'collections.deque' objects is not writable"); - return vm->None; - }); - // NEW: support pickle - vm->bind(type, "__getnewargs__(self) -> tuple[list, int]", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - Tuple ret(2); - List list; - for (PyVar obj : self.dequeItems) - { - list.push_back(obj); - } - ret[0] = VAR(std::move(list)); - if (self.bounded) - ret[1] = VAR(self.maxlen); - else - ret[1] = vm->None; - return VAR(ret); - }); - } - /// @brief initializes a new PyDeque object, actual initialization is done in __init__ - PyDeque::PyDeque(VM *vm, PyVar iterable, PyVar maxlen) - { - - if (maxlen != vm->None) // fix the maxlen first - { - int tmp = CAST(int, maxlen); - if (tmp < 0) - vm->ValueError("maxlen must be non-negative"); - else - { - this->maxlen = tmp; - this->bounded = true; - } - } - else - { - this->bounded = false; - this->maxlen = -1; - } - if (iterable != vm->None) - { - this->dequeItems.clear(); // clear the deque - auto _lock = vm->heap.gc_scope_lock(); // locking the heap - PyVar it = vm->py_iter(iterable); // strong ref - PyVar obj = vm->py_next(it); - while (obj != vm->StopIteration) - { - this->insertObj(false, true, -1, obj); - obj = vm->py_next(it); - } - } - } - int PyDeque::findIndex(VM *vm, PyVar obj, int start, int stop) - { - // the following code is special purpose normalization for this method, taken from CPython: _collectionsmodule.c file - if (start < 0) - { - start = this->dequeItems.size() + start; // try to fix for negative indices - if (start < 0) - start = 0; - } - if (stop < 0) - { - stop = this->dequeItems.size() + stop; // try to fix for negative indices - if (stop < 0) - stop = 0; - } - if (stop > this->dequeItems.size()) - stop = this->dequeItems.size(); - if (start > stop) - start = stop; // end of normalization - PK_ASSERT(start >= 0 && start <= this->dequeItems.size() && stop >= 0 && stop <= this->dequeItems.size() && start <= stop); // sanity check - int loopSize = std::min((int)(this->dequeItems.size()), stop); - int sz = this->dequeItems.size(); - for (int i = start; i < loopSize; i++) - { - if (vm->py_eq(this->dequeItems[i], obj)) - return i; - if (sz != this->dequeItems.size())// mutating the deque during iteration is not allowed - vm->RuntimeError("deque mutated during iteration"); - } - return -1; - } - - /// @brief pops or removes an item from the deque - /// @param front if true, pop from the front of the deque - /// @param back if true, pop from the back of the deque - /// @param item if front and back is not set, remove the first occurrence of item from the deque - /// @param vm is needed for the py_eq - /// @return PyVar if front or back is set, this is a pop operation and we return a PyVar, if front and back are not set, this is a remove operation and we return the removed item or nullptr - PyVar PyDeque::popObj(bool front, bool back, PyVar item, VM *vm) - { - // error handling - if (front && back) - throw std::runtime_error("both front and back are set"); // this should never happen - if (front || back) - { - // front or back is set, we don't care about item, this is a pop operation and we return a PyVar - if (this->dequeItems.empty()) - throw std::runtime_error("pop from an empty deque"); // shouldn't happen - PyVar obj; - if (front) - { - obj = this->dequeItems.front(); - this->dequeItems.pop_front(); - } - else - { - obj = this->dequeItems.back(); - this->dequeItems.pop_back(); - } - return obj; - } - else - { - // front and back are not set, we care about item, this is a remove operation and we return the removed item or nullptr - int sz = this->dequeItems.size(); - for (auto it = this->dequeItems.begin(); it != this->dequeItems.end(); ++it) - { - bool found = vm->py_eq((*it), item); - if (sz != this->dequeItems.size()) // mutating the deque during iteration is not allowed - vm->IndexError("deque mutated during iteration"); - if (found) - { - PyVar obj = *it; // keep a reference to the object for returning - this->dequeItems.erase(it); - return obj; - } - } - return nullptr; // not found - } - } - /// @brief inserts an item into the deque - /// @param front if true, insert at the front of the deque - /// @param back if true, insert at the back of the deque - /// @param index if front and back are not set, insert at the given index - /// @param item the item to insert - /// @return true if the item was inserted successfully, false if the deque is bounded and is already at its maximum size - void PyDeque::insertObj(bool front, bool back, int index, PyVar item) // assume index is not fixed using the vm->normalized_index - { - // error handling - if (front && back) - throw std::runtime_error("both front and back are set"); // this should never happen - if (front || back) - { - // front or back is set, we don't care about index - if (this->bounded) - { - if (this->maxlen == 0) - return; // bounded and maxlen is 0, so we can't append - else if (this->dequeItems.size() == this->maxlen) - { - if (front) - this->dequeItems.pop_back(); // remove the last item - else if (back) - this->dequeItems.pop_front(); // remove the first item - } - } - if (front) - this->dequeItems.emplace_front(item); - else if (back) - this->dequeItems.emplace_back(item); - } - else - { - // front and back are not set, we care about index - if (index < 0) - index = this->dequeItems.size() + index; // try fixing for negative indices - if (index < 0) // still negative means insert at the beginning - this->dequeItems.push_front(item); - else if (index >= this->dequeItems.size()) // still out of range means insert at the end - this->dequeItems.push_back(item); - else - this->dequeItems.insert((this->dequeItems.begin() + index), item); // insert at the given index - } - } - /// @brief marks the deque items for garbage collection - void PyDeque::_gc_mark(VM* vm) const - { - for (PyVar obj : this->dequeItems) vm->obj_gc_mark(obj); - } - /// @brief registers the PyDeque class - void add_module_collections(VM *vm) - { - PyObject* mod = vm->new_module("collections"); - vm->register_user_class(mod, "deque", VM::tp_object, true); - vm->register_user_class(mod, "_deque_iter"); - CodeObject_ code = vm->compile(kPythonLibs_collections, "collections.py", EXEC_MODE); - vm->_exec(code, mod); - } -} // namespace pkpypkpy diff --git a/src/pocketpy.cpp b/src/pocketpy.cpp index 7854c139..e1c5424c 100644 --- a/src/pocketpy.cpp +++ b/src/pocketpy.cpp @@ -1639,6 +1639,7 @@ void VM::__post_init_builtin_types(){ _lazy_modules["cmath"] = kPythonLibs_cmath; _lazy_modules["itertools"] = kPythonLibs_itertools; _lazy_modules["operator"] = kPythonLibs_operator; + _lazy_modules["collections"] = kPythonLibs_collections; try{ // initialize dummy func_decl for exec/eval @@ -1665,7 +1666,6 @@ void VM::__post_init_builtin_types(){ add_module_dataclasses(this); add_module_linalg(this); add_module_easing(this); - add_module_collections(this); add_module_array2d(this); add_module_line_profiler(this); add_module_enum(this); diff --git a/tests/70_collections.py b/tests/70_collections.py index b707553e..da6b7bab 100644 --- a/tests/70_collections.py +++ b/tests/70_collections.py @@ -30,31 +30,56 @@ assert q == deque([1, 2]) def assertEqual(a, b): - assert a == b + if a == b: + return + print(a) + print(b) + raise AssertionError + def assertNotEqual(a, b): - assert a != b + if a != b: + return + print(a) + print(b) + raise AssertionError + def printFailed(function_name, *args, **kwargs): print("X Failed Tests for {} for args: {} {}".format(str(function_name), str(args), str(kwargs))) -BIG = 100000 +BIG = 10000 def fail(): raise SyntaxError yield 1 +d = deque() +assertEqual(len(d), 0) +assertEqual(list(d), []) + +d = deque(range(6)) +assertEqual(list(d), list(range(6))) +d = deque(range(7)) # [0, 1, 2, 3, 4, 5, 6] +# print(d._data, d._head, d._tail, d._capacity) +assertEqual(list(d), list(range(7))) +d = deque(range(8)) +assertEqual(list(d), list(range(8))) +d = deque(range(9)) +assertEqual(list(d), list(range(9))) -d = deque(range(-5125, -5000)) -# d.__init__(range(200)) # not supported d = deque(range(200)) for i in range(200, 400): d.append(i) + +assertEqual(len(d), 400) +assertEqual(list(d), list(range(400))) + for i in reversed(range(-200, 0)): d.appendleft(i) -assertEqual(list(d), list(range(-200, 400))) assertEqual(len(d), 600) +assertEqual(list(d), list(range(-200, 400))) left = [d.popleft() for i in range(250)] assertEqual(left, list(range(-200, 50))) @@ -65,74 +90,6 @@ right.reverse() assertEqual(right, list(range(150, 400))) assertEqual(list(d), list(range(50, 150))) -####### TEST maxlen############### -try: - dq = deque() - dq.maxlen = -1 - printFailed("deque.maxlen", -1) - exit(1) -except AttributeError: - pass - -try: - dq = deque() - dq.maxlen = -2 - printFailed("deque.maxlen", -2) - exit(1) -except AttributeError: - pass - -it = iter(range(10)) -d = deque(it, maxlen=3) -assertEqual(list(it), []) -assertEqual(repr(d), 'deque([7, 8, 9], maxlen=3)') -assertEqual(list(d), [7, 8, 9]) -assertEqual(d, deque(range(10), 3)) -d.append(10) -assertEqual(list(d), [8, 9, 10]) -d.appendleft(7) -assertEqual(list(d), [7, 8, 9]) -d.extend([10, 11]) -assertEqual(list(d), [9, 10, 11]) -d.extendleft([8, 7]) -assertEqual(list(d), [7, 8, 9]) -d = deque(range(200), maxlen=10) -d.append(d) -assertEqual(repr(d)[-30:], ', 198, 199, [...]], maxlen=10)') -d = deque(range(10), maxlen=None) -assertEqual(repr(d), 'deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])') - -####### TEST maxlen = 0############### -it = iter(range(100)) -deque(it, maxlen=0) -assertEqual(list(it), []) - -it = iter(range(100)) -d = deque(maxlen=0) -d.extend(it) -assertEqual(list(it), []) - -it = iter(range(100)) -d = deque(maxlen=0) -d.extendleft(it) -assertEqual(list(it), []) - - -####### TEST maxlen attribute ############# - -assertEqual(deque().maxlen, None) -assertEqual(deque('abc').maxlen, None) -assertEqual(deque('abc', maxlen=4).maxlen, 4) -assertEqual(deque('abc', maxlen=2).maxlen, 2) -assertEqual(deque('abc', maxlen=0).maxlen, 0) -try: - d = deque('abc') - d.maxlen = 10 - printFailed("deque.maxlen", 10) - exit(1) -except AttributeError: - pass - ######### TEST count()################# for s in ('', 'abracadabra', 'simsalabim'*500+'abc'): s = list(s) @@ -178,29 +135,21 @@ except ArithmeticError: pass -class MutatingCompare: - def __eq__(self, other): - d.pop() - return True +# class MutatingCompare: +# def __eq__(self, other): +# d.pop() +# return True +# m = MutatingCompare() +# d = deque([1, 2, 3, m, 4, 5]) +# m.d = d -m = MutatingCompare() -d = deque([1, 2, 3, m, 4, 5]) -m.d = d - -try: - d.count(3) - printFailed("deque.count", "MutatingCompare()") - exit(1) -except RuntimeError: - pass - -d = deque([None]*16) -for i in range(len(d)): - d.rotate(-1) -d.rotate(1) -assertEqual(d.count(1), 0) -assertEqual(d.count(None), 16) +# try: +# d.count(3) +# printFailed("deque.count", "MutatingCompare()") +# exit(1) +# except RuntimeError: +# pass #### TEST comparisons == ##### @@ -234,25 +183,25 @@ for i in range(n): assertEqual((n+1) not in d, True) -class MutateCmp: - def __init__(self, deque, result): - self.deque = deque - self.result = result +# class MutateCmp: +# def __init__(self, deque, result): +# self.deque = deque +# self.result = result - def __eq__(self, other): - self.deque.clear() - return self.result +# def __eq__(self, other): +# self.deque.clear() +# return self.result -# # Test detection of mutation during iteration -d = deque(range(n)) -d[n//2] = MutateCmp(d, False) -try: - n in d - printFailed("deque.__contains__", n) - exit(1) -except RuntimeError: - pass +# # # Test detection of mutation during iteration +# d = deque(range(n)) +# d[n//2] = MutateCmp(d, False) +# try: +# n in d +# printFailed("deque.__contains__", n) +# exit(1) +# except RuntimeError: +# pass class BadCmp: @@ -262,7 +211,7 @@ class BadCmp: # # Test detection of comparison exceptions d = deque(range(n)) -d[n//2] = BadCmp() +d.append(BadCmp()) try: n in d printFailed("deque.__contains__", n) @@ -270,33 +219,6 @@ try: except RuntimeError: pass - -##### test_contains_count_stop_crashes##### - -class A: - def __eq__(self, other): - d.clear() - return NotImplemented - - -d = deque([A(), A()]) - -try: - _ = 3 in d - printFailed("deque.__contains__", 3) - exit(1) -except RuntimeError: - pass - -d = deque([A(), A()]) -try: - _ = d.count(3) - printFailed("deque.count", 3) - exit(1) -except RuntimeError: - pass - - ######## TEST extend()################ @@ -335,203 +257,6 @@ try: except SyntaxError: pass -##### TEST get_item ################ - -n = 200 -d = deque(range(n)) -l = list(range(n)) -for i in range(n): - d.popleft() - l.pop(0) - if random.random() < 0.5: - d.append(i) - l.append(i) - for j in range(1-len(l), len(l)): - assert d[j] == l[j] - -d = deque('superman') -assertEqual(d[0], 's') -assertEqual(d[-1], 'n') -d = deque() -try: - d.__getitem__(0) - printFailed("deque.__getitem__", 0) - exit(1) -except IndexError: - pass -try: - d.__getitem__(-1) - printFailed("deque.__getitem__", -1) - exit(1) -except IndexError: - pass - - -######### TEST index()############### -for n in 1, 2, 30, 40, 200: - - d = deque(range(n)) - for i in range(n): - assertEqual(d.index(i), i) - - try: - d.index(n+1) - printFailed("deque.index", n+1) - exit(1) - except ValueError: - pass - - # Test detection of mutation during iteration - d = deque(range(n)) - d[n//2] = MutateCmp(d, False) - - try: - d.index(n) - printFailed("deque.index", n) - exit(1) - except RuntimeError: - pass - - # Test detection of comparison exceptions - d = deque(range(n)) - d[n//2] = BadCmp() - - try: - d.index(n) - printFailed("deque.index", n) - exit(1) - except RuntimeError: - pass - - -# Test start and stop arguments behavior matches list.index() -# COMMENT: Current List behavior doesn't support start and stop arguments, so this test is not supported -# elements = 'ABCDEFGHI' -# nonelement = 'Z' -# d = deque(elements * 2) -# s = list(elements * 2) -# for start in range(-5 - len(s)*2, 5 + len(s) * 2): -# for stop in range(-5 - len(s)*2, 5 + len(s) * 2): -# for element in elements + 'Z': -# try: -# print(element, start, stop) -# target = s.index(element, start, stop) -# except ValueError: -# try: -# d.index(element, start, stop) -# print("X Failed Tests!") -# exit(1) -# except ValueError: -# continue -# # with assertRaises(ValueError): -# # d.index(element, start, stop) -# assertEqual(d.index(element, start, stop), target) - - -# Test large start argument -d = deque(range(0, 10000, 10)) -for step in range(100): - i = d.index(8500, 700) - assertEqual(d[i], 8500) - # Repeat test with a different internal offset - d.rotate() - -########### test_index_bug_24913############# -d = deque('A' * 3) -try: - d.index('A', 1, 0) - printFailed("deque.index", 'A', 1, 0) - exit(1) -except ValueError: - pass - -########### test_insert############# - # Test to make sure insert behaves like lists -elements = 'ABCDEFGHI' -for i in range(-5 - len(elements)*2, 5 + len(elements) * 2): - d = deque('ABCDEFGHI') - s = list('ABCDEFGHI') - d.insert(i, 'Z') - s.insert(i, 'Z') - assertEqual(list(d), s) - - -########### test_insert_bug_26194############# -data = 'ABC' -d = deque(data, maxlen=len(data)) -try: - d.insert(0, 'Z') - printFailed("deque.insert", 0, 'Z') - exit(1) -except IndexError: - pass - -elements = 'ABCDEFGHI' -for i in range(-len(elements), len(elements)): - d = deque(elements, maxlen=len(elements)+1) - d.insert(i, 'Z') - if i >= 0: - assertEqual(d[i], 'Z') - else: - assertEqual(d[i-1], 'Z') - - -######### test set_item ############# -n = 200 -d = deque(range(n)) -for i in range(n): - d[i] = 10 * i -assertEqual(list(d), [10*i for i in range(n)]) -l = list(d) -for i in range(1-n, 0, -1): - d[i] = 7*i - l[i] = 7*i -assertEqual(list(d), l) - - -########## test del_item ############# -n = 500 # O(n**2) test, don't make this too big -d = deque(range(n)) -try: - d.__delitem__(-n-1) - printFailed("deque.__delitem__", -n-1) - exit(1) -except IndexError: - pass - -try: - d.__delitem__(n) - printFailed("deque.__delitem__", n) - exit(1) -except IndexError: - pass -for i in range(n): - assertEqual(len(d), n-i) - j = random.randint(0, len(d)-1) - val = d[j] - assertEqual(val in d, True) - del d[j] - assertEqual(val in d, False) -assertEqual(len(d), 0) - - -######### test reverse()############### - -n = 500 # O(n**2) test, don't make this too big -data = [random.random() for i in range(n)] -for i in range(n): - d = deque(data[:i]) - r = d.reverse() - assertEqual(list(d), list(reversed(data[:i]))) - assertEqual(r, None) - d.reverse() - assertEqual(list(d), data[:i]) -try: - d.reverse(1) - printFailed("deque.reverse", 1) - exit(1) -except TypeError: - pass ############ test rotate############# s = tuple('abcde') @@ -550,6 +275,7 @@ assertEqual(tuple(d), s) for i in range(n*3): d = deque(s) e = deque(d) + # print(i, d, e) d.rotate(i) # check vs. rot(1) n times for j in range(i): e.rotate(1) @@ -643,54 +369,20 @@ assertEqual(list(d), []) d.clear() # clear an empty deque assertEqual(list(d), []) - -############# test remove############# -d = deque('abcdefghcij') -d.remove('c') -assertEqual(d, deque('abdefghcij')) -d.remove('c') -assertEqual(d, deque('abdefghij')) -try: - d.remove('c') - printFailed("deque.remove", "c") - exit(1) -except ValueError: - pass -assertEqual(d, deque('abdefghij')) - # Handle comparison errors d = deque(['a', 'b', BadCmp(), 'c']) e = deque(d) -try: - d.remove('c') - printFailed("deque.remove", "c") - exit(1) -except RuntimeError: - pass for x, y in zip(d, e): # verify that original order and values are retained. assertEqual(x is y, True) -# Handle evil mutator -for match in (True, False): - d = deque(['ab']) - d.extend([MutateCmp(d, match), 'c']) - try: - d.remove('c') - printFailed("deque.remove", "c") - exit(1) - except IndexError: - pass - assertEqual(d, deque()) - - ########### test repr############# d = deque(range(200)) e = eval(repr(d)) assertEqual(list(d), list(e)) -d.append(d) -assertEqual(repr(d)[-20:], '7, 198, 199, [...]])') +d.append(None) +assertEqual(repr(d)[-19:], '7, 198, 199, None])') ######### test init ############# @@ -794,24 +486,12 @@ assertEqual(list(d), list(e)) ########## test pickle ############# -for d in deque(range(200)), deque(range(200), 100): - for i in range(5 + 1): - s = pickle.dumps(d) - e = pickle.loads(s) - assertNotEqual(id(e), id(d)) - assertEqual(list(e), list(d)) - assertEqual(e.maxlen, d.maxlen) - -######## test pickle recursive ######## -# the following doesn't work because the pickle module doesn't -# for d in deque('abc'), deque('abc', 3): -# d.append(d) -# for i in range(5 + 1): -# e = pickle.loads(pickle.dumps(d)) -# assertNotEqual(id(e), id(d)) -# assertEqual(id(e[-1]), id(e)) -# assertEqual(e.maxlen, d.maxlen) - +d = deque(range(200)) +for _ in range(5 + 1): + s = pickle.dumps(d) + e = pickle.loads(s) + assertNotEqual(id(e), id(d)) + assertEqual(list(e), list(d)) ### test copy ######## @@ -828,12 +508,6 @@ assertEqual(list(d), list(e)) for s in ('abcd', range(2000)): assertEqual(list(reversed(deque(s))), list(reversed(s))) - -# probably not supported -# klass = type(reversed(deque())) -# for s in ('abcd', range(2000)): -# assertEqual(list(klass(deque(s))), list(reversed(s))) - d = deque() for i in range(100): d.append(1)