|
14 | 14 | #include "include/modules/pythonmonkey/pythonmonkey.hh" |
15 | 15 | #include "include/jsTypeFactory.hh" |
16 | 16 | #include "include/pyTypeFactory.hh" |
| 17 | +#include "include/PyProxyHandler.hh" |
17 | 18 |
|
18 | 19 | #include <jsapi.h> |
19 | 20 | #include <jsfriendapi.h> |
@@ -194,3 +195,58 @@ bool JSObjectProxyMethodDefinitions::JSObjectProxy_richcompare_helper(JSObjectPr |
194 | 195 |
|
195 | 196 | return true; |
196 | 197 | } |
| 198 | + |
| 199 | +PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_iter(JSObjectProxy *self) { |
| 200 | + JSContext *cx = GLOBAL_CX; |
| 201 | + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(self->jsObject)); |
| 202 | + |
| 203 | + // Get **enumerable** own properties |
| 204 | + JS::RootedIdVector props(cx); |
| 205 | + if (!js::GetPropertyKeys(cx, self->jsObject, JSITER_OWNONLY, &props)) { |
| 206 | + return NULL; |
| 207 | + } |
| 208 | + |
| 209 | + // Populate a Python tuple with (propertyKey, value) pairs from the JS object |
| 210 | + // Similar to `Object.entries()` |
| 211 | + size_t length = props.length(); |
| 212 | + PyObject *seq = PyTuple_New(length); |
| 213 | + for (size_t i = 0; i < length; i++) { |
| 214 | + JS::HandleId id = props[i]; |
| 215 | + PyObject *key = idToKey(cx, id); |
| 216 | + |
| 217 | + JS::RootedValue *jsVal = new JS::RootedValue(cx); |
| 218 | + JS_GetPropertyById(cx, self->jsObject, id, jsVal); |
| 219 | + PyObject *value = pyTypeFactory(cx, global, jsVal)->getPyObject(); |
| 220 | + |
| 221 | + PyTuple_SetItem(seq, i, PyTuple_Pack(2, key, value)); |
| 222 | + } |
| 223 | + |
| 224 | + // Convert to a Python iterator |
| 225 | + return PyObject_GetIter(seq); |
| 226 | +} |
| 227 | + |
| 228 | +PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_repr(JSObjectProxy *self) { |
| 229 | + // Detect cyclic objects |
| 230 | + PyObject *objPtr = PyLong_FromVoidPtr(self->jsObject.get()); |
| 231 | + // For `Py_ReprEnter`, we must get a same PyObject when visiting the same JSObject. |
| 232 | + // We cannot simply use the object returned by `PyLong_FromVoidPtr` because it won't reuse the PyLongObjects for ints not between -5 and 256. |
| 233 | + // Instead, we store this PyLongObject in a global dict, using itself as the hashable key, effectively interning the PyLongObject. |
| 234 | + PyObject *tsDict = PyThreadState_GetDict(); |
| 235 | + PyObject *cyclicKey = PyDict_SetDefault(tsDict, /*key*/ objPtr, /*value*/ objPtr); // cyclicKey = (tsDict[objPtr] ??= objPtr) |
| 236 | + int status = Py_ReprEnter(cyclicKey); |
| 237 | + if (status != 0) { // the object has already been processed |
| 238 | + return status > 0 ? PyUnicode_FromString("[Circular]") : NULL; |
| 239 | + } |
| 240 | + |
| 241 | + // Convert JSObjectProxy to a dict |
| 242 | + PyObject *dict = PyDict_New(); |
| 243 | + // Update from the iterator emitting key-value pairs |
| 244 | + // see https://docs.python.org/3/c-api/dict.html#c.PyDict_MergeFromSeq2 |
| 245 | + PyDict_MergeFromSeq2(dict, JSObjectProxy_iter(self), /*override*/ false); |
| 246 | + // Get the string representation of this dict |
| 247 | + PyObject *str = PyObject_Repr(dict); |
| 248 | + |
| 249 | + Py_ReprLeave(cyclicKey); |
| 250 | + PyDict_DelItem(tsDict, cyclicKey); |
| 251 | + return str; |
| 252 | +} |
0 commit comments