diff --git a/LICENSE b/LICENSE index e488de74..c4867839 100644 --- a/LICENSE +++ b/LICENSE @@ -72,4 +72,51 @@ products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License -Agreement. \ No newline at end of file +Agreement. + +------------------------------------------------------------------------------- + +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" \ No newline at end of file diff --git a/README.md b/README.md index b9f00f07..1fa5dbfd 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ js_eval("console.log")('hello, world') - Python host environment supplies basic subsets of NodeJS's fs, path, process, etc, modules; as-needed by dcp-client (other project?) - [done] Python TypedArrays coerce to JS TypeArrays - [done] JS TypedArrays coerce to Python TypeArrays -- [done] Python List coerce to JS Arrays +- [done] JS Arrays coerce to Python Lists, providing all List methods implemented on Arrays +- [done] Python List coerce to JS Arrays, providing all Array methods implemented on Lists ## Build Instructions diff --git a/include/JSArrayProxy.hh b/include/JSArrayProxy.hh index d358ccc5..0e04b760 100644 --- a/include/JSArrayProxy.hh +++ b/include/JSArrayProxy.hh @@ -24,7 +24,7 @@ */ typedef struct { PyListObject list; - JS::RootedObject jsObject; + JS::RootedObject jsArray; } JSArrayProxy; /** diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index 21188e26..8bb99af8 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -1,11 +1,11 @@ /** - * @file PyProxy.hh - * @author Caleb Aikens (caleb@distributive.network) - * @brief Struct for creating JS proxy objects. Used by DictType for object coercion + * @file PyProxyHandler.hh + * @author Caleb Aikens (caleb@distributive.network) and Philippe Laporte (philippe@distributive.network) + * @brief Structs for creating JS proxy objects. Used by DictType for object coercion and by ListType for List coercion * @version 0.1 * @date 2023-04-20 * - * Copyright (c) 2023 Distributive Corp. + * Copyright (c) 2023-2024 Distributive Corp. * */ @@ -30,6 +30,14 @@ public: bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible) const override final; }; +enum ProxySlots {PyObjectSlot}; + +typedef struct { + const char *name; /* The name of the method */ + JSNative call; /* The C function that implements it */ + uint16_t nargs; /* The argument count for the method */ +} JSMethodDef; + /** * @brief This struct is the ProxyHandler for JS Proxy Objects pythonmonkey creates to handle coercion from python dicts to JS Objects * @@ -157,17 +165,27 @@ public: JS::HandleId id, JS::Handle desc, JS::ObjectOpResult &result) const override; + + bool getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const override; }; /** * @brief This struct is the ProxyHandler for JS Proxy Objects pythonmonkey creates - * to handle coercion from python lists to JS Array-like objects + * to handle coercion from python lists to JS Array objects */ struct PyListProxyHandler : public PyBaseProxyHandler { public: PyListProxyHandler(PyObject *pyObj) : PyBaseProxyHandler(pyObj, &family) {}; static const char family; + /** + * @brief Handles python object reference count when JS Proxy object is finalized + * + * @param gcx pointer to JS::GCContext + * @param proxy the proxy object being finalized + */ + void finalize(JS::GCContext *gcx, JSObject *proxy) const override; + bool getOwnPropertyDescriptor( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle> desc @@ -180,6 +198,8 @@ public: bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const override; bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override; + bool isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const override; + bool getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const override; }; /** diff --git a/src/JSArrayProxy.cc b/src/JSArrayProxy.cc index 17cbac02..0d2c3c84 100644 --- a/src/JSArrayProxy.cc +++ b/src/JSArrayProxy.cc @@ -25,7 +25,7 @@ void JSArrayProxyMethodDefinitions::JSArrayProxy_dealloc(JSArrayProxy *self) { - self->jsObject.set(nullptr); + self->jsArray.set(nullptr); Py_TYPE(self)->tp_free((PyObject *)self); } @@ -43,7 +43,7 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_init(JSArrayProxy *self, PyObjec Py_ssize_t JSArrayProxyMethodDefinitions::JSArrayProxy_length(JSArrayProxy *self) { uint32_t length; - JS::GetArrayLength(GLOBAL_CX, self->jsObject, &length); + JS::GetArrayLength(GLOBAL_CX, self->jsArray, &length); return (Py_ssize_t)length; } @@ -56,9 +56,9 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get(JSArrayProxy *self, Py } JS::RootedValue *value = new JS::RootedValue(GLOBAL_CX); - JS_GetPropertyById(GLOBAL_CX, self->jsObject, id, value); + JS_GetPropertyById(GLOBAL_CX, self->jsArray, id, value); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); // look through the methods for dispatch and return key if no method found for (size_t index = 0;; index++) { const char *methodName = JSArrayProxyType.tp_methods[index].ml_name; @@ -83,11 +83,11 @@ static PyObject *list_slice(JSArrayProxy *self, Py_ssize_t ilow, Py_ssize_t ihig jArgs[0].setInt32(ilow); jArgs[1].setInt32(ihigh); JS::RootedValue *jReturnedArray = new JS::RootedValue(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "slice", jArgs, jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "slice", jArgs, jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jReturnedArray)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jReturnedArray)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get_subscript(JSArrayProxy *self, PyObject *key) @@ -113,9 +113,9 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get_subscript(JSArrayProxy JS_IndexToId(GLOBAL_CX, index, &id); JS::RootedValue *value = new JS::RootedValue(GLOBAL_CX); - JS_GetPropertyById(GLOBAL_CX, self->jsObject, id, value); + JS_GetPropertyById(GLOBAL_CX, self->jsArray, id, value); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), value)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), value)->getPyObject(); } else if (PySlice_Check(key)) { Py_ssize_t start, stop, step, slicelength, index; @@ -137,14 +137,14 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get_subscript(JSArrayProxy JS::RootedValue elementVal(GLOBAL_CX); for (size_t cur = start, index = 0; index < slicelength; cur += (size_t)step, index++) { - JS_GetElement(GLOBAL_CX, self->jsObject, cur, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, cur, &elementVal); JS_SetElement(GLOBAL_CX, jCombinedArray, index, elementVal); } JS::RootedValue *jCombinedArrayValue = new JS::RootedValue(GLOBAL_CX); jCombinedArrayValue->setObjectOrNull(jCombinedArray); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jCombinedArrayValue)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jCombinedArrayValue)->getPyObject(); } } else { @@ -226,30 +226,30 @@ static int list_ass_slice(JSArrayProxy *self, Py_ssize_t ilow, Py_ssize_t ihigh, if (d < 0) { /* Delete -d items */ JS::RootedValue elementVal(GLOBAL_CX); for (size_t index = ihigh, count = 0; count < selfLength - ihigh; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index+d, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index+d, elementVal); } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, selfLength + d); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, selfLength + d); } else if (d > 0) { /* Insert d items */ k = selfLength; - JS::SetArrayLength(GLOBAL_CX, self->jsObject, k + d); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, k + d); selfLength = k + d; JS::RootedValue elementVal(GLOBAL_CX); for (size_t index = ihigh, count = 0; count < k - ihigh; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index+d, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index+d, elementVal); } } JS::RootedValue elementVal(GLOBAL_CX); for (k = 0; k < n; k++, ilow++) { elementVal.set(jsTypeFactory(GLOBAL_CX, vitem[k])); - JS_SetElement(GLOBAL_CX, self->jsObject, ilow, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, ilow, elementVal); } result = 0; @@ -282,10 +282,10 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P if (value) { // we are setting a value JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, value)); - JS_SetPropertyById(GLOBAL_CX, self->jsObject, id, jValue); + JS_SetPropertyById(GLOBAL_CX, self->jsArray, id, jValue); } else { // we are deleting a value JS::ObjectOpResult ignoredResult; - JS_DeletePropertyById(GLOBAL_CX, self->jsObject, id, ignoredResult); + JS_DeletePropertyById(GLOBAL_CX, self->jsArray, id, ignoredResult); } return 0; @@ -341,8 +341,8 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P } for (size_t index = cur, count = 0; count < lim; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index + 1, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index - i, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index + 1, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index - i, elementVal); } } @@ -350,12 +350,12 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P if (cur < (size_t)selfSize) { for (size_t index = cur, count = 0; count < selfSize - cur; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index - slicelength, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index - slicelength, elementVal); } } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, selfSize - slicelength); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, selfSize - slicelength); return 0; } @@ -395,7 +395,7 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P JS::RootedValue elementVal(GLOBAL_CX); for (cur = start, i = 0; i < slicelength; cur += (size_t)step, i++) { elementVal.set(jsTypeFactory(GLOBAL_CX, seqitems[i])); - JS_SetElement(GLOBAL_CX, self->jsObject, cur, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, cur, elementVal); } Py_DECREF(seq); @@ -444,18 +444,18 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_richcompare(JSArrayProxy * } JS::RootedValue *elementVal; - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); Py_ssize_t index; /* Search for the first index where items are different */ for (index = 0; index < selfLength && index < otherLength; index++) { elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *leftItem = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); PyObject *rightItem; if (PyObject_TypeCheck(other, &JSArrayProxyType)) { - JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)other)->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)other)->jsArray, index, elementVal); rightItem = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); } else { rightItem = ((PyListObject *)other)->ob_item[index]; @@ -492,7 +492,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_richcompare(JSArrayProxy * } elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); /* Compare the final item again using the proper operator */ return PyObject_RichCompare(pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(), ((PyListObject *)other)->ob_item[index], op); } @@ -516,7 +516,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repr(JSArrayProxy *self) { /* "[" + "1" + ", 2" * (len - 1) + "]" */ writer.min_length = 1 + 1 + (2 + 1) * (selfLength - 1) + 1; - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); if (_PyUnicodeWriter_WriteChar(&writer, '[') < 0) { goto error; @@ -531,10 +531,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repr(JSArrayProxy *self) { } JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *s; - if (&elementVal->toObject() == self->jsObject.get()) { + if (&elementVal->toObject() == self->jsArray.get()) { s = PyObject_Repr((PyObject *)self); } else { s = PyObject_Repr(pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject()); @@ -569,10 +569,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_iter(JSArrayProxy *self) { PyObject *seq = PyList_New(selfLength); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (size_t index = 0; index < selfLength; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyList_SET_ITEM(seq, index, pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject()); } @@ -612,13 +612,13 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_concat(JSArrayProxy *self, JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < sizeSelf; inputIdx++) { - JS_GetElement(GLOBAL_CX, self->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, inputIdx, &elementVal); JS_SetElement(GLOBAL_CX, jCombinedArray, inputIdx, elementVal); } if (PyObject_TypeCheck(value, &JSArrayProxyType)) { for (Py_ssize_t inputIdx = 0; inputIdx < sizeValue; inputIdx++) { - JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)value)->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)value)->jsArray, inputIdx, &elementVal); JS_SetElement(GLOBAL_CX, jCombinedArray, sizeSelf + inputIdx, elementVal); } } else { @@ -632,7 +632,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_concat(JSArrayProxy *self, JS::RootedValue *jCombinedArrayValue = new JS::RootedValue(GLOBAL_CX); jCombinedArrayValue->setObjectOrNull(jCombinedArray); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jCombinedArrayValue)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jCombinedArrayValue)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repeat(JSArrayProxy *self, Py_ssize_t n) { @@ -650,7 +650,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repeat(JSArrayProxy *self, // one might think of using copyWithin but in SpiderMonkey it's implemented in JS! JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < input_size; inputIdx++) { - JS_GetElement(GLOBAL_CX, self->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, inputIdx, &elementVal); for (Py_ssize_t repeatIdx = 0; repeatIdx < n; repeatIdx++) { JS_SetElement(GLOBAL_CX, jCombinedArray, repeatIdx * input_size + inputIdx, elementVal); } @@ -659,7 +659,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repeat(JSArrayProxy *self, JS::RootedValue jCombinedArrayValue(GLOBAL_CX); jCombinedArrayValue.setObjectOrNull(jCombinedArray); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), &jCombinedArrayValue)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), &jCombinedArrayValue)->getPyObject(); } int JSArrayProxyMethodDefinitions::JSArrayProxy_contains(JSArrayProxy *self, PyObject *element) { @@ -667,10 +667,10 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_contains(JSArrayProxy *self, PyO int cmp; Py_ssize_t numElements = JSArrayProxy_length(self); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (index = 0, cmp = 0; cmp == 0 && index < numElements; ++index) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *item = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(item); cmp = PyObject_RichCompareBool(item, element, Py_EQ); @@ -684,7 +684,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_concat(JSArrayProx Py_ssize_t valueLength = Py_SIZE(value); // allocate extra spacePy_SIZE - JS::SetArrayLength(GLOBAL_CX, self->jsObject, selfLength + valueLength); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, selfLength + valueLength); JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, value)); JS::RootedObject jRootedValue = JS::RootedObject(GLOBAL_CX, jValue.toObjectOrNull()); @@ -692,7 +692,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_concat(JSArrayProx JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < valueLength; inputIdx++) { JS_GetElement(GLOBAL_CX, jRootedValue, inputIdx, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, selfLength + inputIdx, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, selfLength + inputIdx, elementVal); } Py_INCREF(self); @@ -716,15 +716,15 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_repeat(JSArrayProx return PyErr_NoMemory(); } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, input_size * n); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, input_size * n); // repeat within self // one might think of using copyWithin but in SpiderMonkey it's implemented in JS! JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < input_size; inputIdx++) { - JS_GetElement(GLOBAL_CX, self->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, inputIdx, &elementVal); for (Py_ssize_t repeatIdx = 0; repeatIdx < n; repeatIdx++) { - JS_SetElement(GLOBAL_CX, self->jsObject, repeatIdx * input_size + inputIdx, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, repeatIdx * input_size + inputIdx, elementVal); } } @@ -733,7 +733,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_repeat(JSArrayProx } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_clear(JSArrayProxy *self) { - JS::SetArrayLength(GLOBAL_CX, self->jsObject, 0); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, 0); Py_RETURN_NONE; } @@ -743,10 +743,10 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_clear_slot(JSArrayProxy *self) { } int JSArrayProxyMethodDefinitions::JSArrayProxy_traverse(JSArrayProxy *self, visitproc visit, void *arg) { - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t i = JSArrayProxy_length(self); --i >= 0; ) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, i, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, i, elementVal); Py_VISIT(pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject()); } return 0; @@ -757,19 +757,19 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_copy(JSArrayProxy *self) { jArgs[0].setInt32(0); jArgs[1].setInt32(JSArrayProxy_length(self)); JS::RootedValue *jReturnedArray = new JS::RootedValue(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "slice", jArgs, jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "slice", jArgs, jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jReturnedArray)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jReturnedArray)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_append(JSArrayProxy *self, PyObject *value) { Py_ssize_t len = JSArrayProxy_length(self); - JS::SetArrayLength(GLOBAL_CX, self->jsObject, len + 1); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, len + 1); JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, value)); - JS_SetElement(GLOBAL_CX, self->jsObject, len, jValue); + JS_SetElement(GLOBAL_CX, self->jsArray, len, jValue); Py_RETURN_NONE; } @@ -816,7 +816,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_insert(JSArrayProxy *self, jArgs[2].set(jsTypeFactory(GLOBAL_CX, value)); JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "splice", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "splice", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -839,14 +839,14 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_extend(JSArrayProxy *self, Py_ssize_t m = JSArrayProxy_length(self); - JS::SetArrayLength(GLOBAL_CX, self->jsObject, m + n); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, m + n); // populate the end of self with iterable's items. PyObject **src = PySequence_Fast_ITEMS(iterable); for (Py_ssize_t i = 0; i < n; i++) { PyObject *o = src[i]; JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, o)); - JS_SetElement(GLOBAL_CX, self->jsObject, m + i, jValue); + JS_SetElement(GLOBAL_CX, self->jsArray, m + i, jValue); } Py_DECREF(iterable); @@ -875,9 +875,9 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_extend(JSArrayProxy *self, break; } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, len + 1); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, len + 1); JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, item)); - JS_SetElement(GLOBAL_CX, self->jsObject, len, jValue); + JS_SetElement(GLOBAL_CX, self->jsArray, len, jValue); len++; } @@ -928,7 +928,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_pop(JSArrayProxy *self, Py jArgs[1].setInt32(1); JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "splice", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "splice", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -938,16 +938,16 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_pop(JSArrayProxy *self, Py JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); JS_GetElement(GLOBAL_CX, rootedReturnedArray, 0, elementVal); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), elementVal)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), elementVal)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_remove(JSArrayProxy *self, PyObject *value) { Py_ssize_t selfSize = JSArrayProxy_length(self); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t index = 0; index < selfSize; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *obj = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); @@ -957,7 +957,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_remove(JSArrayProxy *self, jArgs[0].setInt32(index); jArgs[1].setInt32(1); JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "splice", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "splice", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1010,10 +1010,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_index(JSArrayProxy *self, } } - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t index = start; index < stop && index < selfSize; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *obj = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); @@ -1034,10 +1034,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_count(JSArrayProxy *self, Py_ssize_t count = 0; Py_ssize_t length = JSArrayProxy_length(self); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t index = 0; index < length; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *obj = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); @@ -1055,7 +1055,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_count(JSArrayProxy *self, PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_reverse(JSArrayProxy *self) { if (JSArrayProxy_length(self) > 1) { JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "reverse", JS::HandleValueArray::empty(), &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "reverse", JS::HandleValueArray::empty(), &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1085,17 +1085,17 @@ static bool sort_compare_key_func(JSContext *cx, unsigned argc, JS::Value *vp) { bool reverse = reverseValue.toBoolean(); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(&args.callee())); + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(&args.callee())); - JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX, args[0]); - PyObject *args_0 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0]); + PyObject *args_0 = pyTypeFactory(cx, global, elementVal)->getPyObject(); PyObject *args_0_result = PyObject_CallFunction(keyfunc, "O", args_0); if (!args_0_result) { return false; } - elementVal = new JS::RootedValue(GLOBAL_CX, args[1]); - PyObject *args_1 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + elementVal = new JS::RootedValue(cx, args[1]); + PyObject *args_1 = pyTypeFactory(cx, global, elementVal)->getPyObject(); PyObject *args_1_result = PyObject_CallFunction(keyfunc, "O", args_1); if (!args_1_result) { return false; @@ -1135,13 +1135,13 @@ static bool sort_compare_default(JSContext *cx, unsigned argc, JS::Value *vp) { } bool reverse = reverseValue.toBoolean(); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(&args.callee())); + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(&args.callee())); - JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX, args[0]); - PyObject *args_0 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0]); + PyObject *args_0 = pyTypeFactory(cx, global, elementVal)->getPyObject(); - elementVal = new JS::RootedValue(GLOBAL_CX, args[1]); - PyObject *args_1 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + elementVal = new JS::RootedValue(cx, args[1]); + PyObject *args_1 = pyTypeFactory(cx, global, elementVal)->getPyObject(); int cmp = PyObject_RichCompareBool(args_0, args_1, Py_LT); if (cmp > 0) { @@ -1245,18 +1245,29 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].setObject(*funObj); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); } return NULL; } + + // cleanup + if (!JS_DeleteProperty(GLOBAL_CX, funObj, "_key_func_param")) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); + return NULL; + } + + if (!JS_DeleteProperty(GLOBAL_CX, funObj, "_reverse_param")) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); + return NULL; + } } else { // two-arg js-style JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].set(jsTypeFactory(GLOBAL_CX, keyfunc)); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1274,7 +1285,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P // we got a JS compare function, use it as-is JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].set(jsTypeFactory(GLOBAL_CX, keyfunc)); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1302,7 +1313,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].setObject(*funObj); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); } @@ -1329,7 +1340,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].setObject(*funObj); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } diff --git a/src/JSObjectProxy.cc b/src/JSObjectProxy.cc index 16d4338d..7e7e7bec 100644 --- a/src/JSObjectProxy.cc +++ b/src/JSObjectProxy.cc @@ -5,7 +5,7 @@ * @version 0.1 * @date 2023-06-26 * - * Copyright (c) 2023 Distributive Corp. + * Copyright (c) 2023-2024 Distributive Corp. * */ @@ -44,6 +44,7 @@ void JSObjectProxyMethodDefinitions::JSObjectProxy_dealloc(JSObjectProxy *self) { // TODO (Caleb Aikens): intentional override of PyDict_Type's tp_dealloc. Probably results in leaking dict memory self->jsObject.set(nullptr); + PyObject_GC_UnTrack(self); Py_TYPE(self)->tp_free((PyObject *)self); return; } @@ -301,7 +302,10 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_or(JSObjectProxy *self, // call Object.assign JS::RootedValue Object(GLOBAL_CX); - JS_GetProperty(GLOBAL_CX, *global, "Object", &Object); + if (!JS_GetProperty(GLOBAL_CX, *global, "Object", &Object)) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); + return NULL; + } JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull()); JS::RootedValue *ret = new JS::RootedValue(GLOBAL_CX); @@ -328,11 +332,14 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_ior(JSObjectProxy *self, // call Object.assign JS::RootedValue Object(GLOBAL_CX); - JS_GetProperty(GLOBAL_CX, *global, "Object", &Object); + if (!JS_GetProperty(GLOBAL_CX, *global, "Object", &Object)) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); + return NULL; + } JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull()); - JS::RootedValue *ret = new JS::RootedValue(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, rootedObject, "assign", args, ret)) { + JS::RootedValue ret(GLOBAL_CX); + if (!JS_CallFunctionName(GLOBAL_CX, rootedObject, "assign", args, &ret)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); return NULL; } @@ -456,7 +463,10 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_copy_method(JSObjectProx // call Object.assign JS::RootedValue Object(GLOBAL_CX); - JS_GetProperty(GLOBAL_CX, *global, "Object", &Object); + if (!JS_GetProperty(GLOBAL_CX, *global, "Object", &Object)) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); + return NULL; + } JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull()); JS::RootedValue *ret = new JS::RootedValue(GLOBAL_CX); diff --git a/src/ListType.cc b/src/ListType.cc index cd619569..96eeec53 100644 --- a/src/ListType.cc +++ b/src/ListType.cc @@ -16,6 +16,6 @@ ListType::ListType(PyObject *object) : PyType(object) {} ListType::ListType(JSContext *cx, JS::HandleObject jsArrayObj) { JSArrayProxy *proxy = (JSArrayProxy *)PyObject_CallObject((PyObject *)&JSArrayProxyType, NULL); - proxy->jsObject.set(jsArrayObj); + proxy->jsArray.set(jsArrayObj); this->pyObject = (PyObject *)proxy; } \ No newline at end of file diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index b9fdc0d6..af79f477 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -1,16 +1,17 @@ /** * @file PyProxyHandler.cc - * @author Caleb Aikens (caleb@distributive.network) - * @brief Struct for creating JS proxy objects. Used by DictType for object coercion + * @author Caleb Aikens (caleb@distributive.network) and Philippe Laporte (philippe@distributive.network) + * @brief Struct for creating JS proxy objects. Used by DictType for object coercion TODO * @version 0.1 * @date 2023-04-20 * - * Copyright (c) 2023 Distributive Corp. + * Copyright (c) 2023-2024 Distributive Corp. * */ #include "include/PyProxyHandler.hh" +#include "include/JSArrayProxy.hh" #include "include/jsTypeFactory.hh" #include "include/pyTypeFactory.hh" #include "include/StrType.hh" @@ -19,6 +20,8 @@ #include #include #include +#include +#include #include @@ -155,6 +158,12 @@ bool PyProxyHandler::defineProperty(JSContext *cx, JS::HandleObject proxy, return result.failInvalidDescriptor(); } +bool PyProxyHandler::getBuiltinClass(JSContext *cx, JS::HandleObject proxy, + js::ESClass *cls) const { + *cls = js::ESClass::Object; + return true; +} + bool PyBaseProxyHandler::getPrototypeIfOrdinary(JSContext *cx, JS::HandleObject proxy, bool *isOrdinary, JS::MutableHandleObject protop) const { @@ -164,41 +173,2078 @@ bool PyBaseProxyHandler::getPrototypeIfOrdinary(JSContext *cx, JS::HandleObject return true; } -bool PyBaseProxyHandler::preventExtensions(JSContext *cx, JS::HandleObject proxy, - JS::ObjectOpResult &result) const { - result.succeed(); +bool PyBaseProxyHandler::preventExtensions(JSContext *cx, JS::HandleObject proxy, + JS::ObjectOpResult &result) const { + result.succeed(); + return true; +} + +bool PyBaseProxyHandler::isExtensible(JSContext *cx, JS::HandleObject proxy, + bool *extensible) const { + *extensible = false; + return true; +} + + + +// PyList ---------------------------------------------------------------------- +const char PyListProxyHandler::family = 0; + +static bool array_reverse(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + if (PyList_GET_SIZE(self) > 1) { + if (PyList_Reverse(self) < 0) { + return false; + } + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + +static bool array_pop(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + if (PyList_GET_SIZE(self) == 0) { + args.rval().setUndefined(); + return true; + } + + PyObject *result = PyObject_CallMethod(self, "pop", NULL); + + if (!result) { + PyErr_Clear(); + args.rval().setUndefined(); + return true; + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_push(JSContext *cx, unsigned argc, JS::Value *vp) { // surely the function name is in there...review JSAPI examples + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + unsigned numArgs = args.length(); + for (unsigned index = 0; index < numArgs; index++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + elementVal->set(args[index].get()); + if (PyList_Append(self, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } + } + + args.rval().setInt32(PyList_GET_SIZE(self)); + return true; +} + +static bool array_shift(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfSize = PyList_GET_SIZE(self); + + if (selfSize == 0) { + args.rval().setUndefined(); + return true; + } + + PyObject *result = PyList_GetItem(self, 0); + if (!result) { + return false; + } + if (PySequence_DelItem(self, 0) < 0) { + return false; + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_unshift(JSContext *cx, unsigned argc, JS::Value *vp) { // surely the function name is in there...review JSAPI examples + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + for (int index = args.length() - 1; index >= 0; index--) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + elementVal->set(args[index].get()); + if (PyList_Insert(self, 0, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } + } + + args.rval().setInt32(PyList_GET_SIZE(self)); + return true; +} + +// private util +static inline uint64_t normalizeSliceTerm(int64_t value, uint64_t length) { + if (value < 0) { + value += length; + if (value < 0) { + return 0; + } + } + else if (double(value) > double(length)) { + return length; + } + return value; +} + +static bool array_slice(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "slice", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + uint64_t start = 0; + uint64_t stop = selfLength; + if (args.length() > 0) { + int64_t d; + + if (!JS::ToInt64(cx, args[0], &d)) { + return false; + } + + start = normalizeSliceTerm(d, selfLength); + + if (args.hasDefined(1)) { + if (!JS::ToInt64(cx, args[1], &d)) { + return false; + } + + stop = normalizeSliceTerm(d, selfLength); + } + } + + PyObject *result = PyList_GetSlice(self, (Py_ssize_t)start, (Py_ssize_t)stop); + if (!result) { + return false; + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_indexOf(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "indexOf", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + if (selfLength == 0) { + args.rval().setInt32(-1); + return true; + } + + uint64_t start = 0; + if (args.length() > 1) { + int64_t n; + if (!JS::ToInt64(cx, args[1], &n)) { + return false; + } + + if (n >= selfLength) { + args.rval().setInt32(-1); + return true; + } + + if (n >= 0) { + start = uint64_t(n); + } + else { + int64_t d = selfLength + n; + if (d >= 0) { + start = d; + } + } + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0].get()); + PyObject *result = PyObject_CallMethod(self, "index", "Oi", pyTypeFactory(cx, global, elementVal)->getPyObject(), start); + + if (!result) { + PyErr_Clear(); + args.rval().setInt32(-1); + return true; + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_splice(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + + int64_t relativeStart; + if (!JS::ToInt64(cx, args.get(0), &relativeStart)) { + return false; + } + + /* actualStart is the index after which elements will be + deleted and/or new elements will be added */ + uint64_t actualStart; + if (relativeStart < 0) { + actualStart = uint64_t(std::max(double(selfLength) + relativeStart, 0.0)); + } else { + actualStart = uint64_t(std::min(double(relativeStart), double(selfLength))); + } + + unsigned int argsLength = args.length(); + + /* insertCount is the number of elements being added */ + uint32_t insertCount; + if (argsLength < 2) { + insertCount = 0; + } + else { + insertCount = argsLength - 2; + } + + /* actualDeleteCount is the number of elements being deleted */ + uint64_t actualDeleteCount; + if (argsLength < 1) { + actualDeleteCount = 0; + } + else if (argsLength < 2) { + actualDeleteCount = selfLength - actualStart; + } + else { + int64_t deleteCount; + if (!JS::ToInt64(cx, args.get(1), &deleteCount)) { + return false; + } + + actualDeleteCount = uint64_t(std::min(std::max(0.0, double(deleteCount)), double(selfLength - actualStart))); + } + + // get deleted items for return value + PyObject *deleted = PyList_GetSlice(self, actualStart, actualStart + actualDeleteCount); + if (!deleted) { + return false; + } + + // build list for SetSlice call + PyObject *inserted = PyList_New(insertCount); + if (!inserted) { + return false; + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + for (int index = 0; index < insertCount; index++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[index + 2].get()); + if (PyList_SetItem(inserted, index, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } + } + + if (PyList_SetSlice(self, actualStart, actualStart + actualDeleteCount, inserted) < 0) { + return false; + } + + args.rval().set(jsTypeFactory(cx, deleted)); + return true; +} + +static bool array_fill(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "fill", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + + unsigned int argsLength = args.length(); + + int64_t relativeStart; + if (argsLength > 1) { + if (!JS::ToInt64(cx, args.get(1), &relativeStart)) { + return false; + } + } else { + relativeStart = 0; + } + + uint64_t actualStart; + if (relativeStart < 0) { + actualStart = uint64_t(std::max(double(selfLength) + relativeStart, 0.0)); + } else { + actualStart = uint64_t(std::min(double(relativeStart), double(selfLength))); + } + + int64_t relativeEnd; + if (argsLength > 2) { + if (!JS::ToInt64(cx, args.get(2), &relativeEnd)) { + return false; + } + } else { + relativeEnd = selfLength; + } + + uint64_t actualEnd; + if (relativeEnd < 0) { + actualEnd = uint64_t(std::max(double(selfLength) + relativeEnd, 0.0)); + } else { + actualEnd = uint64_t(std::min(double(relativeEnd), double(selfLength))); + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + JS::RootedValue *fillValue = new JS::RootedValue(cx, args[0].get()); + PyObject *fillValueItem = pyTypeFactory(cx, global, fillValue)->getPyObject(); + for (int index = actualStart; index < actualEnd; index++) { + if (PyList_SetItem(self, index, fillValueItem) < 0) { + return false; + } + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + +static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + int64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + + unsigned int argsLength = args.length(); + + int64_t relativeTarget; + if (argsLength > 0) { + if (!JS::ToInt64(cx, args.get(0), &relativeTarget)) { + return false; + } + } else { + relativeTarget = 0; + } + + int64_t actualTarget; + if (relativeTarget < 0) { + actualTarget = int64_t(std::max(double(selfLength) + relativeTarget, 0.0)); + } else { + actualTarget = int64_t(std::min(double(relativeTarget), double(selfLength))); + } + + int64_t relativeStart; + if (argsLength > 1) { + if (!JS::ToInt64(cx, args.get(1), &relativeStart)) { + return false; + } + } else { + relativeStart = 0; + } + + int64_t actualStart; + if (relativeStart < 0) { + actualStart = int64_t(std::max(double(selfLength) + relativeStart, 0.0)); + } else { + actualStart = int64_t(std::min(double(relativeStart), double(selfLength))); + } + + int64_t relativeEnd; + if (argsLength > 2) { + if (!JS::ToInt64(cx, args.get(2), &relativeEnd)) { + return false; + } + } else { + relativeEnd = selfLength; + } + + int64_t actualEnd; + if (relativeEnd < 0) { + actualEnd = int64_t(std::max(double(selfLength) + relativeEnd, 0.0)); + } else { + actualEnd = int64_t(std::min(double(relativeEnd), double(selfLength))); + } + + int64_t count = int64_t(std::min(actualEnd - actualStart, selfLength - actualTarget)); + + if (actualStart < actualTarget && actualTarget < actualStart + count) { + actualStart = actualStart + count - 1; + actualTarget = actualTarget + count - 1; + + while (count > 0) { + PyObject *itemStart = PyList_GetItem(self, actualStart); + if (PyList_SetItem(self, actualTarget, itemStart) < 0) { + return false; + } + + actualStart--; + actualTarget--; + count--; + } + } else { + while (count > 0) { + PyObject *itemStart = PyList_GetItem(self, actualStart); + if (PyList_SetItem(self, actualTarget, itemStart) < 0) { + return false; + } + + actualStart++; + actualTarget++; + count--; + } + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + +static bool array_concat(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + Py_ssize_t selfSize = PyList_GET_SIZE(self); + + PyObject *result = PyList_New(selfSize); + + for (Py_ssize_t index = 0; index < selfSize; index++) { + PyList_SetItem(result, index, PyList_GetItem(self, index)); + } + + unsigned numArgs = args.length(); + for (unsigned index = 0; index < numArgs; index++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + elementVal->set(args[index].get()); + + PyObject *item = pyTypeFactory(cx, global, elementVal)->getPyObject(); + if (PyObject_TypeCheck(item, &JSArrayProxyType)) { + // flatten the array only a depth 1 + Py_ssize_t itemLength = JSArrayProxyMethodDefinitions::JSArrayProxy_length((JSArrayProxy *)item); + for (Py_ssize_t flatIndex = 0; flatIndex < itemLength; flatIndex++) { + elementVal = new JS::RootedValue(cx); + if (!JS_GetElement(cx, ((JSArrayProxy *)item)->jsArray, flatIndex, elementVal)) { + return false; + } + if (PyList_Append(result, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } + } + } + else if (PyObject_TypeCheck(item, &PyList_Type)) { + // flatten the array only at depth 1 + Py_ssize_t itemLength = PyList_GET_SIZE(item); + for (Py_ssize_t flatIndex = 0; flatIndex < itemLength; flatIndex++) { + if (PyList_Append(result, PyList_GetItem(item, flatIndex)) < 0) { + return false; + } + } + } + else { + if (PyList_Append(result, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } + } + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_lastIndexOf(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "lastIndexOf", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + if (selfLength == 0) { + args.rval().setInt32(-1); + return true; + } + + uint64_t start = selfLength - 1; + if (args.length() > 1) { + int64_t n; + if (!JS::ToInt64(cx, args[1], &n)) { + return false; + } + + if (n < 0) { + double d = double(selfLength) + n; + if (d < 0) { + args.rval().setInt32(-1); + return true; + } + start = uint64_t(d); + } else if (n < double(start)) { + start = uint64_t(n); + } + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0].get()); + PyObject *element = pyTypeFactory(cx, global, elementVal)->getPyObject(); + for (int64_t index = start; index >= 0; index--) { + PyObject *item = PyList_GetItem(self, index); + Py_INCREF(item); + int cmp = PyObject_RichCompareBool(item, element, Py_EQ); + Py_DECREF(item); + if (cmp < 0) { + return false; + } + else if (cmp == 1) { + args.rval().setInt32(index); + return true; + } + } + + args.rval().setInt32(-1); + return true; +} + +static bool array_includes(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "includes", 1)) { + return false; + } + + if (!array_indexOf(cx, argc, vp)) { + return false; + } + + args.rval().setBoolean(args.rval().get().toInt32() >= 0 ? true : false); + return true; +} + +static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "forEach", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "forEach: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + Py_ssize_t len = PyList_GET_SIZE(self); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + for (Py_ssize_t index = 0; index < len; index++) { + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; + } + } + + args.rval().setUndefined(); + return true; +} + +static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "map", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + // Decompiling the faulty arg is not accessible through the JSAPI so we do the best effort for the error message + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "map: callback"); + return false; + } + + Py_ssize_t len = PyList_GET_SIZE(self); + + JSObject *retArray = JS::NewArrayObject(cx, len); + JS::RootedObject rootedRetArray(cx, retArray); + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + for (Py_ssize_t index = 0; index < len; index++) { + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; + } + + JS_SetElement(cx, rootedRetArray, index, rval); + } + + args.rval().setObject(*retArray); + return true; +} + +static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "filter", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "filter: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedValueVector retVector(cx); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + JS::Value item = jsTypeFactory(cx, PyList_GetItem(self, index)); + jArgs[0].set(item); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; + } + + if (rval.toBoolean()) { + if (!retVector.append(item)) { + return false; + } + } + } + + JS::HandleValueArray jsValueArray(retVector); + JSObject *retArray = JS::NewArrayObject(cx, jsValueArray); + + args.rval().setObject(*retArray); + return true; +} + +static bool array_reduce(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "reduce", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "reduce: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue *accumulator; + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (args.length() > 1) { + accumulator = new JS::RootedValue(cx, args[1].get()); + } + else { + if (len == 0) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_EMPTY_ARRAY_REDUCE); + return false; + } + accumulator = new JS::RootedValue(cx, jsTypeFactory(cx, PyList_GetItem(self, 0))); + } + + for (Py_ssize_t index = args.length() > 1 ? 0 : 1; index < len; index++) { + jArgs[0].set(*accumulator); + jArgs[1].set(jsTypeFactory(cx, PyList_GetItem(self, index))); + jArgs[2].setInt32(index); + jArgs[3].set(selfValue); + + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, accumulator)) { + delete accumulator; + return false; + } + } + + args.rval().set(accumulator->get()); + delete accumulator; + return true; +} + +static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "reduceRight", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "reduceRight: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue *accumulator; + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (args.length() > 1) { + accumulator = new JS::RootedValue(cx, args[1].get()); + } + else { + if (len == 0) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_EMPTY_ARRAY_REDUCE); + return false; + } + accumulator = new JS::RootedValue(cx, jsTypeFactory(cx, PyList_GetItem(self, len - 1))); + } + + for (int64_t index = args.length() > 1 ? len - 1 : len - 2; index >= 0; index--) { + jArgs[0].set(*accumulator); + jArgs[1].set(jsTypeFactory(cx, PyList_GetItem(self, index))); + jArgs[2].setInt32(index); + jArgs[3].set(selfValue); + + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, accumulator)) { + delete accumulator; + return false; + } + } + + args.rval().set(accumulator->get()); + delete accumulator; + return true; +} + +static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "some", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "some: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; + } + + if (rval.toBoolean()) { + args.rval().setBoolean(true); + return true; + } + } + + args.rval().setBoolean(false); + return true; +} + +static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "every", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "every: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; + } + + if (!rval.toBoolean()) { + args.rval().setBoolean(false); + return true; + } + } + + args.rval().setBoolean(true); + return true; +} + +static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "find", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "find: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + JS::Value item = jsTypeFactory(cx, PyList_GetItem(self, index)); + jArgs[0].set(item); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; + } + + if (rval.toBoolean()) { + args.rval().set(item); + return true; + } + } + + args.rval().setUndefined(); + return true; +} + +static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "findIndex", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "findIndex: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; + } + + if (rval.toBoolean()) { + args.rval().setInt32(index); + return true; + } + } + + args.rval().setInt32(-1); + return true; +} + +// private +static uint32_t FlattenIntoArray(JSContext *cx, JS::HandleObject global, + JSObject *retArray, PyObject *source, + Py_ssize_t sourceLen, uint32_t start, uint32_t depth) { + + uint32_t targetIndex = start; + + JS::RootedObject rootedGlobal(cx, global); + + for (uint32_t sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + + if (PyObject_TypeCheck(source, &JSArrayProxyType)) { + JS_GetElement(cx, ((JSArrayProxy *)source)->jsArray, sourceIndex, elementVal); + } + else if (PyObject_TypeCheck(source, &PyList_Type)) { + elementVal->set(jsTypeFactory(cx, PyList_GetItem(source, sourceIndex))); + } + + PyObject *element = pyTypeFactory(cx, &rootedGlobal, elementVal)->getPyObject(); + + bool shouldFlatten; + if (depth > 0) { + shouldFlatten = PyObject_TypeCheck(element, &JSArrayProxyType) || PyObject_TypeCheck(element, &PyList_Type); + } else { + shouldFlatten = false; + } + + if (shouldFlatten) { + Py_ssize_t elementLen; + if (PyObject_TypeCheck(element, &JSArrayProxyType)) { + elementLen = JSArrayProxyMethodDefinitions::JSArrayProxy_length((JSArrayProxy *)element); + } + else if (PyObject_TypeCheck(element, &PyList_Type)) { + elementLen = PyList_GET_SIZE(element); + } + + targetIndex = FlattenIntoArray(cx, global, + retArray, + element, + elementLen, + targetIndex, + depth - 1 + ); + } + else { + JS::RootedObject rootedRetArray(cx, retArray); + + uint32_t length; + JS::GetArrayLength(cx, rootedRetArray, &length); + if (targetIndex >= length) { + JS::SetArrayLength(cx, rootedRetArray, targetIndex + 1); + } + + JS_SetElement(cx, rootedRetArray, targetIndex, *elementVal); + + targetIndex++; + } + } + + return targetIndex; +} + +// private +static uint32_t FlattenIntoArrayWithCallBack(JSContext *cx, JS::HandleObject global, + JSObject *retArray, PyObject *source, + Py_ssize_t sourceLen, uint32_t start, uint32_t depth, + JS::HandleValue callBack, JS::HandleObject thisArg) { + + uint32_t targetIndex = start; + + JS::RootedObject rootedGlobal(cx, global); + JS::RootedValue sourceValue(cx, jsTypeFactory(cx, source)); + JS::Rooted> jArgs(cx); + JS::RootedValue elementVal(cx); + JS::RootedValue retVal(cx); + + for (uint32_t sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { + if (PyObject_TypeCheck(source, &JSArrayProxyType)) { + JS_GetElement(cx, ((JSArrayProxy *)source)->jsArray, sourceIndex, &elementVal); + } + else if (PyObject_TypeCheck(source, &PyList_Type)) { + elementVal.set(jsTypeFactory(cx, PyList_GetItem(source, sourceIndex))); + } + + JS::RootedValue *retVal = new JS::RootedValue(cx); + + jArgs[0].set(elementVal); + jArgs[1].setInt32(sourceIndex); + jArgs[2].set(sourceValue); + if (!JS_CallFunctionValue(cx, thisArg, callBack, jArgs, retVal)) { + return false; + } + + PyObject *element = pyTypeFactory(cx, &rootedGlobal, retVal)->getPyObject(); + + bool shouldFlatten; + if (depth > 0) { + shouldFlatten = PyObject_TypeCheck(element, &JSArrayProxyType) || PyObject_TypeCheck(element, &PyList_Type); + } else { + shouldFlatten = false; + } + + Py_ssize_t elementLen; + if (PyObject_TypeCheck(element, &JSArrayProxyType)) { + elementLen = JSArrayProxyMethodDefinitions::JSArrayProxy_length((JSArrayProxy *)element); + } + else if (PyObject_TypeCheck(element, &PyList_Type)) { + elementLen = PyList_GET_SIZE(element); + } + + if (shouldFlatten) { + targetIndex = FlattenIntoArrayWithCallBack(cx, global, + retArray, + element, + elementLen, + targetIndex, + depth - 1, + callBack, + thisArg + ); + } + else { + JS::RootedObject rootedRetArray(cx, retArray); + + uint32_t length; + JS::GetArrayLength(cx, rootedRetArray, &length); + + if (PyObject_TypeCheck(element, &JSArrayProxyType) || PyObject_TypeCheck(element, &PyList_Type)) { + // flatten array callBack result to depth 1 + JS::RootedValue elementIndexVal(cx); + for (uint32_t elementIndex = 0; elementIndex < elementLen; elementIndex++, targetIndex++) { + if (PyObject_TypeCheck(element, &JSArrayProxyType)) { + JS_GetElement(cx, ((JSArrayProxy *)element)->jsArray, elementIndex, &elementIndexVal); + } + else { + elementIndexVal.set(jsTypeFactory(cx, PyList_GetItem(element, elementIndex))); + } + + if (targetIndex >= length) { + JS::SetArrayLength(cx, rootedRetArray, length = targetIndex + 1); + } + + JS_SetElement(cx, rootedRetArray, targetIndex, elementIndexVal); + } + + return targetIndex; + } + else { + if (targetIndex >= length) { + JS::SetArrayLength(cx, rootedRetArray, targetIndex + 1); + } + + JS_SetElement(cx, rootedRetArray, targetIndex, *retVal); + + targetIndex++; + } + } + } + + return targetIndex; +} + +static bool array_flat(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t sourceLen = PyList_GET_SIZE(self); + + uint32_t depthNum; + if (args.length() > 0) { + depthNum = args[0].get().toInt32(); + } + else { + depthNum = 1; + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JSObject *retArray = JS::NewArrayObject(cx, sourceLen); // min end length + + FlattenIntoArray(cx, *global, retArray, self, sourceLen, 0, depthNum); + + args.rval().setObject(*retArray); + return true; +} + +static bool array_flatMap(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "flatMap", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t sourceLen = PyList_GET_SIZE(self); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "flatMap: callback"); + return false; + } + + JS::RootedValue callBack(cx, callbackfn); + + JS::RootedObject rootedThisArg(cx); + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JSObject *retArray = JS::NewArrayObject(cx, sourceLen); // min end length + + FlattenIntoArrayWithCallBack(cx, *global, retArray, self, sourceLen, 0, 1, callBack, rootedThisArg); + + args.rval().setObject(*retArray); + return true; +} + +static bool array_join(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + if (selfLength == 0) { + args.rval().setString(JS_NewStringCopyZ(cx, "")); + return true; + } + + JS::RootedString *rootedSeparator; + if (args.hasDefined(0)) { + rootedSeparator = new JS::RootedString(cx, JS::ToString(cx, args[0])); + } + else { + rootedSeparator = new JS::RootedString(cx, JS_NewStringCopyZ(cx, ",")); + } + + JSString *writer = JS_NewStringCopyZ(cx, ""); + JS::RootedString rootedWriter(cx); + + for (Py_ssize_t index = 0; index < selfLength; index++) { + rootedWriter.set(writer); + if (index > 0) { + writer = JS_ConcatStrings(cx, rootedWriter, *rootedSeparator); + rootedWriter.set(writer); + } + + JS::RootedValue element(cx, jsTypeFactory(cx, PyList_GetItem(self, index))); + if (!element.isNullOrUndefined()) { + JS::RootedValue rval(cx); + + JS::RootedObject retObject(cx); + + if (!JS_ValueToObject(cx, element, &retObject)) { + delete rootedSeparator; + return false; + } + + if (!JS_CallFunctionName(cx, retObject, "toString", JS::HandleValueArray::empty(), &rval)) { + delete rootedSeparator; + return false; + } + + JS::RootedString retString(cx, rval.toString()); + writer = JS_ConcatStrings(cx, rootedWriter, retString); + } + } + + delete rootedSeparator; + + args.rval().setString(writer); return true; } -bool PyBaseProxyHandler::isExtensible(JSContext *cx, JS::HandleObject proxy, - bool *extensible) const { - *extensible = false; +static bool array_toString(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_join(cx, argc, vp); +} + +static bool array_toLocaleString(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + if (selfLength == 0) { + args.rval().setString(JS_NewStringCopyZ(cx, "")); + return true; + } + + JS::RootedString rootedSeparator(cx, JS_NewStringCopyZ(cx, ",")); + + JSString *writer = JS_NewStringCopyZ(cx, ""); + JS::RootedString rootedWriter(cx); + + JS::HandleValueArray jArgs(args); + + for (Py_ssize_t index = 0; index < selfLength; index++) { + rootedWriter.set(writer); + if (index > 0) { + writer = JS_ConcatStrings(cx, rootedWriter, rootedSeparator); + rootedWriter.set(writer); + } + + JS::RootedValue element(cx, jsTypeFactory(cx, PyList_GetItem(self, index))); + if (!element.isNullOrUndefined()) { + JS::RootedValue rval(cx); + + JS::RootedObject retObject(cx); + + if (!JS_ValueToObject(cx, element, &retObject)) { + return false; + } + + if (!JS_CallFunctionName(cx, retObject, "toLocaleString", jArgs, &rval)) { + return false; + } + + JS::RootedString retString(cx, rval.toString()); + writer = JS_ConcatStrings(cx, rootedWriter, retString); + } + } + + args.rval().setString(writer); return true; } -const char PyListProxyHandler::family = 0; +static bool array_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + + +////// Sorting + + +static void swapItems(PyObject *list, int i, int j) { + if (i != j) { + PyObject *list_i = PyList_GetItem(list, i); + PyObject *list_j = PyList_GetItem(list, j); + Py_INCREF(list_i); + Py_INCREF(list_j); + PyList_SetItem(list, i, list_j); + PyList_SetItem(list, j, list_i); + } +} + +static int invokeCallBack(PyObject *list, int index, JS::HandleValue leftValue, JSContext *cx, JS::HandleFunction callBack) { + JS::Rooted> jArgs(cx); + + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(list, index))); + jArgs[1].set(leftValue); + + JS::RootedValue retVal(cx); + if (!JS_CallFunction(cx, nullptr, callBack, jArgs, &retVal)) { + throw "JS_CallFunction failed"; + } + + return retVal.toInt32(); +} + +// Adapted from Kernigan&Ritchie's C book +static void quickSort(PyObject *list, int left, int right, JSContext *cx, JS::HandleFunction callBack) { + if (left >= right) { + // base case + return; + } + + swapItems(list, left, (left + right) / 2); + + JS::RootedValue leftValue(cx, jsTypeFactory(cx, PyList_GetItem(list, left))); + + int last = left; + for (int index = left + 1; index <= right; index++) { + if (invokeCallBack(list, index, leftValue, cx, callBack) < 0) { + swapItems(list, ++last, index); + } + } + + swapItems(list, left, last); + + quickSort(list, left, last - 1, cx, callBack); + + quickSort(list, last + 1, right, cx, callBack); +} + +// private +static bool js_sort_compare_default(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue leftVal(cx, args[0]); + JS::RootedValue rightVal(cx, args[1]); + + // check for undefined + if (leftVal.isNullOrUndefined()) { + if (rightVal.isNullOrUndefined()) { + args.rval().setInt32(0); + } + else { + args.rval().setInt32(1); + } + return true; + } + else if (rightVal.isNullOrUndefined()) { + args.rval().setInt32(-1); + return true; + } + + JS::RootedObject leftObject(cx); + if (!JS_ValueToObject(cx, leftVal, &leftObject)) { + return false; + } + JS::RootedValue leftToStringVal(cx); + if (!JS_CallFunctionName(cx, leftObject, "toString", JS::HandleValueArray::empty(), &leftToStringVal)) { + return false; + } + + JS::RootedObject rightObject(cx); + if (!JS_ValueToObject(cx, rightVal, &rightObject)) { + return false; + } + JS::RootedValue rightToStringVal(cx); + if (!JS_CallFunctionName(cx, rightObject, "toString", JS::HandleValueArray::empty(), &rightToStringVal)) { + return false; + } + + int32_t cmpResult; + if (!JS_CompareStrings(cx, leftToStringVal.toString(), rightToStringVal.toString(), &cmpResult)) { + return false; + } + + args.rval().setInt32(cmpResult); + return true; +} + +// private +static bool js_sort_compare_key_func(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject callee(cx, &args.callee()); + + JS::RootedValue keyFunc(cx); + if (!JS_GetProperty(cx, callee, "_key_func_param", &keyFunc)) { + PyErr_Format(PyExc_SystemError, "JSAPI call failed"); + return false; + } + PyObject *keyfunc = (PyObject *)keyFunc.toPrivate(); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(&args.callee())); + + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0]); + PyObject *args_0 = pyTypeFactory(cx, global, elementVal)->getPyObject(); + + elementVal = new JS::RootedValue(cx, args[1]); + PyObject *args_1 = pyTypeFactory(cx, global, elementVal)->getPyObject(); + + PyObject *result = PyObject_CallFunction(keyfunc, "OO", args_0, args_1); + if (!result) { + return false; + } + + if (PyLong_Check(result)) { + args.rval().setInt32((int32_t)PyLong_AsLongLong(result)); + return true; + } + else { + PyErr_Format(PyExc_TypeError, "incorrect compare function return type"); + return false; + } +} + +static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (len > 1) { + if (args.length() < 1) { + JS::RootedFunction funObj(cx, JS_NewFunction(cx, js_sort_compare_default, 2, 0, NULL)); + + try { + quickSort(self, 0, len - 1, cx, funObj); + } catch (const char *message) { + return false; + } + } + else { + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG); + return false; + } + + JS::RootedValue callBack(cx, callbackfn); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + PyObject *pyFunc = pyTypeFactory(cx, global, new JS::RootedValue(cx, args[0].get()))->getPyObject(); + // check if JS or Python function + if (PyFunction_Check(pyFunc)) { + // it's a user-defined python function, check has two arguments + PyObject *code = PyFunction_GetCode(pyFunc); + if (((PyCodeObject *)code)->co_argcount == 1) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG); + return false; + } + else { + JSFunction *compareFun = JS_NewFunction(cx, js_sort_compare_key_func, 2, 0, NULL); + JS::RootedFunction rootedFun(cx, compareFun); + JS::RootedObject rootedFunObj(cx, JS_GetFunctionObject(compareFun)); + + JS::RootedValue privateValue(cx, JS::PrivateValue(pyFunc)); + if (!JS_SetProperty(cx, rootedFunObj, "_key_func_param", privateValue)) { // JS::SetReservedSlot(functionObj, KeyFuncSlot, JS::PrivateValue(keyfunc)); does not work + PyErr_Format(PyExc_SystemError, "JSAPI call failed"); + return NULL; + } + + try { + quickSort(self, 0, len - 1, cx, rootedFun); + } catch (const char *message) { + return false; + } + + // cleanup + if (!JS_DeleteProperty(cx, rootedFunObj, "_key_func_param")) { + PyErr_Format(PyExc_SystemError, "JSAPI call failed"); + return false; + } + } + } else { + // it's either a JS function or a builtin python func + int flags = PyCFunction_GetFlags(pyFunc); + + if (flags & METH_VARARGS && !(flags & METH_KEYWORDS)) { + // it's a user-defined JS func + JS::RootedFunction funObj(cx, JS_ValueToFunction(cx, callBack)); + + try { + quickSort(self, 0, len - 1, cx, funObj); + } catch (const char *message) { + return false; + } + } + else { + // it's a built-in python function + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG); + return false; + } + } + } + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + + + +// ListIterator + + +#define ITEM_KIND_KEY 0 +#define ITEM_KIND_VALUE 1 +#define ITEM_KIND_KEY_AND_VALUE 2 + +enum { + ListIteratorSlotIteratedObject, + ListIteratorSlotNextIndex, + ListIteratorSlotItemKind, + ListIteratorSlotCount +}; + +static JSClass listIteratorClass = {"ListIterator", JSCLASS_HAS_RESERVED_SLOTS(ListIteratorSlotCount)}; + +static bool iterator_next(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject thisObj(cx); + if (!args.computeThis(cx, &thisObj)) return false; + + PyObject *self = JS::GetMaybePtrFromReservedSlot(thisObj, ListIteratorSlotIteratedObject); + + JS::RootedValue rootedNextIndex(cx, JS::GetReservedSlot(thisObj, ListIteratorSlotNextIndex)); + JS::RootedValue rootedItemKind(cx, JS::GetReservedSlot(thisObj, ListIteratorSlotItemKind)); + + int32_t nextIndex; + int32_t itemKind; + if (!JS::ToInt32(cx, rootedNextIndex, &nextIndex) || !JS::ToInt32(cx, rootedItemKind, &itemKind)) return false; + + JS::RootedObject result(cx, JS_NewPlainObject(cx)); + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (nextIndex >= len) { + // UnsafeSetReservedSlot(obj, ITERATOR_SLOT_TARGET, null); // TODO lose ref + JS::RootedValue done(cx, JS::BooleanValue(true)); + if (!JS_SetProperty(cx, result, "done", done)) return false; + args.rval().setObject(*result); + return result; + } + + JS::SetReservedSlot(thisObj, ListIteratorSlotNextIndex, JS::Int32Value(nextIndex + 1)); + + JS::RootedValue done(cx, JS::BooleanValue(false)); + if (!JS_SetProperty(cx, result, "done", done)) return false; + + if (itemKind == ITEM_KIND_VALUE) { + PyObject *item = PyList_GetItem(self, nextIndex); + if (!item) { + return false; + } + JS::RootedValue value(cx, jsTypeFactory(cx, item)); + if (!JS_SetProperty(cx, result, "value", value)) return false; + } + else if (itemKind == ITEM_KIND_KEY_AND_VALUE) { + JS::Rooted> items(cx); + + JS::RootedValue rootedNextIndex(cx, JS::Int32Value(nextIndex)); + items[0].set(rootedNextIndex); + + PyObject *item = PyList_GetItem(self, nextIndex); + if (!item) { + return false; + } + JS::RootedValue value(cx, jsTypeFactory(cx, item)); + items[1].set(value); + + JS::RootedValue pair(cx); + JSObject *array = JS::NewArrayObject(cx, items); + pair.setObject(*array); + if (!JS_SetProperty(cx, result, "value", pair)) return false; + } + else { // itemKind == ITEM_KIND_KEY + JS::RootedValue value(cx, JS::Int32Value(nextIndex)); + if (!JS_SetProperty(cx, result, "value", value)) return false; + } + + args.rval().setObject(*result); + return true; +} + +static JSFunctionSpec list_iterator_methods[] = { + JS_FN("next", iterator_next, 0, JSPROP_ENUMERATE), + JS_FS_END +}; + +static bool ListIteratorConstructor(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.isConstructing()) { + JS_ReportErrorASCII(cx, "You must call this constructor with 'new'"); + return false; + } + + JS::RootedObject thisObj(cx, JS_NewObjectForConstructor(cx, &listIteratorClass, args)); + if (!thisObj) { + return false; + } + + args.rval().setObject(*thisObj); + return true; +} + +static bool DefineListIterator(JSContext *cx, JS::HandleObject global) { + JS::RootedObject iteratorPrototype(cx); + if (!JS_GetClassPrototype(cx, JSProto_Iterator, &iteratorPrototype)) { + return false; + } + + JS::RootedObject protoObj(cx, + JS_InitClass(cx, global, + nullptr, iteratorPrototype, + "ListIterator", + ListIteratorConstructor, 0, + nullptr, list_iterator_methods, + nullptr, nullptr) + ); + + return protoObj; // != nullptr +} + +// private util +static bool array_iterator_func(JSContext *cx, unsigned argc, JS::Value *vp, int itemKind) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JS::RootedValue constructor_val(cx); + if (!JS_GetProperty(cx, global, "ListIterator", &constructor_val)) return false; + if (!constructor_val.isObject()) { + if (!DefineListIterator(cx, global)) { + return false; + } + + if (!JS_GetProperty(cx, global, "ListIterator", &constructor_val)) return false; + if (!constructor_val.isObject()) { + JS_ReportErrorASCII(cx, "ListIterator is not a constructor"); + return false; + } + } + JS::RootedObject constructor(cx, &constructor_val.toObject()); + + JS::RootedObject obj(cx); + if (!JS::Construct(cx, constructor_val, JS::HandleValueArray::empty(), &obj)) return false; + if (!obj) return false; + + JS::SetReservedSlot(obj, ListIteratorSlotIteratedObject, JS::PrivateValue((void *)self)); + JS::SetReservedSlot(obj, ListIteratorSlotNextIndex, JS::Int32Value(0)); + JS::SetReservedSlot(obj, ListIteratorSlotItemKind, JS::Int32Value(itemKind)); + + args.rval().setObject(*obj); + return true; +} + +static bool array_entries(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_iterator_func(cx, argc, vp, ITEM_KIND_KEY_AND_VALUE); +} + +static bool array_keys(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_iterator_func(cx, argc, vp, ITEM_KIND_KEY); +} + +static bool array_values(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_iterator_func(cx, argc, vp, ITEM_KIND_VALUE); +} + + +static JSMethodDef array_methods[] = { + {"reverse", array_reverse, 0}, + {"pop", array_pop, 0}, + {"push", array_push, 1}, + {"shift", array_shift, 0}, + {"unshift", array_unshift, 1}, + {"concat", array_concat, 1}, + {"slice", array_slice, 2}, + {"indexOf", array_indexOf, 1}, + {"lastIndexOf", array_lastIndexOf, 1}, + {"splice", array_splice, 2}, + {"sort", array_sort, 1}, + {"fill", array_fill, 3}, + {"copyWithin", array_copyWithin, 3}, + {"includes", array_includes, 1}, + {"forEach", array_forEach, 1}, + {"map", array_map, 1}, + {"filter", array_filter, 1}, + {"reduce", array_reduce, 1}, + {"reduceRight", array_reduceRight, 1}, + {"some", array_some, 1}, + {"every", array_every, 1}, + {"find", array_find, 1}, + {"findIndex", array_findIndex, 1}, + {"flat", array_flat, 1}, + {"flatMap", array_flatMap, 1}, + {"join", array_join, 1}, + {"toString", array_toString, 0}, + {"toLocaleString", array_toLocaleString, 0}, + {"valueOf", array_valueOf, 0}, + {"entries", array_entries, 0}, + {"keys", array_keys, 0}, + {"values", array_values, 0}, + {NULL, NULL, 0} +}; + bool PyListProxyHandler::getOwnPropertyDescriptor( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle> desc ) const { + // see if we're calling a function + if (id.isString()) { + for (size_t index = 0;; index++) { + bool isThatFunction; + const char *methodName = array_methods[index].name; + if (methodName == NULL) { // reached end of list + break; + } + else if (JS_StringEqualsAscii(cx, id.toString(), methodName, &isThatFunction) && isThatFunction) { + JSFunction *newFunction = JS_NewFunction(cx, array_methods[index].call, array_methods[index].nargs, 0, NULL); + if (!newFunction) return false; + JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction)); + desc.set(mozilla::Some( + JS::PropertyDescriptor::Data( + JS::ObjectValue(*funObj), + {JS::PropertyAttribute::Enumerable} + ) + )); + return true; + } + } + } - // We're trying to get the "length" property + // "length" property bool isLengthProperty; if (id.isString() && JS_StringEqualsLiteral(cx, id.toString(), "length", &isLengthProperty) && isLengthProperty) { - // proxy.length = len(pyObject) desc.set(mozilla::Some( JS::PropertyDescriptor::Data( - JS::Int32Value(PySequence_Size(pyObject)) + JS::Int32Value(PyList_Size(pyObject)) + ) + )); + return true; + } + + // "constructor" property + bool isConstructorProperty; + if (id.isString() && JS_StringEqualsLiteral(cx, id.toString(), "constructor", &isConstructorProperty) && isConstructorProperty) { + JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JS::RootedObject rootedArrayPrototype(cx); + if (!JS_GetClassPrototype(cx, JSProto_Array, &rootedArrayPrototype)) { + return false; + } + + JS::RootedValue Array_Prototype_Constructor(cx); + if (!JS_GetProperty(cx, rootedArrayPrototype, "constructor", &Array_Prototype_Constructor)) { + return false; + } + + JS::RootedObject rootedArrayPrototypeConstructor(cx, Array_Prototype_Constructor.toObjectOrNull()); + + desc.set(mozilla::Some( + JS::PropertyDescriptor::Data( + JS::ObjectValue(*rootedArrayPrototypeConstructor), + {JS::PropertyAttribute::Enumerable} ) )); return true; } - // We're trying to get an item + // symbol property + if (id.isSymbol()) { + JS::RootedSymbol rootedSymbol(cx, id.toSymbol()); + + if (JS::GetSymbolCode(rootedSymbol) == JS::SymbolCode::iterator) { + JSFunction *newFunction = JS_NewFunction(cx, array_values, 0, 0, NULL); + if (!newFunction) return false; + JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction)); + desc.set(mozilla::Some( + JS::PropertyDescriptor::Data( + JS::ObjectValue(*funObj), + {JS::PropertyAttribute::Enumerable} + ) + )); + return true; + } + } + + // item Py_ssize_t index; PyObject *item; - if (idToIndex(cx, id, &index) && (item = PySequence_GetItem(pyObject, index))) { + if (idToIndex(cx, id, &index) && (item = PyList_GetItem(pyObject, index))) { desc.set(mozilla::Some( JS::PropertyDescriptor::Data( jsTypeFactory(cx, item), @@ -211,6 +2257,10 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( return true; } +void PyListProxyHandler::finalize(JS::GCContext *gcx, JSObject *proxy) const { + JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(nullptr)); +} + bool PyListProxyHandler::defineProperty( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::Handle desc, JS::ObjectOpResult &result @@ -231,7 +2281,7 @@ bool PyListProxyHandler::defineProperty( JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); JS::RootedValue *itemV = new JS::RootedValue(cx, desc.value()); PyObject *item = pyTypeFactory(cx, global, itemV)->getPyObject(); - if (PySequence_SetItem(pyObject, index, item) < 0) { + if (PyList_SetItem(pyObject, index, item) < 0) { return result.failBadIndex(); } return result.succeed(); @@ -239,7 +2289,7 @@ bool PyListProxyHandler::defineProperty( bool PyListProxyHandler::ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const { // Modified from https://hg.mozilla.org/releases/mozilla-esr102/file/3b574e1/dom/base/RemoteOuterWindowProxy.cpp#l137 - int32_t length = PySequence_Size(pyObject); + int32_t length = PyList_Size(pyObject); if (!props.reserve(length + 1)) { return false; } @@ -259,8 +2309,18 @@ bool PyListProxyHandler::delete_(JSContext *cx, JS::HandleObject proxy, JS::Hand } // Set to undefined instead of actually deleting it - if (PySequence_SetItem(pyObject, index, Py_None) < 0) { + if (PyList_SetItem(pyObject, index, Py_None) < 0) { return result.failCantDelete(); // report failure } return result.succeed(); // report success } + +bool PyListProxyHandler::isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const { + *answer = JS::IsArrayAnswer::Array; + return true; +} + +bool PyListProxyHandler::getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const { + *cls = js::ESClass::Array; + return true; +} \ No newline at end of file diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index fa631460..fba294d3 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -165,15 +165,20 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { returnType.setObject(*((JSObjectProxy *)object)->jsObject); } else if (PyObject_TypeCheck(object, &JSArrayProxyType)) { - returnType.setObject(*((JSArrayProxy *)object)->jsObject); + returnType.setObject(*((JSArrayProxy *)object)->jsArray); } else if (PyDict_Check(object) || PyList_Check(object)) { JS::RootedValue v(cx); JSObject *proxy; if (PyList_Check(object)) { - proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, NULL); + JS::RootedObject arrayPrototype(cx); + JS_GetClassPrototype(cx, JSProto_Array, &arrayPrototype); // so that instanceof will work, not that prototype methods will + proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, arrayPrototype.get()); + JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(object)); } else { - proxy = js::NewProxyObject(cx, new PyProxyHandler(object), v, NULL); + JS::RootedObject objectPrototype(cx); + JS_GetClassPrototype(cx, JSProto_Object, &objectPrototype); // so that instanceof will work, not that prototype methods will + proxy = js::NewProxyObject(cx, new PyProxyHandler(object), v, objectPrototype.get()); } returnType.setObject(*proxy); } @@ -239,7 +244,9 @@ bool callPyFunc(JSContext *cx, unsigned int argc, JS::Value *vp) { JS::RootedObject *thisv = new JS::RootedObject(cx); JS_ValueToObject(cx, callargs.thisv(), thisv); - if (!callargs.length()) { + unsigned int callArgsLength = callargs.length(); + + if (!callArgsLength) { #if PY_VERSION_HEX >= 0x03090000 PyObject *pyRval = PyObject_CallNoArgs(pyFunc); #else @@ -255,8 +262,8 @@ bool callPyFunc(JSContext *cx, unsigned int argc, JS::Value *vp) { } // populate python args tuple - PyObject *pyArgs = PyTuple_New(callargs.length()); - for (size_t i = 0; i < callargs.length(); i++) { + PyObject *pyArgs = PyTuple_New(callArgsLength); + for (size_t i = 0; i < callArgsLength; i++) { JS::RootedValue *jsArg = new JS::RootedValue(cx, callargs[i]); PyType *pyArg = pyTypeFactory(cx, thisv, jsArg); if (!pyArg) return false; // error occurred diff --git a/src/modules/pythonmonkey/pythonmonkey.cc b/src/modules/pythonmonkey/pythonmonkey.cc index 6bfc106c..bbb9bf77 100644 --- a/src/modules/pythonmonkey/pythonmonkey.cc +++ b/src/modules/pythonmonkey/pythonmonkey.cc @@ -381,7 +381,10 @@ PyMODINIT_FUNC PyInit_pythonmonkey(void) return NULL; } - JS::RealmOptions options; + JS::RealmCreationOptions creationOptions = JS::RealmCreationOptions(); + JS::RealmBehaviors behaviours = JS::RealmBehaviors(); + creationOptions.setIteratorHelpersEnabled(true); + JS::RealmOptions options = JS::RealmOptions(creationOptions, behaviours); static JSClass globalClass = {"global", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps}; global = new JS::RootedObject(GLOBAL_CX, JS_NewGlobalObject(GLOBAL_CX, &globalClass, nullptr, JS::FireOnNewGlobalHook, options)); if (!global) { diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py new file mode 100644 index 00000000..d8ccc977 --- /dev/null +++ b/tests/python/test_arrays.py @@ -0,0 +1,1755 @@ +import pythonmonkey as pm +from datetime import datetime + +def test_assign(): + items = [1,2,3] + pm.eval("(arr) => {arr[0] = 42}")(items) + assert items[0] == 42 + +def test_get(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr[1]}")(result, items) + assert result[0] == 2 + +def test_get_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.length}")(result, items) + assert result[0] == 3 + +def test_missing_func(): + items = [1,2,3] + try: + pm.eval("(arr) => {arr.after()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('TypeError: arr.after is not a function') + +#reverse +def test_reverse(): + items = [1,2,3] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [3,2,1] + +def test_reverse_size_one(): + items = [1] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [1] + +def test_reverse_size_zero(): + items = [] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [] + +def test_reverse_returns_reference(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reverse(); result[0][0] = 4}")(result, items) + assert result[0] == [4,2,1] + assert items == [4,2,1] + +def test_reverse_ignores_extra_args(): + items = [1,2,3] + pm.eval("(arr) => {arr.reverse(9)}")(items) + assert items == [3,2,1] + +#pop +def test_pop(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) + assert items == [1,2] + assert result[0] == 3 + +def test_pop_empty(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) + assert items == [] + assert result[0] is None + +def test_pop_ignore_extra_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop(1)}")(result, items) + assert items == [1,2] + assert result[0] == 3 + +#join +def test_join_no_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,2,3' + +def test_join_empty_array(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '' + +def test_join_no_arg_diff_types(): + items = [1,False,"END"] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,false,END' + +def test_join_no_arg_with_embedded_list_type(): + items = [1,[2,3],"END"] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,2,3,END' + +def test_join_with_sep(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join('-')}")(result, items) + assert result[0] == '1-2-3' + +def test_join_none(): + items = [None,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == ',2,3' + +def test_join_null(): + items = [pm.null,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == ',2,3' + +def test_join_utf8(): + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, prices) + assert result[0] == '¥7,500,8123,12' + +#toString +def test_toString(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) + assert result[0] == '1,2,3' + +#push +def test_push(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push(4)}")(result, items) + assert items == [1,2,3,4] + assert result[0] == 4 + +def test_push_no_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push()}")(result, items) + assert items == [1,2,3,] + assert result[0] == 3 + +def test_push_two_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push(4,false)}")(result, items) + assert items == [1,2,3,4,False] + assert result[0] == 5 + +def test_push_list(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push([4,5])}")(result, items) + assert items == [1,2,3,[4,5]] + assert result[0] == 4 + +#shift +def test_shift(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) + assert items == [2,3] + assert result[0] == 1 + +def test_shift_empty(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) + assert items == [] + assert result[0] is None + +#unshift +def test_unshift_zero_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift()}")(result, items) + assert items == [1,2,3] + assert result[0] == 3 + +def test_unshift_one_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift(6)}")(result, items) + assert items == [6,1,2,3] + assert result[0] == 4 + +def test_unshift_two_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift(6,7)}")(result, items) + assert items == [6,7,1,2,3] + assert result[0] == 5 + +#concat +def test_concat_primitive(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat(4)}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,4] + +def test_concat_array(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([4,5])}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,4,5] + +def test_concat_empty_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3] + assert items is not result[0] + +def test_concat_two_arrays(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([7,8], [0,1])}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,7,8,0,1] + +def test_concat_mix(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([7,8], true, [0,1])}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,7,8,True,0,1] + +def test_concat_object_element(): + d = {"a":1} + items = [1, 2, d] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) + assert items == [1, 2, d] + assert result[0] == [1, 2, d] + assert items is not result[0] + assert d is items[2] + assert d is result[0][2] + +#slice +def test_slice(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,2)}")(result, items) + assert items == [1,2,3] + assert result[0] == [2] + +def test_slice_copy(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(0,3)}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3] + assert items is not result[0] + +def test_slice_start_zero(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(0,2)}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2] + +def test_slice_stop_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,3)}")(result, items) + assert items == [1,2,3] + assert result[0] == [2,3] + +def test_slice_stop_beyond_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,4)}")(result, items) + assert items == [1,2,3] + assert result[0] == [2,3] + +def test_slice_start_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(-3,-1)}")(result, items) + assert result[0] == [1,2] + +#indexOf +def test_indexOf(): + items = [1,2,3,1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) + assert result[0] == 0 + +def test_indexOf_with_start(): + items = [1,2,3,4,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(3, 3)}")(result, items) + assert result[0] == 4 + +def test_indexOf_with_negative_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(3, -2)}")(result, items) + assert result[0] == 2 + +def test_indexOf_zero_size(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) + assert result[0] == -1 + +def test_indexOf_start_beyond_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1, 10)}")(result, items) + assert result[0] == -1 + +def test_indexOf_not_found(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(10)}")(result, items) + assert result[0] == -1 + +def test_indexOf_small_start(): + items = [1,2,3,1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1, -10)}")(result, items) + assert result[0] == 0 + +#lastIndexOf +def test_lastIndexOf(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_dup(): + items = [1,2,3,1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) + assert result[0] == 3 + +def test_lastIndexOf_with_from_index(): + items = [1,2,3,1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 2)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_with_from_index_greater_than_size(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 10)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_with_negative_from_index(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -2)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_not_found(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(3, 0)}")(result, items) + assert result[0] == -1 + +def test_lastIndexOf_small_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -10)}")(result, items) + assert result[0] == -1 + +#splice +def test_splice_no_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice()}")(result, items) + assert items == [1,2,3] + assert result[0] == [] + +def test_splice_one_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1)}")(result, items) + assert items == [1] + assert result[0] == [2,3] + +def test_splice_one_arg_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(-2)}")(result, items) + assert items == [1] + assert result[0] == [2,3] + +def test_splice_two_args_negative_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, -1)}")(result, items) + assert items == [1,2,3] + assert result[0] == [] + +def test_splice_two_args_zero_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 0)}")(result, items) + assert items == [1,2,3] + assert result[0] == [] + +def test_splice_two_args_one_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 1)}")(result, items) + assert items == [1,3] + assert result[0] == [2] + +def test_splice_two_args_two_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 2)}")(result, items) + assert items == [1] + assert result[0] == [2,3] + +def test_splice_three_args_zero_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5)}")(result, items) + assert items == [1,5,2,3] + assert result[0] == [] + +def test_splice_three_args_one_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5)}")(result, items) + assert items == [1,5,3] + assert result[0] == [2] + +def test_splice_three_args_two_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5)}")(result, items) + assert items == [1,5] + assert result[0] == [2,3] + +def test_splice_four_args_zero_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5,6)}")(result, items) + assert items == [1,5,6,2,3] + assert result[0] == [] + +def test_splice_four_args_one_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5,6)}")(result, items) + assert items == [1,5,6,3] + assert result[0] == [2] + +def test_splice_four_args_two_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5,6)}")(result, items) + assert items == [1,5,6] + assert result[0] == [2,3] + +#fill +def test_fill_returns_ref_to_self(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8)}")(result, items) + assert items == [8,8,8] + assert items is result[0] + +def test_fill_other_type(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(false)}")(result, items) + assert items == [False,False,False] + +def test_fill_with_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1)}")(result, items) + assert items == [1,8,8] + +def test_fill_with_start_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,-2)}")(result, items) + assert items == [1,8,8] + +def test_fill_with_start_too_high(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,7)}")(result, items) + assert items == [1,2,3] + +def test_fill_with_stop_smaller_than_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,7,2)}")(result, items) + assert items == [1,2,3] + +def test_fill_with_stop(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,2)}")(result, items) + assert items == [1,8,3] + +def test_fill_with_negative_stop(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-1)}")(result, items) + assert items == [1,8,3] + +def test_fill_with_negative_stop_too_low(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-10)}")(result, items) + assert items == [1,2,3] + +def test_fill_with_stop_too_high(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,10)}")(result, items) + assert items == [1,8,8] + +def test_fill_object(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {let a = {a:1}; result[0] = arr.fill(a)}")(result, items) + assert items == [{"a":1},{"a":1},{"a":1}] + assert items is result[0] + assert items[0] is items[1] is items[2] + +#copyWithin +def test_copyWithin(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(0,1)}")(result, items) + assert items == [2,3,3] + assert items is result[0] + +def test_copyWithin_no_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin()}")(result, items) + assert items == [1,2,3] + +def test_copyWithin_target_only_overwrite_all(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(0)}")(result, items) + assert items == [1,2,3] + +def test_copyWithin_target_only(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1)}")(result, items) + assert items == [1,1,2] + +def test_copyWithin_negative_target_only(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(-1)}")(result, items) + assert items == [1,2,1] + +def test_copyWithin_target_too_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(10)}")(result, items) + assert items == [1,2,3] + +def test_copyWithin_target_and_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 2)}")(result, items) + assert items == [1,3,3] + +def test_copyWithin_target_and_start_too_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 10)}")(result, items) + assert items == [1,2,3] + +def test_copyWithin_target_and_negative_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -1)}")(result, items) + assert items == [1,3,3] + +def test_copyWithin_target_and_start_and_end(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,3)}")(result, items) + assert items == [1,3,3,4,5] + +def test_copyWithin_target_and_start_and_negative_end(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-2)}")(result, items) + assert items == [1,3,3,4,5] + +def test_copyWithin_target_too_small_and_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(-10,2)}")(result, items) + assert items == [3,2,3] + +def test_copyWithin_target_greater_than_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => { result[0] = arr.copyWithin(2,1)}")(result, items) + assert items == [1,2,2] + +def test_copyWithin_target_and_start_too_small(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -10)}")(result, items) + assert items == [1,1,2] + +def test_copyWithin_target_and_start_and_end_too_small(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-10)}")(result, items) + assert items == [1,2,3,4,5] + +def test_copyWithin_target_and_start_and_end_too_large(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,1,10)}")(result, items) + assert items == [1,2,2,3,4] + +def test_copyWithin_target_and_start_greater_than_end(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,3,2)}")(result, items) + assert items == [1,2,3,4,5] + +#includes +def test_includes(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1)}")(result, items) + assert result[0] == True + +def test_includes_start_index(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, 1)}")(result, items) + assert result[0] == False + +def test_includes_start_index_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, -1)}")(result, items) + assert result[0] == False + +def test_includes_start_index_negative_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, -10)}")(result, items) + assert result[0] == True + +def test_includes_start_index_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, 10)}")(result, items) + assert result[0] == False + +def test_includes_other_type(): + items = [1,2,'Hi'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes('Hi')}")(result, items) + assert result[0] == True + +def test_includes_not(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(5)}")(result, items) + assert result[0] == False + +def test_includes_not_other_type(): + items = [1,2,'Hi'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes('Joe')}")(result, items) + assert result[0] == False + +def test_includes_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.includes()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: includes: At least 1 argument required, but only 0 passed") + +#sort +def test_sort_empty(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [] + +def test_sort_one(): + items = [5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [5] + +def test_sort_numbers(): + items = [4,2,6,7] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [2,4,6,7] + +def test_sort_strings(): + items = ['Four', 'Three', 'One'] + pm.eval("(arr) => {arr.sort()}")(items) + assert items == ['Four', 'One', 'Three'] + +def test_sort_with_two_args_keyfunc(): + items = ['Four', 'Three', 'One'] + def myFunc(e, f): + return len(e) - len(f) + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert items == ['One', 'Four', 'Three'] + +def test_sort_with_two_args_keyfunc_wrong_return_type(): + items = ['Four', 'Three', 'One'] + def myFunc(e,f): + return e + f + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "incorrect compare function return type" + +def test_sort_with_two_args_keyfunc_wrong_data_type(): + items = [4,2,6,7] + def myFunc(e,f): + return len(e) - len(f) + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "object of type 'float' has no len()" + +def test_sort_with_one_arg_keyfunc(): + items = ['Four', 'Three', 'One'] + def myFunc(e): + return len(e) + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("invalid Array.prototype.sort argument") + +def test_sort_with_builtin_keyfunc(): + items = ['Four', 'Three', 'One'] + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, len) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("invalid Array.prototype.sort argument") + +def test_sort_with_js_func(): + items = ['Four', 'Three', 'One'] + result = [None] + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + pm.eval("(result, arr, compareFun) => {result[0] = arr.sort(compareFun)}")(result, items, myFunc) + assert result[0] is items + assert items == ['Four', 'One', 'Three'] + +def test_sort_numbers_tricky(): + items = [1, 30, 4, 21, 100000] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [1, 100000, 21, 30, 4] + +def test_sort_with_js_func_wrong_data_type(): + items = [4,2,6,7] + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: a.toLocaleUpperCase is not a function") + +#forEach +def test_forEach(): + items = ['Four', 'Three', 'One'] + result = [''] + returnResult = [0] + pm.eval("(returnResult, result, arr) => {returnResult[0] = arr.forEach((element) => result[0] += element)}")(returnResult, result, items) + assert items == ['Four', 'Three', 'One'] + assert result == ['FourThreeOne'] + assert returnResult == [None] + +def test_forEach_check_index(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.forEach((element, index) => result[0] += index)}")(result, items) + assert result == ['012'] + +def test_forEach_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.forEach((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_forEach_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.forEach(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + +def test_forEach_check_this_arg_wrong_type(): + items = ['Four', 'Three', 'One'] + result = [None] + a = 9 + try: + pm.eval("(result, arr, a) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, a);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items, a) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: 'this' argument is not an object or null") + +# TODO python function support + +# this one does not get the result into items +#def test_forEach_with_python_function(): +# def func(element, index, array): +# return "to each his own" +# items = ['Four', 'Three', 'One'] +# returnResult = [0] +# pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.forEach(func)}")(returnResult, items, func) +# assert items == ['to each his own', 'to each his own', 'to each his own'] +# assert returnResult == [None] + +#def test_forEach_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.forEach(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + + +# TODO should not pass +def test_forEach_check_this_arg_null(): + items = ['Four', 'Three', 'One'] + result = [None] + try: + pm.eval("(result, arr) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, null);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: this is null") + +def test_forEach_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.forEach()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: forEach: At least 1 argument required, but only 0 passed") + +#map +def test_map(): + items = [4,2,6,7] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.map((x) => x * x)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == [16,4,36,49] + +def test_map_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.map((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_map_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.map((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_map_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.map(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + +def test_map_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.map()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: map: At least 1 argument required, but only 0 passed") + +def test_map_arg_wrong_type(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.map(8)}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: map: callback is not a function") + +def test_map_check_array_mutation(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.map((element, index, array) => {array[0] = 'Ten'; result[0] = array})}")(result, items) + assert result[0] == ['Ten', 'Three', 'One'] + assert items == ['Ten', 'Three', 'One'] + +#def test_map_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.map(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + +#filter +def test_filter(): + words = ['spray', 'elite', 'exuberant', 'destruction', 'present'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.filter((word) => word.length > 6)}")(result, words) + assert words == ['spray', 'elite', 'exuberant', 'destruction', 'present'] + assert result[0] == ['exuberant', 'destruction', 'present'] + +def test_filter_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.filter((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_filter_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.filter((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_filter_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.filter(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + +def test_filter_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.filter()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") + +#def test_filter_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.filter(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + +#reduce +def test_reduce(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0)}")(result, items) + assert items == [1,2,3,4,5] + assert result[0] == 15 + +def test_reduce_empty_array_no_accumulator(): + items = [] + try: + pm.eval("(arr) => {arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: reduce of empty array with no initial value") + +def test_reduce_float(): + items = [1.9, 4.6, 9.3, 16.5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0)}")(result, items) + assert result[0] == 32.3 + +def test_reduce_string(): + items = ['Hi', 'There'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, "")}")(result, items) + assert result[0] == 'HiThere' + +def test_reduce_initial_value_not_zero(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 5)}")(result, items) + assert items == [1,2,3,4,5] + assert result[0] == 20 + +def test_reduce_no_initial_value(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(result, items) + assert items == [1,2,3,4,5] + assert result[0] == 15 + +def test_reduce_length_one_with_initial_value(): + items = [1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 2)}")(result, items) + assert result[0] == 3 + +def test_reduce_length_one_no_initial_value(): + items = [1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(result, items) + assert result[0] == 1 + +def test_reduce_list_meaningless(): + items = [['Hi', 'There']] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((x) => x * 2)}")(result, items) + assert result[0] == ['Hi', 'There'] + +#reduceRight +def test_reduceRight_list_concat(): + items = [[0, 1],[2, 3],[4, 5]] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue))}")(result, items) + assert result[0] == [4, 5, 2, 3, 0, 1] + +def test_reduceRight_list_concat_with_initial_value(): + items = [[0, 1],[2, 3],[4, 5]] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue), [7,8])}")(result, items) + assert result[0] == [7, 8, 4, 5, 2, 3, 0, 1] + +def test_reduceRight(): + items = [0,1,2,3,4] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) + assert result[0] == 10 + +def test_reduceRight_with_initial_value(): + items = [0,1,2,3,4] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue, 5)}")(result, items) + assert result[0] == 15 + +def test_reduceRight_float(): + items = [1.9, 4.6, 9.3, 16.5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) + assert result[0] == 32.3 + +def test_reduceRight_check_index(): + items = [1.9, 4.6, 9.3, 16.5] + result = [''] + pm.eval("(result, arr) => {arr.reduceRight((accumulator, currentValue, index, array) => {accumulator + currentValue; result[0] += index})}")(result, items) + assert result[0] == '210' + +def test_reduceRight_check_array(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval("(result, arr) => {arr.reduceRight((accumulator, currentValue, index, array) => {accumulator + currentValue; result[0] = array})}")(result, items) + assert result == [items] + assert result[0] is items + +#some +def test_some_true(): + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) + assert items == [1, 2, 3, 4, 5] + assert result[0] == True + +def test_some_false(): + items = [1,3,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) + assert result[0] == False + +def test_some_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.some((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_some_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.some((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_some_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.some(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + +def test_some_truthy_conversion(): + result = [None] + pm.eval( + """ + (result) => { + const TRUTHY_VALUES = [true, "true", 1, {}]; + function getBoolean(value) { + if (typeof value === "string") { + value = value.toLowerCase().trim(); + } + return TRUTHY_VALUES.some((t) => t === value); + } + result[0] = getBoolean(1); + } + """)(result) + assert result[0] == True + +#def test_some_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.some(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + +#every +def test_every_true(): + items = [2,4,6] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) + assert items == [2,4,6] + assert result[0] == True + +def test_every_false(): + items = [1,2,4,6] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) + assert result[0] == False + +def test_every_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.every((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0' + +def test_every_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.every((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_every_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.every(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [1] + +#def test_every_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.every(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + +#find +def test_find_found_once(): + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 100)}")(result, items) + assert items == [5, 12, 8, 130, 44] + assert result[0] == 130 + +def test_find_found_twice(): + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 10)}")(result, items) + assert result[0] == 12 + +def test_find_not_found(): + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 1000)}")(result, items) + assert result[0] is None + +def test_find_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.find((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_find_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.find((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_find_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.find(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + +#def test_find_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.find(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + +#findIndex +def test_findIndex_found_once(): + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 100)}")(result, items) + assert items == [5, 12, 8, 130, 44] + assert result[0] == 3 + +def test_findIndex_found_twice(): + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 10)}")(result, items) + assert result[0] == 1 + +def test_findIndex_not_found(): + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 1000)}")(result, items) + assert result[0] == -1 + +def test_findIndex_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.findIndex((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_findIndex_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.findIndex((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_findIndex_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.findIndex(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + +#def test_findIndex_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.findIndex(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + +#flat +def test_flat(): + items = [0, 1, 2, [3, 4]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat()}")(result, items) + assert items == [0, 1, 2, [3, 4]] + assert result[0] == [0, 1, 2, 3, 4] + +def test_flat_with_js_array(): + items = [0, 1, 2, [3, 4]] + result = [0] + pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flat()}")(result, items) + assert items == [0, [10, 11], 2, [3, 4]] + assert result[0] == [0, 10, 11, 2, 3, 4] + +def test_flat_depth_zero(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(0)}")(result, items) + assert result[0] == [0, 1, [2, [3, [4, 5]]]] + +def test_flat_depth_one(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(1)}")(result, items) + assert items == [0, 1, [2, [3, [4, 5]]]] + assert result[0] == [0, 1, 2, [3, [4, 5]]] + +def test_flat_depth_two(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(2)}")(result, items) + assert items == [0, 1, [2, [3, [4, 5]]]] + assert result[0] == [0, 1, 2, 3, [4, 5]] + +def test_flat_depth_large(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(10)}")(result, items) + assert result[0] == [0, 1, 2, 3, 4, 5] + +#flatMap +def test_flatMap(): + items = [1, 2, 1] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1,2,1] + assert result[0] == [1,2,2,1] + +def test_flatMap_with_js_array(): + items = [1,2,2,1] + result = [0] + pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1, [10, 11], 2, 1] + assert result[0] == [1, 1, 2, 2, 1] + +def test_flatMap_no_replace(): + items = [1,2,[4,5]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1, 2, [4, 5]] + assert result[0] == [1, 2, 2, 1] + +def test_flatMap_no_replace_depth_one(): + items = [1,2,[4,5]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, [2, 2]] : 1))}")(result, items) + assert items == [1, 2, [4, 5]] + assert result[0] == [1, 2, [2, 2], 1] + +def test_flatMap_equivalence(): + items = [1, 2, 1] + result = [0] + result2 = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + pm.eval("(result, arr) => {result[0] = arr.map((num) => (num === 2 ? [2, 2] : 1)).flat()}")(result2, items) + assert result[0] == result2[0] + +def test_flatMap_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.flatMap((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_flatMap_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.flatMap((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_flatMap_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.flatMap(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + +#def test_flatMap_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.flatMap(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + +#valueOf +def test_valueOf(): + items = [1, 2, 1] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.valueOf()}")(result, items) + assert items == [1,2,1] + assert result[0] is items + +#toLocaleString +def test_toLocaleString(): + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) + assert result[0] == '¥7,¥500,¥8,123,¥12' + +def test_toLocaleString_with_none(): + prices = ["¥7", 500, 8123, None] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) + assert result[0] == '¥7,¥500,¥8,123,' + +def test_toLocaleString_with_null(): + prices = ["¥7", 500, 8123, pm.null] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) + assert result[0] == '¥7,¥500,¥8,123,' + +def test_toLocaleString_no_args(): + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString()}")(result, prices) + assert result[0] == '¥7,500,8,123,12' + +def test_toLocaleString_one_arg_(): + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP')}")(result, prices) + assert result[0] == '¥7,500,8,123,12' + +def test_toLocaleString_one_arg_invalid_locale(): + prices = ["¥7", 500, 8123, 12] + result = [None] + try: + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('asfasfsafsdf')}")(result, prices) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("RangeError: invalid language tag:") + +def test_toLocaleString_two_args_invalid_currency(): + prices = ["¥7", 500, 8123, 12] + result = [None] + try: + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPYsdagasfgas' })}")(result, prices) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("RangeError: invalid currency code in NumberFormat():") + +def test_toLocaleString_with_datetime(): + prices = [500, datetime(year=2020, month=1, day=31, hour=13, minute=14, second=31)] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('en-uk')}")(result, prices) + assert result[0] == '500,31/01/2020, 13:14:31' + +#entries +def test_entries_next(): + items = ['a', 'b', 'c'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0] = result[0].next().value}")(result, items) + assert items == ['a', 'b', 'c'] + assert result[0] == [0, 'a'] + +def test_entries_next_next(): + items = ['a', 'b', 'c'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) + assert result[0] == [1, 'b'] + +def test_entries_next_next_undefined(): + items = ['a'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) + assert result[0] == None + +#keys +def test_keys_iterator(): + items = ['a', 'b', 'c'] + result = [7,8,9] + pm.eval("(result, arr) => { index = 0; iterator = arr.keys(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) + assert result == [0,1,2] + +#values +def test_values_iterator(): + items = ['a', 'b', 'c'] + result = [7,8,9] + pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const value of iterator) { result[index] = value; index++;} }")(result, items) + assert result == ['a', 'b', 'c'] + assert result is not items + +#constructor property +def test_constructor_creates_array(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr.constructor; result[0] = new result[0]; result[0][0] = 9}")(result, items) + assert result[0] == [9] + +#length property +def test_constructor_creates_array(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr.length}")(result, items) + assert result[0] == 2 + +#iterator symbol property +def test_iterator_type_function(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = typeof arr[Symbol.iterator]}")(result, items) + assert result[0] == 'function' + +def test_iterator_first_next(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr[Symbol.iterator]().next()}")(result, items) + assert result[0].value == 1 + assert result[0].done == False + +def test_iterator_second_next(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); result[0] = iterator.next()}")(result, items) + assert result[0].value == 2 + assert result[0].done == False + +def test_iterator_last_next(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); iterator.next(); result[0] = iterator.next()}")(result, items) + assert result[0].value == None + assert result[0].done == True \ No newline at end of file diff --git a/tests/python/test_dicts.py b/tests/python/test_dicts.py index 5e8bd4d2..1be75e61 100644 --- a/tests/python/test_dicts.py +++ b/tests/python/test_dicts.py @@ -133,9 +133,11 @@ def test_eval_objects_jsproxy_inplace_or_true_dict_left(): assert b == {'d': 6.0} assert True == pm.eval("(o) => Object.preventExtensions(o) === o")({ "abc": 1 }) -def test_eval_objects_proxy_proto(): - assert pm.null == pm.eval("(o) => Object.getPrototypeOf(o)")({}) - assert pm.null == pm.eval("(o) => Object.getPrototypeOf(o)")({ "abc": 1 }) +def test_instanceof_object(): + a = {'c':5.0} + result = [None] + pm.eval("(result, obj) => {result[0] = obj instanceof Object}")(result, a) + assert result[0] == True def test_eval_objects_proxy_iterate(): obj = pm.eval("({ a: 123, b: 'test' })") @@ -252,4 +254,4 @@ def test_eval_objects_jsproxy_inplace_or_true_dict_left(): b = pm.eval("({'d':6})") a |= b assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} \ No newline at end of file + assert b == {'d': 6.0} \ No newline at end of file diff --git a/tests/python/test_list_array.py b/tests/python/test_list_array.py index 4def6276..476c3adf 100644 --- a/tests/python/test_list_array.py +++ b/tests/python/test_list_array.py @@ -8,3 +8,32 @@ def test_eval_array_is_list(): def test_eval_array_is_list_type_string(): pythonListTypeString = str(type(pm.eval('[]'))) assert pythonListTypeString == "" + +def test_eval_list_is_array(): + items = [1, 2, 3] + isArray = pm.eval('Array.isArray')(items) + assert isArray == True + +def test_typeof_array(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = typeof arr}")(result, items) + assert result[0] == 'object' + +def test_instanceof_array(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof Array}")(result, items) + assert result[0] == True + +def test_instanceof_object(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof Object}")(result, items) + assert result[0] == True + +def test_not_instanceof_string(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof String}")(result, items) + assert result[0] == False \ No newline at end of file diff --git a/tests/python/test_lists.py b/tests/python/test_lists.py index 8dcf2b03..0942e8b2 100644 --- a/tests/python/test_lists.py +++ b/tests/python/test_lists.py @@ -729,7 +729,7 @@ def myFunc(e): assert str(type(e)) == "" assert str(e) == "object of type 'float' has no len()" -def test_sort_with_function_and_reverse_false(): +def test_sort_with_function_two_args_and_reverse_false(): def myFunc(e,f): return len(e) - len(f) a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])")