diff --git a/src/core/hiwire.c b/src/core/hiwire.c index 226d9960c..7c855da2b 100644 --- a/src/core/hiwire.c +++ b/src/core/hiwire.c @@ -19,11 +19,15 @@ const JsRef Js_null = ((JsRef)(8)); const JsRef Js_novalue = ((JsRef)(1000)); JsRef -hiwire_bool(bool boolean) +hiwire_from_bool(bool boolean) { return boolean ? Js_true : Js_false; } +EM_JS(bool, hiwire_to_bool, (JsRef val), { + return !!Module.hiwire.get_value(val); +}); + EM_JS_NUM(int, hiwire_init, (), { let _hiwire = { objects : new Map(), @@ -49,6 +53,7 @@ EM_JS_NUM(int, hiwire_init, (), { _hiwire.objects.set(Module.hiwire.JSNULL, null); _hiwire.objects.set(Module.hiwire.TRUE, true); _hiwire.objects.set(Module.hiwire.FALSE, false); + let hiwire_next_permanent = Module.hiwire.FALSE + 2; #ifdef DEBUG_F Module.hiwire._hiwire = _hiwire; @@ -83,7 +88,20 @@ EM_JS_NUM(int, hiwire_init, (), { return idval; }; - Module.hiwire.num_keys = function() { return _hiwire.objects.size; }; + Module.hiwire.intern_object = function(obj) + { + let id = hiwire_next_permanent; + hiwire_next_permanent += 2; + _hiwire.objects.set(id, obj); + return id; + }; + + // for testing purposes. + Module.hiwire.num_keys = function(){ + // clang-format off + return Array.from(_hiwire.objects.keys()).filter((x) => x % 2).length + // clang-format on + }; Module.hiwire.get_value = function(idval) { @@ -216,6 +234,20 @@ EM_JS_NUM(int, hiwire_init, (), { return 0; }); +EM_JS_REF(JsRef, JsString_InternFromCString, (const char* str), { + let jsstring = UTF8ToString(str); + return Module.hiwire.intern_object(jsstring); +}) + +JsRef +JsString_FromId(Js_Identifier* id) +{ + if (!id->object) { + id->object = JsString_InternFromCString(id->string); + } + return id->object; +} + EM_JS_REF(JsRef, hiwire_incref, (JsRef idval), { // clang-format off if ((idval & 1) === 0) { @@ -428,44 +460,121 @@ hiwire_call_va(JsRef idobj, ...) return idresult; } -EM_JS_REF(JsRef, - hiwire_call_bound, - (JsRef idfunc, JsRef idthis, JsRef idargs), - { - let func = Module.hiwire.get_value(idfunc); - let this_; - // clang-format off - if (idthis === 0) { - // clang-format on - this_ = null; - } else { - this_ = Module.hiwire.get_value(idthis); - } - let args = Module.hiwire.get_value(idargs); - return Module.hiwire.new_value(func.apply(this_, args)); - }); +EM_JS_REF(JsRef, hiwire_call_OneArg, (JsRef idfunc, JsRef idarg), { + let jsfunc = Module.hiwire.get_value(idfunc); + let jsarg = Module.hiwire.get_value(idarg); + return Module.hiwire.new_value(jsfunc(jsarg)); +}); +// clang-format off EM_JS_REF(JsRef, - hiwire_call_member, - (JsRef idobj, const char* ptrname, JsRef idargs), - { - let jsobj = Module.hiwire.get_value(idobj); - let jsname = UTF8ToString(ptrname); - let jsargs = Module.hiwire.get_value(idargs); - return Module.hiwire.new_value(jsobj[jsname](... jsargs)); - }); +hiwire_call_bound, +(JsRef idfunc, JsRef idthis, JsRef idargs), +{ + let func = Module.hiwire.get_value(idfunc); + let this_; + if (idthis === 0) { + this_ = null; + } else { + this_ = Module.hiwire.get_value(idthis); + } + let args = Module.hiwire.get_value(idargs); + return Module.hiwire.new_value(func.apply(this_, args)); +}); +// clang-format on + +EM_JS_NUM(int, hiwire_HasMethod, (JsRef obj_id, JsRef name), { + // clang-format off + let obj = Module.hiwire.get_value(obj_id); + return obj && typeof obj[Module.hiwire.get_value(name)] === "function"; + // clang-format on +}) + +int +hiwire_HasMethodId(JsRef obj, Js_Identifier* name) +{ + JsRef name_ref = JsString_FromId(name); + if (name_ref == NULL) { + return -1; + } + return hiwire_HasMethod(obj, name_ref); +} + +// clang-format off +EM_JS_REF( +JsRef, +hiwire_CallMethodString, +(JsRef idobj, const char* name, JsRef idargs), +{ + let jsobj = Module.hiwire.get_value(idobj); + let jsname = UTF8ToString(name); + let jsargs = Module.hiwire.get_value(idargs); + return Module.hiwire.new_value(jsobj[jsname](...jsargs)); +}); +// clang-format on + +EM_JS_REF(JsRef, hiwire_CallMethod, (JsRef idobj, JsRef name, JsRef idargs), { + let jsobj = Module.hiwire.get_value(idobj); + let jsname = Module.hiwire.get_value(name); + let jsargs = Module.hiwire.get_value(idargs); + return Module.hiwire.new_value(jsobj[jsname](... jsargs)); +}); + +// clang-format off +EM_JS_REF( +JsRef, +hiwire_CallMethod_OneArg, +(JsRef idobj, JsRef name, JsRef idarg), +{ + let jsobj = Module.hiwire.get_value(idobj); + let jsname = Module.hiwire.get_value(name); + let jsarg = Module.hiwire.get_value(idarg); + return Module.hiwire.new_value(jsobj[jsname](jsarg)); +}); +// clang-format on JsRef -hiwire_call_member_va(JsRef idobj, const char* ptrname, ...) +hiwire_CallMethodId(JsRef idobj, Js_Identifier* name_id, JsRef idargs) +{ + JsRef name_ref = JsString_FromId(name_id); + if (name_ref == NULL) { + return NULL; + } + return hiwire_CallMethod(idobj, name_ref, idargs); +} + +JsRef +hiwire_CallMethodString_va(JsRef idobj, const char* ptrname, ...) { va_list args; va_start(args, ptrname); JsRef idargs = convert_va_args(args); - JsRef idresult = hiwire_call_member(idobj, ptrname, idargs); + JsRef idresult = hiwire_CallMethodString(idobj, ptrname, idargs); hiwire_decref(idargs); return idresult; } +JsRef +hiwire_CallMethodId_va(JsRef idobj, Js_Identifier* name, ...) +{ + va_list args; + va_start(args, name); + JsRef idargs = convert_va_args(args); + JsRef idresult = hiwire_CallMethodId(idobj, name, idargs); + hiwire_decref(idargs); + return idresult; +} + +JsRef +hiwire_CallMethodId_OneArg(JsRef obj, Js_Identifier* name, JsRef arg) +{ + JsRef name_ref = JsString_FromId(name); + if (name_ref == NULL) { + return NULL; + } + return hiwire_CallMethod_OneArg(obj, name_ref, arg); +} + EM_JS_REF(JsRef, hiwire_construct, (JsRef idobj, JsRef idargs), { let jsobj = Module.hiwire.get_value(idobj); let jsargs = Module.hiwire.get_value(idargs); @@ -510,84 +619,6 @@ EM_JS_NUM(bool, hiwire_get_bool, (JsRef idobj), { // clang-format on }); -EM_JS_NUM(bool, hiwire_has_has_method, (JsRef idobj), { - // clang-format off - let obj = Module.hiwire.get_value(idobj); - return obj && typeof obj.has === "function"; - // clang-format on -}); - -EM_JS_NUM(bool, hiwire_call_has_method, (JsRef idobj, JsRef idkey), { - // clang-format off - let obj = Module.hiwire.get_value(idobj); - let key = Module.hiwire.get_value(idkey); - return obj.has(key); - // clang-format on -}); - -EM_JS_NUM(bool, hiwire_has_includes_method, (JsRef idobj), { - // clang-format off - let obj = Module.hiwire.get_value(idobj); - return obj && typeof obj.includes === "function"; - // clang-format on -}); - -EM_JS_NUM(bool, hiwire_call_includes_method, (JsRef idobj, JsRef idval), { - let obj = Module.hiwire.get_value(idobj); - let val = Module.hiwire.get_value(idval); - return obj.includes(val); -}); - -EM_JS_NUM(bool, hiwire_has_get_method, (JsRef idobj), { - // clang-format off - let obj = Module.hiwire.get_value(idobj); - return obj && typeof obj.get === "function"; - // clang-format on -}); - -EM_JS_REF(JsRef, hiwire_call_get_method, (JsRef idobj, JsRef idkey), { - let obj = Module.hiwire.get_value(idobj); - let key = Module.hiwire.get_value(idkey); - let result = obj.get(key); - // clang-format off - if (result === undefined) { - // Try to distinguish between undefined and missing: - // If the object has a "has" method and it returns false for this key, the - // key is missing. Otherwise, assume key present and value was undefined. - // TODO: in absence of a "has" method, should we return None or KeyError? - if (obj.has && typeof obj.has === "function" && !obj.has(key)) { - return ERROR_REF; - } - } - // clang-format on - return Module.hiwire.new_value(result); -}); - -EM_JS_NUM(bool, hiwire_has_set_method, (JsRef idobj), { - // clang-format off - let obj = Module.hiwire.get_value(idobj); - return obj && typeof obj.set === "function"; - // clang-format on -}); - -EM_JS_NUM(errcode, - hiwire_call_set_method, - (JsRef idobj, JsRef idkey, JsRef idval), - { - let obj = Module.hiwire.get_value(idobj); - let key = Module.hiwire.get_value(idkey); - let val = Module.hiwire.get_value(idval); - let result = obj.set(key, val); - }); - -EM_JS_NUM(errcode, hiwire_call_delete_method, (JsRef idobj, JsRef idkey), { - let obj = Module.hiwire.get_value(idobj); - let key = Module.hiwire.get_value(idkey); - if (!obj.delete(key)) { - return -1; - } -}); - EM_JS_NUM(bool, hiwire_is_pyproxy, (JsRef idobj), { return Module.isPyProxy(Module.hiwire.get_value(idobj)); }); diff --git a/src/core/hiwire.h b/src/core/hiwire.h index 945e68018..cb3797469 100644 --- a/src/core/hiwire.h +++ b/src/core/hiwire.h @@ -47,6 +47,25 @@ extern const JsRef Js_null; // For when the return value would be Option extern const JsRef Js_novalue; +// A mechanism for handling static Javascript strings from C +// This is copied from the Python mechanism for handling static Python strings +// from C See the Python definition here: +// https://github.com/python/cpython/blob/24da544014f78e6f1440d5ce5c2d14794a020340/Include/cpython/object.h#L37 + +typedef struct Js_Identifier +{ + const char* string; + JsRef object; +} Js_Identifier; + +#define Js_static_string_init(value) \ + { \ + .string = value, .object = NULL \ + } +#define Js_static_string(varname, value) \ + static Js_Identifier varname = Js_static_string_init(value) +#define Js_IDENTIFIER(varname) Js_static_string(JsId_##varname, #varname) + #define hiwire_CLEAR(x) \ do { \ hiwire_decref(x); \ @@ -150,7 +169,13 @@ hiwire_string_ascii(const char* ptr); * Returns: "New" reference */ JsRef -hiwire_bool(bool boolean); +hiwire_from_bool(bool boolean); + +/** + * Convert value to C boolean + */ +bool +hiwire_to_bool(JsRef value); bool JsArray_Check(JsRef idobj); @@ -230,7 +255,7 @@ JsRef JsObject_Dir(JsRef idobj); /** - * Call a function + * Call a js function * * idargs is a hiwire Array containing the arguments. * @@ -238,6 +263,12 @@ JsObject_Dir(JsRef idobj); JsRef hiwire_call(JsRef idobj, JsRef idargs); +/** + * Call a js function with one argument + */ +JsRef +hiwire_call_OneArg(JsRef idobj, JsRef idarg); + /** * Call a function * @@ -251,28 +282,61 @@ hiwire_call_va(JsRef idobj, ...); JsRef hiwire_call_bound(JsRef idfunc, JsRef idthis, JsRef idargs); -/** - * Call a member function. - * - * ptrname is the member name, as a null-terminated UTF8. - * - * idargs is a hiwire Array containing the arguments. - * - */ -JsRef -hiwire_call_member(JsRef idobj, const char* ptrname, JsRef idargs); +int +hiwire_HasMethod(JsRef obj, JsRef name); + +int +hiwire_HasMethodId(JsRef obj, Js_Identifier* name); /** - * Call a member function. - * - * ptrname is the member name, as a null-terminated UTF8. - * - * Arguments are specified as a NULL-terminated variable arguments list of - * JsRefs. + * name is the method name, as null-terminated UTF8. + * args is an Array containing the arguments. * */ JsRef -hiwire_call_member_va(JsRef idobj, const char* ptrname, ...); +hiwire_CallMethodString(JsRef obj, const char* name, JsRef args); + +/** + * name is the method name, as null-terminated UTF8. + * arg is the argument + */ +JsRef +hiwire_CallMethodString_OneArg(JsRef obj, const char* name, JsRef arg); + +/** + * name is the method name, as null-terminated UTF8. + * Arguments are specified as a NULL-terminated variable arguments list of + * JsRefs. + */ +JsRef +hiwire_CallMethodString_va(JsRef obj, const char* name, ...); + +JsRef +hiwire_CallMethod(JsRef obj, JsRef name, JsRef args); + +JsRef +hiwire_CallMethod_OneArg(JsRef obj, JsRef name, JsRef arg); + +JsRef +hiwire_CallMethod_va(JsRef obj, JsRef name, ...); + +/** + * name is the method name, as a Js_Identifier + * args is a hiwire Array containing the arguments. + */ +JsRef +hiwire_CallMethodId(JsRef obj, Js_Identifier* name, JsRef args); + +/** + * name is the method name, as a Js_Identifier + * Arguments are specified as a NULL-terminated variable arguments list of + * JsRefs. + */ +JsRef +hiwire_CallMethodId_va(JsRef obj, Js_Identifier* name, ...); + +JsRef +hiwire_CallMethodId_OneArg(JsRef obj, Js_Identifier* name, JsRef arg); /** * Calls the constructor of a class object. @@ -306,68 +370,6 @@ hiwire_get_length(JsRef idobj); bool hiwire_get_bool(JsRef idobj); -/** - * Check whether `typeof obj.has === "function"` - */ -bool -hiwire_has_has_method(JsRef idobj); - -/** - * Does `obj.has(val)`. Doesn't check type of return value, if it isn't a - * boolean or an integer it will get coerced to false. - */ -bool -hiwire_call_has_method(JsRef idobj, JsRef idval); - -/** - * Check whether `typeof obj.includes === "function"`. - */ -bool -hiwire_has_includes_method(JsRef idobj); - -/** - * Does `obj.includes(val)`. Doesn't check type of return value, if it isn't a - * boolean or an integer it will get coerced to `false`. - */ -bool -hiwire_call_includes_method(JsRef idobj, JsRef idval); - -/** - * Check whether `typeof obj.get === "function"`. - */ -bool -hiwire_has_get_method(JsRef idobj); - -/** - * Call `obj.get(key)`. If the result is `undefined`, we check for a `has` - * method and if one is present call `obj.has(key)`. If this returns false we - * return `NULL` to signal a `KeyError` otherwise we return `Js_Undefined`. If - * no `has` method is present, we return `Js_Undefined`. - */ -JsRef -hiwire_call_get_method(JsRef idobj, JsRef idkey); - -/** - * Check whether `typeof obj.set === "function"`. - */ -bool -hiwire_has_set_method(JsRef idobj); - -/** - * Call `obj.set(key, value)`. Javascript standard is that `set` returns `false` - * to indicate an error condition, but we ignore the return value. - */ -errcode -hiwire_call_set_method(JsRef idobj, JsRef idkey, JsRef idval); - -/** - * Call `obj.delete(key)`. Javascript standard is that `delete` returns `false` - * to indicate an error condition, if `false` is returned we return `-1` to - * indicate the error. - */ -errcode -hiwire_call_delete_method(JsRef idobj, JsRef idkey); - /** * Check whether the object is a PyProxy. */ diff --git a/src/core/jsproxy.c b/src/core/jsproxy.c index 212c844fb..f1c263528 100644 --- a/src/core/jsproxy.c +++ b/src/core/jsproxy.c @@ -60,6 +60,13 @@ _Py_IDENTIFIER(set_exception); _Py_IDENTIFIER(set_result); _Py_IDENTIFIER(__await__); _Py_IDENTIFIER(__dir__); +Js_IDENTIFIER(then); +Js_IDENTIFIER(finally); +Js_IDENTIFIER(has); +Js_IDENTIFIER(get); +Js_IDENTIFIER(set); +Js_IDENTIFIER(delete); +Js_IDENTIFIER(includes); static PyObject* asyncio_get_event_loop; static PyTypeObject* PyExc_BaseException_Type; @@ -479,6 +486,25 @@ finally: return success ? 0 : -1; } +// A helper method for jsproxy_subscript. +EM_JS_REF(JsRef, JsProxy_subscript_js, (JsRef idobj, JsRef idkey), { + let obj = Module.hiwire.get_value(idobj); + let key = Module.hiwire.get_value(idkey); + let result = obj.get(key); + // clang-format off + if (result === undefined) { + // Try to distinguish between undefined and missing: + // If the object has a "has" method and it returns false for this key, the + // key is missing. Otherwise, assume key present and value was undefined. + // TODO: in absence of a "has" method, should we return None or KeyError? + if (obj.has && typeof obj.has === "function" && !obj.has(key)) { + return 0; + } + } + // clang-format on + return Module.hiwire.new_value(result); +}); + /** * __getitem__ for JsProxies that have a "get" method. Translates proxy[key] to * obj.get(key). Controlled by HAS_GET @@ -493,7 +519,7 @@ JsProxy_subscript(PyObject* o, PyObject* pyidx) ididx = python2js(pyidx); FAIL_IF_NULL(ididx); - idresult = hiwire_call_get_method(self->js, ididx); + idresult = JsProxy_subscript_js(self->js, ididx); if (idresult == NULL) { if (!PyErr_Occurred()) { PyErr_SetObject(PyExc_KeyError, pyidx); @@ -522,9 +548,12 @@ JsProxy_ass_subscript(PyObject* o, PyObject* pyidx, PyObject* pyvalue) bool success = false; JsRef ididx = NULL; JsRef idvalue = NULL; + JsRef jsresult = NULL; ididx = python2js(pyidx); if (pyvalue == NULL) { - if (hiwire_call_delete_method(self->js, ididx)) { + jsresult = hiwire_CallMethodId_OneArg(self->js, &JsId_delete, ididx); + FAIL_IF_NULL(jsresult); + if (!hiwire_to_bool(jsresult)) { if (!PyErr_Occurred()) { PyErr_SetObject(PyExc_KeyError, pyidx); } @@ -533,12 +562,15 @@ JsProxy_ass_subscript(PyObject* o, PyObject* pyidx, PyObject* pyvalue) } else { idvalue = python2js(pyvalue); FAIL_IF_NULL(idvalue); - FAIL_IF_MINUS_ONE(hiwire_call_set_method(self->js, ididx, idvalue)); + jsresult = + hiwire_CallMethodId_va(self->js, &JsId_set, ididx, idvalue, NULL); + FAIL_IF_NULL(jsresult); } success = true; finally: hiwire_CLEAR(ididx); hiwire_CLEAR(idvalue); + hiwire_CLEAR(jsresult); return success ? 0 : -1; } @@ -551,13 +583,17 @@ finally: static int JsProxy_includes(JsProxy* self, PyObject* obj) { + JsRef jsresult = NULL; int result = -1; JsRef jsobj = python2js(obj); FAIL_IF_NULL(jsobj); - result = hiwire_call_includes_method(self->js, jsobj); + jsresult = hiwire_CallMethodId_OneArg(self->js, &JsId_includes, jsobj); + FAIL_IF_NULL(jsresult); + result = hiwire_to_bool(jsresult); finally: hiwire_CLEAR(jsobj); + hiwire_CLEAR(jsresult); return result; } @@ -569,13 +605,17 @@ finally: static int JsProxy_has(JsProxy* self, PyObject* obj) { + JsRef jsresult = NULL; int result = -1; JsRef jsobj = python2js(obj); FAIL_IF_NULL(jsobj); - result = hiwire_call_has_method(self->js, jsobj); + jsresult = hiwire_CallMethodId_OneArg(self->js, &JsId_has, jsobj); + FAIL_IF_NULL(jsresult); + result = hiwire_to_bool(jsresult); finally: hiwire_CLEAR(jsobj); + hiwire_CLEAR(jsresult); return result; } @@ -727,7 +767,7 @@ JsProxy_Await(JsProxy* self) FAIL_IF_NULL(promise_id); promise_handles = create_promise_handles(set_result, set_exception); FAIL_IF_NULL(promise_handles); - promise_result = hiwire_call_member(promise_id, "then", promise_handles); + promise_result = hiwire_CallMethodId(promise_id, &JsId_then, promise_handles); FAIL_IF_NULL(promise_result); result = _PyObject_CallMethodId(fut, &PyId___await__, NULL); @@ -776,7 +816,7 @@ JsProxy_then(JsProxy* self, PyObject* args, PyObject* kwds) FAIL_IF_NULL(promise_id); promise_handles = create_promise_handles(onfulfilled, onrejected); FAIL_IF_NULL(promise_handles); - result_promise = hiwire_call_member(promise_id, "then", promise_handles); + result_promise = hiwire_CallMethodId(promise_id, &JsId_then, promise_handles); if (result_promise == NULL) { Py_CLEAR(onfulfilled); Py_CLEAR(onrejected); @@ -815,7 +855,7 @@ JsProxy_catch(JsProxy* self, PyObject* onrejected) // even if the promise resolves successfully. promise_handles = create_promise_handles(NULL, onrejected); FAIL_IF_NULL(promise_handles); - result_promise = hiwire_call_member(promise_id, "then", promise_handles); + result_promise = hiwire_CallMethodId(promise_id, &JsId_then, promise_handles); if (result_promise == NULL) { Py_DECREF(onrejected); FAIL(); @@ -856,7 +896,8 @@ JsProxy_finally(JsProxy* self, PyObject* onfinally) // `create_once_callable`. proxy = create_once_callable(onfinally); FAIL_IF_NULL(proxy); - result_promise = hiwire_call_member_va(promise_id, "finally", proxy, NULL); + result_promise = + hiwire_CallMethodId_va(promise_id, &JsId_finally, proxy, NULL); if (result_promise == NULL) { Py_DECREF(onfinally); FAIL(); @@ -1657,16 +1698,16 @@ JsProxy_create_with_this(JsRef object, JsRef this) if (hiwire_has_length(object)) { type_flags |= HAS_LENGTH; } - if (hiwire_has_get_method(object)) { + if (hiwire_HasMethodId(object, &JsId_get)) { type_flags |= HAS_GET; } - if (hiwire_has_set_method(object)) { + if (hiwire_HasMethodId(object, &JsId_set)) { type_flags |= HAS_SET; } - if (hiwire_has_has_method(object)) { + if (hiwire_HasMethodId(object, &JsId_has)) { type_flags |= HAS_HAS; } - if (hiwire_has_includes_method(object)) { + if (hiwire_HasMethodId(object, &JsId_includes)) { type_flags |= HAS_INCLUDES; } if (hiwire_is_typedarray(object)) { diff --git a/src/core/pyproxy.c b/src/core/pyproxy.c index a545aa5d2..13d6bdb7a 100644 --- a/src/core/pyproxy.c +++ b/src/core/pyproxy.c @@ -658,7 +658,7 @@ FutureDoneCallback_call_resolve(FutureDoneCallback* self, PyObject* result) JsRef result_js = NULL; JsRef output = NULL; result_js = python2js(result); - output = hiwire_call_va(self->resolve_handle, result_js, NULL); + output = hiwire_call_OneArg(self->resolve_handle, result_js); hiwire_CLEAR(result_js); hiwire_CLEAR(output); @@ -678,7 +678,7 @@ FutureDoneCallback_call_reject(FutureDoneCallback* self) // wrap_exception looks up the current exception and wraps it in a Js error. excval = wrap_exception(); FAIL_IF_NULL(excval); - result = hiwire_call_va(self->reject_handle, excval, NULL); + result = hiwire_call_OneArg(self->reject_handle, excval); success = true; finally: