collections.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. #include "pocketpy/collections.h"
  2. namespace pkpy
  3. {
  4. struct PyDequeIter // Iterator for the deque type
  5. {
  6. PyVar ref;
  7. bool is_reversed;
  8. std::deque<PyVar >::iterator begin, end, current;
  9. std::deque<PyVar >::reverse_iterator rbegin, rend, rcurrent;
  10. PyDequeIter(PyVar ref, std::deque<PyVar >::iterator begin, std::deque<PyVar >::iterator end)
  11. : ref(ref), begin(begin), end(end), current(begin)
  12. {
  13. this->is_reversed = false;
  14. }
  15. PyDequeIter(PyVar ref, std::deque<PyVar >::reverse_iterator rbegin, std::deque<PyVar >::reverse_iterator rend)
  16. : ref(ref), rbegin(rbegin), rend(rend), rcurrent(rbegin)
  17. {
  18. this->is_reversed = true;
  19. }
  20. void _gc_mark() const { PK_OBJ_MARK(ref); }
  21. static void _register(VM *vm, PyVar mod, PyVar type);
  22. };
  23. void PyDequeIter::_register(VM *vm, PyVar mod, PyVar type)
  24. {
  25. vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar obj)
  26. { return obj; });
  27. vm->bind__next__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar obj) -> unsigned
  28. {
  29. PyDequeIter& self = _CAST(PyDequeIter&, obj);
  30. if(self.is_reversed){
  31. if(self.rcurrent == self.rend) return 0;
  32. vm->s_data.push(*self.rcurrent);
  33. ++self.rcurrent;
  34. return 1;
  35. }
  36. else{
  37. if(self.current == self.end) return 0;
  38. vm->s_data.push(*self.current);
  39. ++self.current;
  40. return 1;
  41. } });
  42. }
  43. struct PyDeque
  44. {
  45. PyDeque(VM *vm, PyVar iterable, PyVar maxlen); // constructor
  46. // PyDeque members
  47. std::deque<PyVar > dequeItems;
  48. int maxlen = -1; // -1 means unbounded
  49. bool bounded = false; // if true, maxlen is not -1
  50. void insertObj(bool front, bool back, int index, PyVar item); // insert at index, used purely for internal purposes: append, appendleft, insert methods
  51. PyVar popObj(bool front, bool back, PyVar item, VM *vm); // pop at index, used purely for internal purposes: pop, popleft, remove methods
  52. int findIndex(VM *vm, PyVar obj, int start, int stop); // find the index of the given object in the deque
  53. // Special methods
  54. static void _register(VM *vm, PyVar mod, PyVar type); // register the type
  55. void _gc_mark() const; // needed for container types, mark all objects in the deque for gc
  56. };
  57. void PyDeque::_register(VM *vm, PyVar mod, PyVar type)
  58. {
  59. vm->bind(type, "__new__(cls, iterable=None, maxlen=None)",
  60. [](VM *vm, ArgsView args)
  61. {
  62. Type cls_t = PK_OBJ_GET(Type, args[0]);
  63. PyVar iterable = args[1];
  64. PyVar maxlen = args[2];
  65. return vm->new_object<PyDeque>(cls_t, vm, iterable, maxlen);
  66. });
  67. // gets the item at the given index, if index is negative, it will be treated as index + len(deque)
  68. // if the index is out of range, IndexError will be thrown --> required for [] operator
  69. vm->bind__getitem__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar _0, PyVar _1)
  70. {
  71. PyDeque &self = _CAST(PyDeque &, _0);
  72. i64 index = CAST(i64, _1);
  73. index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index
  74. return self.dequeItems[index];
  75. });
  76. // sets the item at the given index, if index is negative, it will be treated as index + len(deque)
  77. // if the index is out of range, IndexError will be thrown --> required for [] operator
  78. vm->bind__setitem__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar _0, PyVar _1, PyVar _2)
  79. {
  80. PyDeque &self = _CAST(PyDeque&, _0);
  81. i64 index = CAST(i64, _1);
  82. index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index
  83. self.dequeItems[index] = _2;
  84. });
  85. // erases the item at the given index, if index is negative, it will be treated as index + len(deque)
  86. // if the index is out of range, IndexError will be thrown --> required for [] operator
  87. vm->bind__delitem__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar _0, PyVar _1)
  88. {
  89. PyDeque &self = _CAST(PyDeque&, _0);
  90. i64 index = CAST(i64, _1);
  91. index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index
  92. self.dequeItems.erase(self.dequeItems.begin() + index);
  93. });
  94. vm->bind__len__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar _0)
  95. {
  96. PyDeque &self = _CAST(PyDeque&, _0);
  97. return (i64)self.dequeItems.size();
  98. });
  99. vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar _0)
  100. {
  101. PyDeque &self = _CAST(PyDeque &, _0);
  102. return vm->new_user_object<PyDequeIter>(_0, self.dequeItems.begin(), self.dequeItems.end());
  103. });
  104. vm->bind__repr__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar _0) -> Str
  105. {
  106. if(vm->_repr_recursion_set.count(_0)) return "[...]";
  107. const PyDeque &self = _CAST(PyDeque&, _0);
  108. SStream ss;
  109. ss << "deque([";
  110. vm->_repr_recursion_set.insert(_0);
  111. for (auto it = self.dequeItems.begin(); it != self.dequeItems.end(); ++it)
  112. {
  113. ss << vm->py_repr(*it);
  114. if (it != self.dequeItems.end() - 1) ss << ", ";
  115. }
  116. vm->_repr_recursion_set.erase(_0);
  117. self.bounded ? ss << "], maxlen=" << self.maxlen << ")" : ss << "])";
  118. return ss.str();
  119. });
  120. // enables comparison between two deques, == and != are supported
  121. vm->bind__eq__(PK_OBJ_GET(Type, type), [](VM *vm, PyVar _0, PyVar _1)
  122. {
  123. const PyDeque &self = _CAST(PyDeque&, _0);
  124. if(!vm->is_user_type<PyDeque>(_0)) return vm->NotImplemented;
  125. const PyDeque &other = _CAST(PyDeque&, _1);
  126. if (self.dequeItems.size() != other.dequeItems.size()) return vm->False;
  127. for (int i = 0; i < self.dequeItems.size(); i++){
  128. if (vm->py_ne(self.dequeItems[i], other.dequeItems[i])) return vm->False;
  129. }
  130. return vm->True;
  131. });
  132. // clear the deque
  133. vm->bind(type, "clear(self) -> None",
  134. [](VM *vm, ArgsView args)
  135. {
  136. PyDeque &self = _CAST(PyDeque &, args[0]);
  137. self.dequeItems.clear();
  138. return vm->None;
  139. });
  140. // extend the deque with the given iterable
  141. vm->bind(type, "extend(self, iterable) -> None",
  142. [](VM *vm, ArgsView args)
  143. {
  144. auto _lock = vm->heap.gc_scope_lock(); // locking the heap
  145. PyDeque &self = _CAST(PyDeque &, args[0]);
  146. PyVar it = vm->py_iter(args[1]); // strong ref
  147. PyVar obj = vm->py_next(it);
  148. while (obj != vm->StopIteration)
  149. {
  150. self.insertObj(false, true, -1, obj);
  151. obj = vm->py_next(it);
  152. }
  153. return vm->None;
  154. });
  155. // append at the end of the deque
  156. vm->bind(type, "append(self, item) -> None",
  157. [](VM *vm, ArgsView args)
  158. {
  159. PyDeque &self = _CAST(PyDeque &, args[0]);
  160. PyVar item = args[1];
  161. self.insertObj(false, true, -1, item);
  162. return vm->None;
  163. });
  164. // append at the beginning of the deque
  165. vm->bind(type, "appendleft(self, item) -> None",
  166. [](VM *vm, ArgsView args)
  167. {
  168. PyDeque &self = _CAST(PyDeque &, args[0]);
  169. PyVar item = args[1];
  170. self.insertObj(true, false, -1, item);
  171. return vm->None;
  172. });
  173. // pop from the end of the deque
  174. vm->bind(type, "pop(self) -> PyObject",
  175. [](VM *vm, ArgsView args)
  176. {
  177. PyDeque &self = _CAST(PyDeque &, args[0]);
  178. if (self.dequeItems.empty())
  179. {
  180. vm->IndexError("pop from an empty deque");
  181. return vm->None;
  182. }
  183. return self.popObj(false, true, nullptr, vm);
  184. });
  185. // pop from the beginning of the deque
  186. vm->bind(type, "popleft(self) -> PyObject",
  187. [](VM *vm, ArgsView args)
  188. {
  189. PyDeque &self = _CAST(PyDeque &, args[0]);
  190. if (self.dequeItems.empty())
  191. {
  192. vm->IndexError("pop from an empty deque");
  193. return vm->None;
  194. }
  195. return self.popObj(true, false, nullptr, vm);
  196. });
  197. // shallow copy of the deque
  198. vm->bind(type, "copy(self) -> deque",
  199. [](VM *vm, ArgsView args)
  200. {
  201. auto _lock = vm->heap.gc_scope_lock(); // locking the heap
  202. PyDeque &self = _CAST(PyDeque &, args[0]);
  203. PyVar newDequeObj = vm->new_user_object<PyDeque>(vm, vm->None, vm->None); // create the empty deque
  204. PyDeque &newDeque = _CAST(PyDeque &, newDequeObj); // cast it to PyDeque so we can use its methods
  205. for (auto it = self.dequeItems.begin(); it != self.dequeItems.end(); ++it)
  206. newDeque.insertObj(false, true, -1, *it);
  207. return newDequeObj;
  208. });
  209. // NEW: counts the number of occurrences of the given object in the deque
  210. vm->bind(type, "count(self, obj) -> int",
  211. [](VM *vm, ArgsView args)
  212. {
  213. PyDeque &self = _CAST(PyDeque &, args[0]);
  214. PyVar obj = args[1];
  215. int cnt = 0, sz = self.dequeItems.size();
  216. for (auto it = self.dequeItems.begin(); it != self.dequeItems.end(); ++it)
  217. {
  218. if (vm->py_eq((*it), obj))
  219. cnt++;
  220. if (sz != self.dequeItems.size())// mutating the deque during iteration is not allowed
  221. vm->RuntimeError("deque mutated during iteration");
  222. }
  223. return VAR(cnt);
  224. });
  225. // NEW: extends the deque from the left
  226. vm->bind(type, "extendleft(self, iterable) -> None",
  227. [](VM *vm, ArgsView args)
  228. {
  229. auto _lock = vm->heap.gc_scope_lock();
  230. PyDeque &self = _CAST(PyDeque &, args[0]);
  231. PyVar it = vm->py_iter(args[1]); // strong ref
  232. PyVar obj = vm->py_next(it);
  233. while (obj != vm->StopIteration)
  234. {
  235. self.insertObj(true, false, -1, obj);
  236. obj = vm->py_next(it);
  237. }
  238. return vm->None;
  239. });
  240. // NEW: returns the index of the given object in the deque
  241. vm->bind(type, "index(self, obj, start=None, stop=None) -> int",
  242. [](VM *vm, ArgsView args)
  243. {
  244. // 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.
  245. PyDeque &self = _CAST(PyDeque &, args[0]);
  246. PyVar obj = args[1];
  247. int start = CAST_DEFAULT(int, args[2], 0);
  248. int stop = CAST_DEFAULT(int, args[3], self.dequeItems.size());
  249. int index = self.findIndex(vm, obj, start, stop);
  250. if (index < 0) vm->ValueError(vm->py_repr(obj) + " is not in deque");
  251. return VAR(index);
  252. });
  253. // NEW: returns the index of the given object in the deque
  254. vm->bind(type, "__contains__(self, obj) -> bool",
  255. [](VM *vm, ArgsView args)
  256. {
  257. // 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.
  258. PyDeque &self = _CAST(PyDeque &, args[0]);
  259. PyVar obj = args[1];
  260. int start = 0, stop = self.dequeItems.size(); // default values
  261. int index = self.findIndex(vm, obj, start, stop);
  262. if (index != -1)
  263. return VAR(true);
  264. return VAR(false);
  265. });
  266. // NEW: inserts the given object at the given index
  267. vm->bind(type, "insert(self, index, obj) -> None",
  268. [](VM *vm, ArgsView args)
  269. {
  270. PyDeque &self = _CAST(PyDeque &, args[0]);
  271. int index = CAST(int, args[1]);
  272. PyVar obj = args[2];
  273. if (self.bounded && self.dequeItems.size() == self.maxlen)
  274. vm->IndexError("deque already at its maximum size");
  275. else
  276. self.insertObj(false, false, index, obj); // this index shouldn't be fixed using vm->normalized_index, pass as is
  277. return vm->None;
  278. });
  279. // NEW: removes the first occurrence of the given object from the deque
  280. vm->bind(type, "remove(self, obj) -> None",
  281. [](VM *vm, ArgsView args)
  282. {
  283. PyDeque &self = _CAST(PyDeque &, args[0]);
  284. PyVar obj = args[1];
  285. PyVar removed = self.popObj(false, false, obj, vm);
  286. if (removed == nullptr)
  287. vm->ValueError(vm->py_repr(obj) + " is not in list");
  288. return vm->None;
  289. });
  290. // NEW: reverses the deque
  291. vm->bind(type, "reverse(self) -> None",
  292. [](VM *vm, ArgsView args)
  293. {
  294. PyDeque &self = _CAST(PyDeque &, args[0]);
  295. if (self.dequeItems.empty() || self.dequeItems.size() == 1)
  296. return vm->None; // handle trivial cases
  297. int sz = self.dequeItems.size();
  298. for (int i = 0; i < sz / 2; i++)
  299. {
  300. PyVar tmp = self.dequeItems[i];
  301. self.dequeItems[i] = self.dequeItems[sz - i - 1]; // swapping
  302. self.dequeItems[sz - i - 1] = tmp;
  303. }
  304. return vm->None;
  305. });
  306. // NEW: rotates the deque by n steps
  307. vm->bind(type, "rotate(self, n=1) -> None",
  308. [](VM *vm, ArgsView args)
  309. {
  310. PyDeque &self = _CAST(PyDeque &, args[0]);
  311. int n = CAST(int, args[1]);
  312. if (n != 0 && !self.dequeItems.empty()) // trivial case
  313. {
  314. PyVar tmp; // holds the object to be rotated
  315. int direction = n > 0 ? 1 : -1;
  316. n = abs(n);
  317. n = n % self.dequeItems.size(); // make sure n is in range
  318. while (n--)
  319. {
  320. if (direction == 1)
  321. {
  322. tmp = self.dequeItems.back();
  323. self.dequeItems.pop_back();
  324. self.dequeItems.push_front(tmp);
  325. }
  326. else
  327. {
  328. tmp = self.dequeItems.front();
  329. self.dequeItems.pop_front();
  330. self.dequeItems.push_back(tmp);
  331. }
  332. }
  333. }
  334. return vm->None;
  335. });
  336. // NEW: getter and setter of property `maxlen`
  337. vm->bind_property(
  338. type, "maxlen: int",
  339. [](VM *vm, ArgsView args)
  340. {
  341. PyDeque &self = _CAST(PyDeque &, args[0]);
  342. if (self.bounded)
  343. return VAR(self.maxlen);
  344. return vm->None;
  345. },
  346. [](VM *vm, ArgsView args)
  347. {
  348. vm->AttributeError("attribute 'maxlen' of 'collections.deque' objects is not writable");
  349. return vm->None;
  350. });
  351. // NEW: support pickle
  352. vm->bind(type, "__getnewargs__(self) -> tuple[list, int]",
  353. [](VM *vm, ArgsView args)
  354. {
  355. PyDeque &self = _CAST(PyDeque &, args[0]);
  356. Tuple ret(2);
  357. List list;
  358. for (PyVar obj : self.dequeItems)
  359. {
  360. list.push_back(obj);
  361. }
  362. ret[0] = VAR(std::move(list));
  363. if (self.bounded)
  364. ret[1] = VAR(self.maxlen);
  365. else
  366. ret[1] = vm->None;
  367. return VAR(ret);
  368. });
  369. }
  370. /// @brief initializes a new PyDeque object, actual initialization is done in __init__
  371. PyDeque::PyDeque(VM *vm, PyVar iterable, PyVar maxlen)
  372. {
  373. if (maxlen != vm->None) // fix the maxlen first
  374. {
  375. int tmp = CAST(int, maxlen);
  376. if (tmp < 0)
  377. vm->ValueError("maxlen must be non-negative");
  378. else
  379. {
  380. this->maxlen = tmp;
  381. this->bounded = true;
  382. }
  383. }
  384. else
  385. {
  386. this->bounded = false;
  387. this->maxlen = -1;
  388. }
  389. if (iterable != vm->None)
  390. {
  391. this->dequeItems.clear(); // clear the deque
  392. auto _lock = vm->heap.gc_scope_lock(); // locking the heap
  393. PyVar it = vm->py_iter(iterable); // strong ref
  394. PyVar obj = vm->py_next(it);
  395. while (obj != vm->StopIteration)
  396. {
  397. this->insertObj(false, true, -1, obj);
  398. obj = vm->py_next(it);
  399. }
  400. }
  401. }
  402. int PyDeque::findIndex(VM *vm, PyVar obj, int start, int stop)
  403. {
  404. // the following code is special purpose normalization for this method, taken from CPython: _collectionsmodule.c file
  405. if (start < 0)
  406. {
  407. start = this->dequeItems.size() + start; // try to fix for negative indices
  408. if (start < 0)
  409. start = 0;
  410. }
  411. if (stop < 0)
  412. {
  413. stop = this->dequeItems.size() + stop; // try to fix for negative indices
  414. if (stop < 0)
  415. stop = 0;
  416. }
  417. if (stop > this->dequeItems.size())
  418. stop = this->dequeItems.size();
  419. if (start > stop)
  420. start = stop; // end of normalization
  421. PK_ASSERT(start >= 0 && start <= this->dequeItems.size() && stop >= 0 && stop <= this->dequeItems.size() && start <= stop); // sanity check
  422. int loopSize = std::min((int)(this->dequeItems.size()), stop);
  423. int sz = this->dequeItems.size();
  424. for (int i = start; i < loopSize; i++)
  425. {
  426. if (vm->py_eq(this->dequeItems[i], obj))
  427. return i;
  428. if (sz != this->dequeItems.size())// mutating the deque during iteration is not allowed
  429. vm->RuntimeError("deque mutated during iteration");
  430. }
  431. return -1;
  432. }
  433. /// @brief pops or removes an item from the deque
  434. /// @param front if true, pop from the front of the deque
  435. /// @param back if true, pop from the back of the deque
  436. /// @param item if front and back is not set, remove the first occurrence of item from the deque
  437. /// @param vm is needed for the py_eq
  438. /// @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
  439. PyVar PyDeque::popObj(bool front, bool back, PyVar item, VM *vm)
  440. {
  441. // error handling
  442. if (front && back)
  443. throw std::runtime_error("both front and back are set"); // this should never happen
  444. if (front || back)
  445. {
  446. // front or back is set, we don't care about item, this is a pop operation and we return a PyVar
  447. if (this->dequeItems.empty())
  448. throw std::runtime_error("pop from an empty deque"); // shouldn't happen
  449. PyVar obj;
  450. if (front)
  451. {
  452. obj = this->dequeItems.front();
  453. this->dequeItems.pop_front();
  454. }
  455. else
  456. {
  457. obj = this->dequeItems.back();
  458. this->dequeItems.pop_back();
  459. }
  460. return obj;
  461. }
  462. else
  463. {
  464. // front and back are not set, we care about item, this is a remove operation and we return the removed item or nullptr
  465. int sz = this->dequeItems.size();
  466. for (auto it = this->dequeItems.begin(); it != this->dequeItems.end(); ++it)
  467. {
  468. bool found = vm->py_eq((*it), item);
  469. if (sz != this->dequeItems.size()) // mutating the deque during iteration is not allowed
  470. vm->IndexError("deque mutated during iteration");
  471. if (found)
  472. {
  473. PyVar obj = *it; // keep a reference to the object for returning
  474. this->dequeItems.erase(it);
  475. return obj;
  476. }
  477. }
  478. return nullptr; // not found
  479. }
  480. }
  481. /// @brief inserts an item into the deque
  482. /// @param front if true, insert at the front of the deque
  483. /// @param back if true, insert at the back of the deque
  484. /// @param index if front and back are not set, insert at the given index
  485. /// @param item the item to insert
  486. /// @return true if the item was inserted successfully, false if the deque is bounded and is already at its maximum size
  487. void PyDeque::insertObj(bool front, bool back, int index, PyVar item) // assume index is not fixed using the vm->normalized_index
  488. {
  489. // error handling
  490. if (front && back)
  491. throw std::runtime_error("both front and back are set"); // this should never happen
  492. if (front || back)
  493. {
  494. // front or back is set, we don't care about index
  495. if (this->bounded)
  496. {
  497. if (this->maxlen == 0)
  498. return; // bounded and maxlen is 0, so we can't append
  499. else if (this->dequeItems.size() == this->maxlen)
  500. {
  501. if (front)
  502. this->dequeItems.pop_back(); // remove the last item
  503. else if (back)
  504. this->dequeItems.pop_front(); // remove the first item
  505. }
  506. }
  507. if (front)
  508. this->dequeItems.emplace_front(item);
  509. else if (back)
  510. this->dequeItems.emplace_back(item);
  511. }
  512. else
  513. {
  514. // front and back are not set, we care about index
  515. if (index < 0)
  516. index = this->dequeItems.size() + index; // try fixing for negative indices
  517. if (index < 0) // still negative means insert at the beginning
  518. this->dequeItems.push_front(item);
  519. else if (index >= this->dequeItems.size()) // still out of range means insert at the end
  520. this->dequeItems.push_back(item);
  521. else
  522. this->dequeItems.insert((this->dequeItems.begin() + index), item); // insert at the given index
  523. }
  524. }
  525. /// @brief marks the deque items for garbage collection
  526. void PyDeque::_gc_mark() const
  527. {
  528. for (PyVar obj : this->dequeItems)
  529. PK_OBJ_MARK(obj);
  530. }
  531. /// @brief registers the PyDeque class
  532. void add_module_collections(VM *vm)
  533. {
  534. PyVar mod = vm->new_module("collections");
  535. vm->register_user_class<PyDeque>(mod, "deque", VM::tp_object, true);
  536. vm->register_user_class<PyDequeIter>(mod, "_deque_iter");
  537. CodeObject_ code = vm->compile(kPythonLibs_collections, "collections.py", EXEC_MODE);
  538. vm->_exec(code, mod);
  539. }
  540. } // namespace pkpypkpy