diff --git a/Makefile b/Makefile index cab5c8c6f..f5da5f803 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ all: build/pyodide.asm.html build/pyodide.js build/pyodide.asm.html: src/main.bc src/jsproxy.bc src/js2python.bc src/pylocals.bc \ - src/python2js.bc src/runpython.bc root + src/pyproxy.bc src/python2js.bc src/runpython.bc root $(CC) -s WASM=1 -s EXPORT_NAME="'pyodide'" --bind -o $@ $(filter %.bc,$^) $(LDFLAGS) \ $(foreach d,$(wildcard root/*),--preload-file $d@/$(notdir $d)) diff --git a/src/js2python.cpp b/src/js2python.cpp index cf72a76f3..e20a6b52b 100644 --- a/src/js2python.cpp +++ b/src/js2python.cpp @@ -4,6 +4,9 @@ using emscripten::val; +static val *Array = NULL; +static val *Object = NULL; + PyObject *jsToPython(val x) { val xType = x.typeOf(); @@ -20,3 +23,51 @@ PyObject *jsToPython(val x) { return JsProxy_cnew(x); } } + +PyObject *jsToPythonArgs(val args) { + if (!Array->call("isArray", args)) { + PyErr_SetString(PyExc_TypeError, "Invalid args"); + return NULL; + } + + Py_ssize_t n = (Py_ssize_t)args["length"].as(); + PyObject *pyargs = PyTuple_New(n); + if (pyargs == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < n; ++i) { + PyObject *arg = jsToPython(args[i]); + PyTuple_SET_ITEM(pyargs, i, arg); + } + + return pyargs; +} + +PyObject *jsToPythonKwargs(val kwargs) { + val keys = Object->call("keys", kwargs); + + Py_ssize_t n = (Py_ssize_t)keys["length"].as(); + PyObject *pykwargs = PyDict_New(); + if (pykwargs == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < n; ++i) { + PyObject *k = jsToPython(keys[i]); + PyObject *v = jsToPython(kwargs[keys[i]]); + if (PyDict_SetItem(pykwargs, k, v)) { + return NULL; + } + Py_DECREF(k); + Py_DECREF(v); + } + + return pykwargs; +} + +int jsToPython_Ready() { + Array = new val(val::global("Array")); + Object = new val(val::global("Object")); + return 0; +} diff --git a/src/js2python.hpp b/src/js2python.hpp index fa6262e5c..ed8a28774 100644 --- a/src/js2python.hpp +++ b/src/js2python.hpp @@ -7,5 +7,8 @@ #include PyObject *jsToPython(emscripten::val x); +PyObject *jsToPythonArgs(emscripten::val args); +PyObject *jsToPythonKwargs(emscripten::val kwargs); +int jsToPython_Ready(); #endif /* JS2PYTHON_H */ diff --git a/src/main.cpp b/src/main.cpp index 9e6fed206..908c92f5e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include "jsproxy.hpp" #include "js2python.hpp" #include "pylocals.hpp" +#include "pyproxy.hpp" #include "python2js.hpp" #include "runpython.hpp" @@ -21,7 +22,12 @@ using emscripten::val; EMSCRIPTEN_BINDINGS(python) { emscripten::function("runPython", &runPython); - emscripten::class_("PyObject"); + emscripten::class_("Py") + .function("call", &Py::call) + .function("getattr", &Py::getattr) + .function("setattr", &Py::setattr) + .function("getitem", &Py::getitem) + .function("setitem", &Py::setitem); } extern "C" { @@ -31,6 +37,7 @@ extern "C" { Py_InitializeEx(0); if (JsProxy_Ready() || + jsToPython_Ready() || pythonToJs_Ready() || PyLocals_Ready()) { return 1; diff --git a/src/pyproxy.cpp b/src/pyproxy.cpp new file mode 100644 index 000000000..7afea3e9d --- /dev/null +++ b/src/pyproxy.cpp @@ -0,0 +1,89 @@ +#include "pyproxy.hpp" + +using emscripten::val; + +Py::Py(PyObject *obj) : x(obj) { + Py_INCREF(x); +} + +Py::Py(const Py& o) : x(o.x) { + Py_INCREF(x); +} + +Py::~Py() { + Py_DECREF(x); +} + +val Py::call(val args, val kwargs) { + PyObject *pyargs = jsToPythonArgs(args); + if (pyargs == NULL) { + return pythonExcToJs(); + } + + PyObject *pykwargs = jsToPythonKwargs(kwargs); + if (pykwargs == NULL) { + Py_DECREF(pyargs); + return pythonExcToJs(); + } + + PyObject *pyret = PyObject_Call(x, pyargs, pykwargs); + Py_DECREF(pyargs); + Py_DECREF(pykwargs); + if (pyret == NULL) { + return pythonExcToJs(); + } + + val ret = pythonToJs(pyret); + Py_DECREF(pyret); + return ret; +} + +val Py::getattr(val idx) { + PyObject *pyidx = jsToPython(idx); + PyObject *attr = PyObject_GetAttr(x, pyidx); + Py_DECREF(pyidx); + if (attr == NULL) { + return pythonExcToJs(); + } + + val ret = pythonToJs(attr); + Py_DECREF(attr); + return ret; +} + +void Py::setattr(val idx, val v) { + PyObject *pyidx = jsToPython(idx); + PyObject *pyv = jsToPython(v); + + int ret = PyObject_SetAttr(x, pyidx, pyv); + Py_DECREF(pyidx); + Py_DECREF(pyv); + if (ret) { + pythonExcToJs(); + } +} + +val Py::getitem(val idx) { + PyObject *pyidx = jsToPython(idx); + PyObject *item = PyObject_GetItem(x, pyidx); + Py_DECREF(pyidx); + if (item == NULL) { + return pythonExcToJs(); + } + + val ret = pythonToJs(item); + Py_DECREF(item); + return ret; +} + +void Py::setitem(val idx, val v) { + PyObject *pyidx = jsToPython(idx); + PyObject *pyv = jsToPython(v); + + int ret = PyObject_SetItem(x, pyidx, pyv); + Py_DECREF(pyidx); + Py_DECREF(pyv); + if (ret) { + pythonExcToJs(); + } +} diff --git a/src/pyproxy.hpp b/src/pyproxy.hpp new file mode 100644 index 000000000..1635d2b10 --- /dev/null +++ b/src/pyproxy.hpp @@ -0,0 +1,27 @@ +#ifndef PYPROXY_H +#define PYPROXY_H + +#include +#include +#include +#include + +#include "js2python.hpp" +#include "python2js.hpp" + +class Py { + PyObject *x; + +public: + Py(PyObject *obj); + Py(const Py& o); + ~Py(); + + emscripten::val call(emscripten::val args, emscripten::val kwargs); + emscripten::val getattr(emscripten::val idx); + void setattr(emscripten::val idx, emscripten::val v); + emscripten::val getitem(emscripten::val idx); + void setitem(emscripten::val idx, emscripten::val v); +}; + +#endif /* PYPROXY_H */ diff --git a/src/python2js.cpp b/src/python2js.cpp index 6de0a156e..5842fb8eb 100644 --- a/src/python2js.cpp +++ b/src/python2js.cpp @@ -1,11 +1,12 @@ #include "python2js.hpp" #include "jsproxy.hpp" +#include "pyproxy.hpp" using emscripten::val; static val *undefined; -val pythonExcToJS() { +val pythonExcToJs() { PyObject *type; PyObject *value; PyObject *traceback; @@ -36,13 +37,13 @@ val pythonToJs(PyObject *x) { } else if (PyLong_Check(x)) { long x_long = PyLong_AsLongLong(x); if (x_long == -1 && PyErr_Occurred()) { - return pythonExcToJS(); + return pythonExcToJs(); } return val(x_long); } else if (PyFloat_Check(x)) { double x_double = PyFloat_AsDouble(x); if (x_double == -1.0 && PyErr_Occurred()) { - return pythonExcToJS(); + return pythonExcToJs(); } return val(x_double); } else if (PyUnicode_Check(x)) { @@ -51,7 +52,7 @@ val pythonToJs(PyObject *x) { Py_ssize_t length; wchar_t *chars = PyUnicode_AsWideCharString(x, &length); if (chars == NULL) { - return pythonExcToJS(); + return pythonExcToJs(); } std::wstring x_str(chars, length); PyMem_Free(chars); @@ -72,7 +73,7 @@ val pythonToJs(PyObject *x) { for (size_t i = 0; i < length; ++i) { PyObject *item = PySequence_GetItem(x, i); if (item == NULL) { - return pythonExcToJS(); + return pythonExcToJs(); } x_array.call("push", pythonToJs(item)); Py_DECREF(item); @@ -88,7 +89,7 @@ val pythonToJs(PyObject *x) { } return x_object; } else { - return val(x); + return val(Py(x)); } } diff --git a/src/python2js.hpp b/src/python2js.hpp index 7a666569a..41d579518 100644 --- a/src/python2js.hpp +++ b/src/python2js.hpp @@ -6,7 +6,7 @@ #include #include -emscripten::val pythonExcToJS(); +emscripten::val pythonExcToJs(); emscripten::val pythonToJs(PyObject *x); int pythonToJs_Ready(); diff --git a/src/runpython.cpp b/src/runpython.cpp index 44ea92887..17973c19b 100644 --- a/src/runpython.cpp +++ b/src/runpython.cpp @@ -59,7 +59,7 @@ val runPython(std::wstring code) { } ret = PyRun_StringFlags(&*code_utf8.begin(), Py_file_input, globals, locals, &cf); if (ret == NULL) { - return pythonExcToJS(); + return pythonExcToJs(); } Py_DECREF(ret); } @@ -78,7 +78,7 @@ val runPython(std::wstring code) { } if (ret == NULL) { - return pythonExcToJS(); + return pythonExcToJs(); } // Now copy all the variables over to the Javascript side