diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index ebb79e8a..f3c94175 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -144,6 +144,9 @@ public: PyObject* _last_exception; // last exception PyObject* _curr_class; // current class being defined + // this is for repr() recursion detection (no need to mark) + std::set _repr_recursion_set; + // cached code objects for FSTRING_EVAL std::map _cached_codes; diff --git a/src/collections.cpp b/src/collections.cpp index 78289778..bb2a99cd 100644 --- a/src/collections.cpp +++ b/src/collections.cpp @@ -57,7 +57,6 @@ namespace pkpy void insertObj(bool front, bool back, int index, PyObject *item); // insert at index, used purely for internal purposes: append, appendleft, insert methods PyObject *popObj(bool front, bool back, PyObject *item, VM *vm); // pop at index, used purely for internal purposes: pop, popleft, remove methods int findIndex(VM *vm, PyObject *obj, int start, int stop); // find the index of the given object in the deque - std::string getRepr(VM *vm, PyObject* thisObj); // get the string representation of the deque // Special methods static void _register(VM *vm, PyObject *mod, PyObject *type); // register the type void _gc_mark() const; // needed for container types, mark all objects in the deque for gc @@ -74,80 +73,76 @@ namespace pkpy }); // 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(type, "__getitem__(self, index) -> PyObject", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - int index = CAST(int, args[1]); - index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index - return self.dequeItems.at(index); - }); + vm->bind__getitem__(PK_OBJ_GET(Type, type), [](VM *vm, PyObject* _0, PyObject* _1) + { + PyDeque &self = _CAST(PyDeque &, _0); + int index = CAST(int, _1); + index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index + return self.dequeItems.at(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(type, "__setitem__(self, index, newValue) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - int index = CAST(int, args[1]); - PyObject *newValue = args[2]; - index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index - self.dequeItems.at(index) = newValue; - return vm->None; - }); + vm->bind__setitem__(PK_OBJ_GET(Type, type), [](VM *vm, PyObject* _0, PyObject* _1, PyObject* _2) + { + PyDeque &self = _CAST(PyDeque&, _0); + int index = CAST(int, _1); + index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index + self.dequeItems.at(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(type, "__delitem__(self, index) -> None", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - int index = CAST(int, args[1]); - index = vm->normalized_index(index, self.dequeItems.size()); // error is handled by the vm->normalized_index - self.dequeItems.erase(self.dequeItems.begin() + index); - return vm->None; - }); - // returns the length of the deque - vm->bind(type, "__len__(self) -> int", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - return VAR(self.dequeItems.size()); - }); - // returns an iterator for the deque - vm->bind(type, "__iter__(self) -> deque_iterator", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - return vm->heap.gcnew( - PyDequeIter::_type(vm), args[0], - self.dequeItems.begin(), self.dequeItems.end()); - }); - // returns a string representation of the deque - vm->bind(type, "__repr__(self) -> str", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - return VAR(self.getRepr(vm, args[0])); - }); - // returns a string representation of the deque - vm->bind(type, "__str__(self) -> str", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - return VAR(self.getRepr(vm, args[0])); - }); + vm->bind__delitem__(PK_OBJ_GET(Type, type), [](VM *vm, PyObject* _0, PyObject* _1) + { + PyDeque &self = _CAST(PyDeque&, _0); + int index = CAST(int, _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__(PK_OBJ_GET(Type, type), [](VM *vm, PyObject* _0) + { + PyDeque &self = _CAST(PyDeque&, _0); + return (i64)self.dequeItems.size(); + }); + + vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM *vm, PyObject* _0) + { + PyDeque &self = _CAST(PyDeque &, _0); + return vm->heap.gcnew( + PyDequeIter::_type(vm), _0, + self.dequeItems.begin(), self.dequeItems.end()); + }); + + vm->bind__repr__(PK_OBJ_GET(Type, type), [](VM *vm, PyObject* _0) + { + if(vm->_repr_recursion_set.count(_0)) return VAR("[...]"); + 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 << CAST(Str&, 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 VAR(ss.str()); + }); + // enables comparison between two deques, == and != are supported - vm->bind(type, "__eq__(self, other) -> bool", - [](VM *vm, ArgsView args) - { - PyDeque &self = _CAST(PyDeque &, args[0]); - PyDeque &other = _CAST(PyDeque &, args[1]); - if (self.dequeItems.size() != other.dequeItems.size()) // trivial case - return VAR(false); - for (int i = 0; i < self.dequeItems.size(); i++) - if (!vm->py_eq(self.dequeItems[i], other.dequeItems[i])) - return VAR(false); - return VAR(true); - }); + vm->bind__eq__(PK_OBJ_GET(Type, type), [](VM *vm, PyObject* _0, PyObject* _1) + { + const PyDeque &self = _CAST(PyDeque&, _0); + if(!is_non_tagged_type(_0, PyDeque::_type(vm))) 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) @@ -426,22 +421,6 @@ namespace pkpy } } } - std::string PyDeque::getRepr(VM *vm, PyObject *thisObj) - { - std::stringstream ss; - ss << "deque(["; - for (auto it = this->dequeItems.begin(); it != this->dequeItems.end(); ++it) - { - if (*it == thisObj) - ss << "[...]"; - else - ss << CAST(Str &, vm->py_repr(*it)); - if (it != this->dequeItems.end() - 1) - ss << ", "; - } - this->bounded ? ss << "], maxlen=" << this->maxlen << ")" : ss << "])"; - return ss.str(); - } int PyDeque::findIndex(VM *vm, PyObject *obj, int start, int stop) { // the following code is special purpose normalization for this method, taken from CPython: _collectionsmodule.c file diff --git a/src/pocketpy.cpp b/src/pocketpy.cpp index 044de82f..e245fe3a 100644 --- a/src/pocketpy.cpp +++ b/src/pocketpy.cpp @@ -659,17 +659,16 @@ void init_builtins(VM* _vm) { }); _vm->bind__repr__(VM::tp_list, [](VM* vm, PyObject* _0){ + if(vm->_repr_recursion_set.count(_0)) return VAR("[...]"); List& iterable = _CAST(List&, _0); SStream ss; ss << '['; + vm->_repr_recursion_set.insert(_0); for(int i=0; ipy_repr(iterable[i])); - } + ss << CAST(Str&, vm->py_repr(iterable[i])); if(i != iterable.size()-1) ss << ", "; } + vm->_repr_recursion_set.erase(_0); ss << ']'; return VAR(ss.str()); }); @@ -1080,16 +1079,19 @@ void init_builtins(VM* _vm) { }); _vm->bind__repr__(VM::tp_mappingproxy, [](VM* vm, PyObject* _0) { + if(vm->_repr_recursion_set.count(_0)) return VAR("{...}"); MappingProxy& self = _CAST(MappingProxy&, _0); SStream ss; ss << "mappingproxy({"; bool first = true; + vm->_repr_recursion_set.insert(_0); for(auto& item : self.attr().items()){ if(!first) ss << ", "; first = false; ss << item.first.escape() << ": "; ss << CAST(Str, vm->py_repr(item.second)); } + vm->_repr_recursion_set.erase(_0); ss << "})"; return VAR(ss.str()); }); @@ -1227,20 +1229,18 @@ void init_builtins(VM* _vm) { }); _vm->bind__repr__(VM::tp_dict, [](VM* vm, PyObject* _0) { + if(vm->_repr_recursion_set.count(_0)) return VAR("{...}"); Dict& self = _CAST(Dict&, _0); SStream ss; ss << "{"; bool first = true; + vm->_repr_recursion_set.insert(_0); self.apply([&](PyObject* k, PyObject* v){ if(!first) ss << ", "; first = false; - ss << CAST(Str&, vm->py_repr(k)) << ": "; - if(v == _0){ - ss << "{...}"; - }else{ - ss << CAST(Str&, vm->py_repr(v)); - } + ss << CAST(Str&, vm->py_repr(k)) << ": " << CAST(Str&, vm->py_repr(v)); }); + vm->_repr_recursion_set.erase(_0); ss << "}"; return VAR(ss.str()); }); diff --git a/tests/05_list.py b/tests/05_list.py index 19c74536..91ec191a 100644 --- a/tests/05_list.py +++ b/tests/05_list.py @@ -97,7 +97,7 @@ assert b[ 0] == 1 a = [] -a.append(1) -a.append(a) +a.append(0) +a.append([1, 2, a]) -assert repr(a) == '[1, [...]]' +assert repr(a) == "[0, [1, 2, [...]]]"