Add 'from js import X' on Python side

This commit is contained in:
Michael Droettboom 2018-02-28 17:37:14 -05:00
parent d4eb51b167
commit 314ec07227
7 changed files with 121 additions and 155 deletions

View File

@ -22,7 +22,7 @@ LDFLAGS=$(OPTFLAGS) \
all: build/pyodide.asm.html build/pyodide.js
build/pyodide.asm.html: src/main.bc src/jsproxy.bc src/js2python.bc src/pylocals.bc \
build/pyodide.asm.html: src/main.bc src/jsimport.bc src/jsproxy.bc src/js2python.bc \
src/pyproxy.bc src/python2js.bc src/runpython.bc root
$(CC) -s EXPORT_NAME="'pyodide'" --bind -o $@ $(filter %.bc,$^) $(LDFLAGS) \
$(foreach d,$(wildcard root/*),--preload-file $d@/$(notdir $d))

104
src/jsimport.cpp Normal file
View File

@ -0,0 +1,104 @@
#include "jsimport.hpp"
#include "js2python.hpp"
using emscripten::val;
static PyObject *original__import__;
PyObject *globals = NULL;
PyObject *original_globals = NULL;
typedef struct {
PyObject_HEAD
} JsImport;
static PyObject *JsImport_Call(PyObject *self, PyObject *args, PyObject *kwargs) {
PyObject *name = PyTuple_GET_ITEM(args, 0);
if (PyUnicode_CompareWithASCIIString(name, "js") == 0) {
PyObject *locals = PyTuple_GET_ITEM(args, 2);
PyObject *fromlist = PyTuple_GET_ITEM(args, 3);
Py_ssize_t n = PySequence_Size(fromlist);
PyObject *jsmod = PyModule_New("js");
PyObject *d = PyModule_GetDict(jsmod);
for (Py_ssize_t i = 0; i < n; ++i) {
PyObject *key = PySequence_GetItem(fromlist, i);
if (key == NULL) {
return NULL;
}
char *c = PyUnicode_AsUTF8(key);
if (c == NULL) {
Py_DECREF(key);
return NULL;
}
val jsval = val::global(c);
PyObject *pyval = jsToPython(jsval);
if (PyDict_SetItem(d, key, pyval)) {
Py_DECREF(key);
Py_DECREF(pyval);
return NULL;
}
Py_DECREF(key);
Py_DECREF(pyval);
}
return jsmod;
} else {
return PyObject_Call(original__import__, args, kwargs);
}
}
static PyTypeObject JsImportType = {
.tp_name = "JsImport",
.tp_basicsize = sizeof(JsImport),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_call = JsImport_Call,
.tp_doc = "An import hook that imports things from Javascript."
};
static PyObject *JsImport_New() {
JsImport *self;
self = (JsImport *)JsImportType.tp_alloc(&JsImportType, 0);
return (PyObject *)self;
}
int JsImport_Ready() {
if (PyType_Ready(&JsImportType)) {
return 1;
}
PyObject *m = PyImport_AddModule("builtins");
if (m == NULL) {
return 1;
}
PyObject *d = PyModule_GetDict(m);
if (d == NULL) {
return 1;
}
original__import__ = PyDict_GetItemString(d, "__import__");
if (original__import__ == NULL) {
return 1;
}
Py_INCREF(original__import__);
PyObject *importer = JsImport_New();
if (importer == NULL) {
return 1;
}
if (PyDict_SetItemString(d, "__import__", importer)) {
return 1;
}
m = PyImport_AddModule("__main__");
if (m == NULL)
return 1;
globals = PyModule_GetDict(m);
m = PyImport_AddModule("builtins");
PyDict_Update(globals, PyModule_GetDict(m));
original_globals = PyDict_Copy(globals);
return 0;
}

10
src/jsimport.hpp Normal file
View File

@ -0,0 +1,10 @@
#ifndef JSIMPORT_H
#define JSIMPORT_H
#include <Python.h>
int JsImport_Ready();
extern PyObject *globals;
extern PyObject *original_globals;
#endif /* JSIMPORT_H */

View File

@ -4,9 +4,9 @@
#include <Python.h>
#include <node.h> // from CPython
#include "jsimport.hpp"
#include "jsproxy.hpp"
#include "js2python.hpp"
#include "pylocals.hpp"
#include "pyproxy.hpp"
#include "python2js.hpp"
#include "runpython.hpp"
@ -39,7 +39,7 @@ extern "C" {
if (JsProxy_Ready() ||
jsToPython_Ready() ||
pythonToJs_Ready() ||
PyLocals_Ready()) {
JsImport_Ready()) {
return 1;
}

View File

@ -1,123 +0,0 @@
#include "pylocals.hpp"
#include "js2python.hpp"
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
using emscripten::val;
////////////////////////////////////////////////////////////
// PyLocals
//
// This is an object designed to be used as a "locals" namespace dictionary.
// It first looks for things in its own internal dictionary, and failing that,
// looks in the Javascript global namespace. This is a way of merging the
// Python and Javascript namespaces without fullying copying either one.
PyObject *locals = NULL;
PyObject *globals = NULL;
PyObject *original_globals = NULL;
typedef struct {
PyObject_HEAD
PyObject *locals;
} PyLocals;
static void PyLocals_dealloc(PyLocals *self) {
Py_DECREF(self->locals);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static Py_ssize_t PyLocals_length(PyObject *o) {
PyLocals *self = (PyLocals *)o;
return PyDict_Size(self->locals);
}
PyObject* PyLocals_get(PyObject *o, PyObject *key) {
PyLocals *self = (PyLocals *)o;
{
PyObject *str = PyObject_Str(key);
if (str == NULL) {
return NULL;
}
char *c = PyUnicode_AsUTF8(str);
Py_DECREF(str);
}
PyObject *py_val = PyDict_GetItem(self->locals, key);
if (py_val != NULL) {
Py_INCREF(py_val);
return py_val;
}
PyObject *str = PyObject_Str(key);
if (str == NULL) {
return NULL;
}
char *c = PyUnicode_AsUTF8(str);
val v = val::global(c);
Py_DECREF(str);
return jsToPython(v);
}
int PyLocals_set(PyObject *o, PyObject *k, PyObject *v) {
PyLocals *self = (PyLocals *)o;
if (v == NULL) {
// TODO: This might not actually be here to delete...
return PyDict_DelItem(self->locals, k);
} else {
return PyDict_SetItem(self->locals, k, v);
}
}
static PyMappingMethods PyLocals_as_mapping = {
PyLocals_length,
PyLocals_get,
PyLocals_set
};
static PyTypeObject PyLocalsType = {
.tp_name = "PyLocals",
.tp_basicsize = sizeof(PyLocals),
.tp_dealloc = (destructor)PyLocals_dealloc,
.tp_as_mapping = &PyLocals_as_mapping,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "A proxy that looks in a dict first, otherwise in the global JS namespace"
};
static PyObject *PyLocals_cnew(PyObject *d)
{
PyLocals *self;
self = (PyLocals *)PyLocalsType.tp_alloc(&PyLocalsType, 0);
if (self != NULL) {
Py_INCREF(d);
self->locals = d;
}
return (PyObject *)self;
}
int PyLocals_Ready() {
if (PyType_Ready(&PyLocalsType) < 0)
return 1;
PyObject *m = PyImport_AddModule("__main__");
if (m == NULL)
return 1;
globals = PyModule_GetDict(m);
m = PyImport_AddModule("builtins");
PyDict_Update(globals, PyModule_GetDict(m));
original_globals = PyDict_Copy(globals);
locals = PyLocals_cnew(globals);
if (locals == NULL)
return 1;
return 0;
}

View File

@ -1,12 +0,0 @@
#ifndef PYLOCALS_H
#define PYLOCALS_H
#include <Python.h>
int PyLocals_Ready();
extern PyObject *locals;
extern PyObject *globals;
extern PyObject *original_globals;
#endif /* PYLOCALS_H */

View File

@ -6,7 +6,7 @@
#include <Python.h>
#include <node.h> // from Python
#include "pylocals.hpp"
#include "jsimport.hpp"
#include "python2js.hpp"
using emscripten::val;
@ -57,7 +57,7 @@ val runPython(std::wstring code) {
*last_line = 0;
last_line++;
}
ret = PyRun_StringFlags(&*code_utf8.begin(), Py_file_input, globals, locals, &cf);
ret = PyRun_StringFlags(&*code_utf8.begin(), Py_file_input, globals, globals, &cf);
if (ret == NULL) {
return pythonExcToJs();
}
@ -70,10 +70,10 @@ val runPython(std::wstring code) {
ret = Py_None;
break;
case 1:
ret = PyRun_StringFlags(&*last_line, Py_eval_input, globals, locals, &cf);
ret = PyRun_StringFlags(&*last_line, Py_eval_input, globals, globals, &cf);
break;
case 2:
ret = PyRun_StringFlags(&*last_line, Py_file_input, globals, locals, &cf);
ret = PyRun_StringFlags(&*last_line, Py_file_input, globals, globals, &cf);
break;
}
@ -81,19 +81,6 @@ val runPython(std::wstring code) {
return pythonExcToJs();
}
// Now copy all the variables over to the Javascript side
{
val js_globals = val::global("window");
PyObject *k, *v;
Py_ssize_t pos = 0;
while (PyDict_Next(globals, &pos, &k, &v)) {
if (!PyDict_Contains(original_globals, k)) {
js_globals.set(pythonToJs(k), pythonToJs(v));
}
}
}
val result = pythonToJs(ret);
Py_DECREF(ret);
return result;