Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JsProxy.to_py method #1244

Merged
merged 11 commits into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
301 changes: 259 additions & 42 deletions src/core/js2python.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
#include "jsproxy.h"
#include "pyproxy.h"

// Since we're going *to* Python, just let any Python exceptions at conversion
// bubble out to Python

PyObject*
_js2python_allocate_string(int size, int max_code_point)
{
Expand Down Expand Up @@ -67,15 +64,26 @@ _js2python_memoryview(JsRef id)
return PyMemoryView_FromObject(jsproxy);
}

PyObject*
_js2python_jsproxy(JsRef id)
{
return JsProxy_create(id);
}
EM_JS_REF(PyObject*, js2python, (JsRef id), {
let value = Module.hiwire.get_value(id);
let result = Module.__js2python_convertImmutable(value);
// clang-format off
if (result !== 0) {
return result;
}
if (value['byteLength'] !== undefined) {
return __js2python_memoryview(id);
}
return _JsProxy_create(id);
// clang-format on
})

EM_JS_REF(PyObject*, js2python_convert, (JsRef id, int depth), {
return Module.__js2python_convert(id, new Map(), depth);
});

// TODO: Add some meaningful order
EM_JS_REF(PyObject*, __js2python, (JsRef id), {
function __js2python_string(value)
EM_JS_NUM(errcode, js2python_init, (), {
Module.__js2python_string = function(value)
{
// The general idea here is to allocate a Python string and then
// have Javascript write directly into its buffer. We first need
Expand Down Expand Up @@ -124,39 +132,248 @@ EM_JS_REF(PyObject*, __js2python, (JsRef id), {
}

return result;
}
};

// clang-format off
let value = Module.hiwire.get_value(id);
let type = typeof value;
if (type === 'string') {
return __js2python_string(value);
} else if (type === 'number') {
return __js2python_number(value);
} else if (value === undefined || value === null) {
return __js2python_none();
} else if (value === true) {
return __js2python_true();
} else if (value === false) {
return __js2python_false();
} else if (Module.PyProxy.isPyProxy(value)) {
return __js2python_pyproxy(Module.PyProxy._getPtr(value));
} else if (value['byteLength'] !== undefined) {
return __js2python_memoryview(id);
} else {
return __js2python_jsproxy(id);
Module.__js2python_convertImmutable = function(value)
{
let type = typeof value;
// clang-format off
if (type === 'string') {
return Module.__js2python_string(value);
} else if (type === 'number') {
return __js2python_number(value);
} else if (value === undefined || value === null) {
return __js2python_none();
} else if (value === true) {
return __js2python_true();
} else if (value === false) {
return __js2python_false();
} else if (Module.PyProxy.isPyProxy(value)) {
return __js2python_pyproxy(Module.PyProxy._getPtr(value));
}
// clang-format on
return 0;
};

class TempError extends Error
{};

Module.__js2python_convertList = function(obj, cache, depth)
{
let list = _PyList_New(obj.length);
// clang-format off
if (list === 0) {
// clang-format on
return 0;
}
let entryid = 0;
let item = 0;
try {
cache.set(obj, list);
for (let i = 0; i < obj.length; i++) {
entryid = Module.hiwire.new_value(obj[i]);
item = Module.__js2python_convert(entryid, cache, depth);
// clang-format off
if (item === 0) {
// clang-format on
throw new TempError();
}
// clang-format off
// PyList_SetItem steals a reference to item no matter what
_Py_IncRef(item);
if (_PyList_SetItem(list, i, item) === -1) {
// clang-format on
throw new TempError();
}
Module.hiwire.decref(entryid);
entryid = 0;
_Py_DecRef(item);
item = 0;
}
} catch (e) {
Module.hiwire.decref(entryid);
_Py_DecRef(item);
_Py_DecRef(list);
if (e instanceof TempError) {
return 0;
} else if (_PyErr_Occurred()) {
return 0;
} else {
throw e;
}
}

return list;
};

Module.__js2python_convertMap = function(obj, entries, cache, depth)
{
let dict = _PyDict_New();
// clang-format off
if (dict === 0) {
// clang-format on
return 0;
}
let key_py = 0;
let value_id = 0;
let value_py = 0;
try {
cache.set(obj, dict);
for (let[key_js, value_js] of entries) {
key_py = Module.__js2python_convertImmutable(key_js);
// clang-format off
if (key_py === 0) {
// clang-format on
if (_PyErr_Occurred()) {
throw new TempError();
} else {
let key_type =
(key_js.constructor && key_js.constructor.name) || typeof(key_js);
// clang-format off
throw new Error(`Cannot use key of type ${key_type} as a key to a Python dict`);
// clang-format on
}
}
value_id = Module.hiwire.new_value(value_js);
value_py = Module.__js2python_convert(value_id, cache, depth);
// clang-format off
if (value_py === 0) {
// clang-format on
throw new TempError();
}

// clang-format off
if (_PyDict_SetItem(dict, key_py, value_py) === -1) {
// clang-format on
throw new TempError();
}
_Py_DecRef(key_py);
key_py = 0;
Module.hiwire.decref(value_id);
value_id = 0;
_Py_DecRef(value_py);
value_py = 0;
}
} catch (e) {
_Py_DecRef(key_py);
Module.hiwire.decref(value_id);
_Py_DecRef(value_py);
_Py_DecRef(dict);
if (e instanceof TempError) {
return 0;
} else if (_PyErr_Occurred()) {
return 0;
} else {
throw e;
}
}
return dict;
};

Module.__js2python_convertSet = function(obj, cache, depth)
{
let set = _PySet_New(0);
// clang-format off
if (set === 0) {
// clang-format on
return 0;
}
let key_py = 0;
try {
cache.set(obj, set);
for (let key_js of obj) {
key_py = Module.__js2python_convertImmutable(key_js);
// clang-format off
if (key_py === 0) {
// clang-format on
if (_PyErr_Occurred()) {
throw new TempError();
} else {
let key_type =
(key_js.constructor && key_js.constructor.name) || typeof(key_js);
// clang-format off
throw new Error(`Cannot use key of type ${key_type} as a key to a Python set`);
// clang-format on
}
}
let errcode = _PySet_Add(set, key_py);
// clang-format off
if (errcode === -1) {
// clang-format on
throw new TempError();
}
_Py_DecRef(key_py);
key_py = 0;
}
} catch (e) {
_Py_DecRef(key_py);
_Py_DecRef(set);
if (e instanceof TempError) {
return 0;
} else if (_PyErr_Occurred()) {
return 0;
} else {
throw e;
}
}
return set;
};

function checkBoolIntCollision(obj, ty)
{
if (obj.has(1) && obj.has(true)) {
throw new Error(`Cannot faithfully convert ${
ty } into Python since it ` +
"contains both 1 and true as keys.");
}
if (obj.has(0) && obj.has(false)) {
throw new Error(`Cannot faithfully convert ${
ty } into Python since it ` +
"contains both 0 and false as keys.");
}
}
// clang-format on
});

PyObject*
js2python(JsRef id)
{
return (PyObject*)__js2python(id);
}
Module.__js2python_convertOther = function(id, value, cache, depth)
{
let toStringTag = Object.prototype.toString.call(value);
// clang-format off
if (Array.isArray(value) || value === "[object HTMLCollection]" ||
value === "[object NodeList]") {
return Module.__js2python_convertList(value, cache, depth);
}
if (toStringTag === "[object Map]" || value instanceof Map) {
checkBoolIntCollision(value, "Map");
return Module.__js2python_convertMap(value, value.entries(), cache, depth);
}
if (toStringTag === "[object Set]" || value instanceof Set) {
checkBoolIntCollision(value, "Set");
return Module.__js2python_convertSet(value, cache, depth);
}
if (toStringTag === "[object Object]" && (value.constructor === undefined || value.constructor.name === "Object")) {
return Module.__js2python_convertMap(value, Object.entries(value), cache, depth);
}
// clang-format on
return _JsProxy_create(id);
};

Module.__js2python_convert = function(id, cache, depth)
{
let value = Module.hiwire.get_value(id);
let result = Module.__js2python_convertImmutable(value);
// clang-format off
if (result !== 0) {
return result;
}
if (depth === 0) {
return _JsProxy_create(id);
}
result = cache.get(value);
if (result !== undefined) {
return result;
}
// clang-format on
return Module.__js2python_convertOther(id, value, cache, depth - 1);
};

int
js2python_init()
{
return 0;
}
})
3 changes: 3 additions & 0 deletions src/core/js2python.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
PyObject*
js2python(JsRef x);

PyObject*
js2python_convert(JsRef x, int depth);

/** Initialize any global variables used by this module. */
int
js2python_init();
Expand Down
26 changes: 25 additions & 1 deletion src/core/jsproxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,25 @@ JsProxy_Dir(PyObject* self, PyObject* _args)
return result;
}

static PyObject*
JsProxy_toPy(PyObject* self, PyObject* const* args, Py_ssize_t nargs)
{
if (nargs > 1) {
PyErr_Format(
PyExc_TypeError, "to_py expected at most 1 argument, got %zd", nargs);
return NULL;
}
int depth = -1;
if (nargs == 1) {
int overflow;
depth = PyLong_AsLongAndOverflow(args[0], &overflow);
if (overflow == 0 && depth == -1 && PyErr_Occurred()) {
return NULL;
}
}
return js2python_convert(GET_JSREF(self), depth);
}

static int
JsProxy_Bool(PyObject* o)
{
Expand Down Expand Up @@ -382,7 +401,12 @@ static PyMethodDef JsProxy_Methods[] = {
{ "__dir__",
(PyCFunction)JsProxy_Dir,
METH_NOARGS,
"Returns a list of the members and methods on the object." },
PyDoc_STR("Returns a list of the members and methods on the object.") },
{
"to_py",
(PyCFunction)JsProxy_toPy,
METH_FASTCALL,
PyDoc_STR("Convert the JsProxy to a native Python object (as best as possible)")},
{ NULL }
};
// clang-format on
Expand Down