MAINT Add Js_IDENTIFIER, new hiwire_CallMethod commands (#1749)

This commit is contained in:
Hood Chatham 2021-08-31 00:02:03 +02:00 committed by GitHub
parent a47cd930cc
commit 5a63152172
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 276 additions and 202 deletions

View File

@ -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));
});

View File

@ -47,6 +47,25 @@ extern const JsRef Js_null;
// For when the return value would be Option<JsRef>
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.
*/

View File

@ -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)) {

View File

@ -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: