Commit 65628719 authored by Michael Droettboom's avatar Michael Droettboom

Merge branch 'hiwire'

parents 7d0f7649 7d19fcd6
version: 2
jobs:
build:
docker:
- image: circleci/python:3.6.5-stretch-browsers
working_directory: ~/repo
steps:
- checkout
- run:
name: dependencies
command: |
sudo apt-get install node-less cmake build-essential
# We need at least g++-8, but stretch comes with g++-6
# Set up the Debian testing repo, and then install g++ from there...
sudo bash -c "echo \"deb http://ftp.us.debian.org/debian testing main contrib non-free\" >> /etc/apt/sources.list"
sudo apt-get update
sudo apt-get install -t testing g++-8
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 80 --slave /usr/bin/g++ g++ /usr/bin/g++-8
sudo update-alternatives --set gcc /usr/bin/gcc-8
sudo pip install pytest-xdist selenium
# Get recent version of Firefox and geckodriver
wget -O firefox.tar.bz2 https://download.mozilla.org/\?product\=firefox-nightly-latest-ssl\&os\=linux64\&lang\=en-US
tar jxf firefox.tar.bz2
wget https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz
tar zxf geckodriver-v0.20.1-linux64.tar.gz -C firefox
# This Debian is so old, it doesn't know about wasm as a mime type, which then
# causes Firefox to complain when loading it. Let's just add the new mime type.
sudo bash -c "echo 'application/wasm wasm' >> /etc/mime.types"
- restore_cache:
keys:
- v1-emsdk-{{ checksum "emsdk/Makefile" }}
# fallback to using the latest cache if no exact match is found
- v1-emsdk-
- run:
name: build
no_output_timeout: 1200
command: |
make
- save_cache:
paths:
- ./emsdk/emsdk
key: v1-emsdk-{{ checksum "emsdk/Makefile" }}
- run:
name: test
command: |
export PATH=$PWD/firefox:$PATH
make test
PYODIDE_ROOT=$(abspath .)
include Makefile.envs
FILEPACKAGER=emsdk/emsdk/emscripten/incoming/tools/file_packager.py
FILEPACKAGER=emsdk/emsdk/emscripten/tag-1.38.4/tools/file_packager.py
CPYTHONROOT=cpython
CPYTHONLIB=$(CPYTHONROOT)/installs/python-$(PYVERSION)/lib/python$(PYMINOR)
......@@ -11,6 +11,11 @@ CXX=em++
OPTFLAGS=-O3
CFLAGS=$(OPTFLAGS) -g -I$(PYTHONINCLUDE) -Wno-warn-absolute-paths
CXXFLAGS=$(CFLAGS) -std=c++14
# __ZNKSt3__220__vector_base_commonILb1EE20__throw_length_errorEv is in
# EXPORTED_FUNCTIONS to keep the C++ standard library in the core, even though
# there isn't any C++ there, for the sake of loading dynamic modules written in
# C++, such as those in matplotlib.
LDFLAGS=\
-O3 \
-s MODULARIZE=1 \
......@@ -20,10 +25,12 @@ LDFLAGS=\
-s MAIN_MODULE=1 \
-s EMULATED_FUNCTION_POINTERS=1 \
-s EMULATE_FUNCTION_POINTER_CASTS=1 \
-s EXPORTED_FUNCTIONS='["_main"]' \
-s EXPORTED_FUNCTIONS='["_main", "__ZNKSt3__220__vector_base_commonILb1EE20__throw_length_errorEv"]' \
-s WASM=1 \
-s SWAPPABLE_ASM_MODULE=1 \
-s USE_FREETYPE=1 \
-std=c++14 \
-lstdc++ \
--memory-init-file 0
NUMPY_ROOT=numpy/build/numpy
......@@ -79,9 +86,9 @@ all: build/pyodide.asm.js \
build/pyodide.asm.js: src/main.bc src/jsimport.bc src/jsproxy.bc src/js2python.bc \
src/pyimport.bc src/pyproxy.bc src/python2js.bc \
src/runpython.bc src/dummy_thread.bc
src/runpython.bc src/dummy_thread.bc src/hiwire.bc
[ -d build ] || mkdir build
$(CC) -s EXPORT_NAME="'pyodide'" --bind -o build/pyodide.asm.html $(filter %.bc,$^) \
$(CXX) -s EXPORT_NAME="'pyodide'" -o build/pyodide.asm.html $(filter %.bc,$^) \
$(LDFLAGS) -s FORCE_FILESYSTEM=1
rm build/pyodide.asm.asm.js
rm build/pyodide.asm.wasm.pre
......
export PATH := $(PYODIDE_ROOT)/emsdk/emsdk:$(PYODIDE_ROOT)/emsdk/emsdk/clang/fastcomp/build_incoming_64/bin:$(PYODIDE_ROOT)/emsdk/emsdk/node/8.9.1_64bit/bin:$(PYODIDE_ROOT)/emsdk/emsdk/emscripten/incoming:$(PYODIDE_ROOT)/emsdk/emsdk/binaryen/master_64bit_binaryen/bin:$(PATH)
export PATH := $(PYODIDE_ROOT)/emsdk/emsdk:$(PYODIDE_ROOT)/emsdk/emsdk/clang/tag-e-1.38.4/build_tag-e1.38.4_64/bin:$(PYODIDE_ROOT)/emsdk/emsdk/node/8.9.1_64bit/bin:$(PYODIDE_ROOT)/emsdk/emsdk/emscripten/tag-1.38.4:$(PYODIDE_ROOT)/emsdk/emsdk/binaryen/tag-1.38.4_64bit_binaryen/bin:$(PATH)
export EMSDK = $(PYODIDE_ROOT)/emsdk/emsdk
export EM_CONFIG = $(PYODIDE_ROOT)/emsdk/emsdk/.emscripten
export EM_CACHE = $(PYODIDE_ROOT)/emsdk/emsdk/.emscripten_cache
export EMSCRIPTEN = $(PYODIDE_ROOT)/emsdk/emsdk/emscripten/incoming
export BINARYEN_ROOT = $(PYODIDE_ROOT)/emsdk/emsdk/binaryen/master_64bit_binaryen
export EMSCRIPTEN = $(PYODIDE_ROOT)/emsdk/emsdk/emscripten/tag-1.38.4
export BINARYEN_ROOT = $(PYODIDE_ROOT)/emsdk/emsdk/binaryen/tag-1.38.4_64bit_binaryen
export PYVERSION=3.6.4
export PYMINOR=$(basename $(PYVERSION))
......@@ -15,6 +16,7 @@ export PLATFORMSLUG=$(shell $(HOSTPYTHON) -c "import sysconfig; print(sysconfig.
export SIDE_LDFLAGS=\
-O3 \
-s "BINARYEN_METHOD='native-wasm'" \
-Werror \
-s EMULATED_FUNCTION_POINTERS=1 \
-s EMULATE_FUNCTION_POINTER_CASTS=1 \
......
all: emsdk/emsdk
# We hack the CPU_CORES, because if you use all of the cores on Circle-CI, you
# run out of memory
emsdk/emsdk:
git clone https://github.com/juj/emsdk.git
sed -i -e "s#CPU_CORES = max(multiprocessing.cpu_count()-1, 1)#CPU_CORES = 3#g" emsdk/emsdk
( \
cd emsdk ; \
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit ; \
./emsdk install --build=Release sdk-tag-1.38.4-64bit binaryen-tag-1.38.4-64bit ; \
cd .. ; \
(cat patches/*.patch | patch -p1) ; \
cd emsdk ; \
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit ; \
./emsdk activate --embedded --build=Release sdk-incoming-64bit binaryen-master-64bit \
cd emsdk/binaryen/tag-1.38.4_64bit_binaryen/ ; \
make ; \
cd ../.. ; \
./emsdk activate --embedded --build=Release sdk-tag-1.38.4-64bit binaryen-tag-1.38.4-64bit \
)
clean:
......
index f6c9842ff..9f83181eb 100644
--- a/emsdk/emscripten/tag-1.38.4/src/support.js
+++ b/emsdk/emscripten/tag-1.38.4/src/support.js
@@ -158,7 +158,33 @@ function loadWebAssemblyModule(binary) {
'Infinity': Infinity,
},
'global.Math': Math,
- env: env
+ env: env,
+ 'asm2wasm': { // special asm2wasm imports
+ "f64-rem": function(x, y) {
+ return x % y;
+ },
+ "debugger": function() {
+ debugger;
+ }
+#if NEED_ALL_ASM2WASM_IMPORTS
+ ,
+ "f64-to-int": function(x) {
+ return x | 0;
+ },
+ "i32s-div": function(x, y) {
+ return ((x | 0) / (y | 0)) | 0;
+ },
+ "i32u-div": function(x, y) {
+ return ((x >>> 0) / (y >>> 0)) >>> 0;
+ },
+ "i32s-rem": function(x, y) {
+ return ((x | 0) % (y | 0)) | 0;
+ },
+ "i32u-rem": function(x, y) {
+ return ((x >>> 0) % (y >>> 0)) >>> 0;
+ }
+#endif // NEED_ALL_ASM2WASM_IMPORTS
+ },
};
#if ASSERTIONS
var oldTable = [];
diff --git a/emsdk/binaryen/master/src/passes/FuncCastEmulation.cpp b/emsdk/binaryen/master/src/passes/FuncCastEmulation.cpp
index 013e9403..d95fc282 100644
--- a/emsdk/binaryen/master/src/passes/FuncCastEmulation.cpp
+++ b/emsdk/binaryen/master/src/passes/FuncCastEmulation.cpp
--- a/emsdk/binaryen/tag-1.38.4/src/passes/FuncCastEmulation.cpp
+++ b/emsdk/binaryen/tag-1.38.4/src/passes/FuncCastEmulation.cpp
@@ -39,7 +39,7 @@ namespace wasm {
// This should be enough for everybody. (As described above, we need this
// to match when dynamically linking, and also dynamic linking is why we
......
#include <emscripten.h>
EM_JS(void, hiwire_setup, (), {
var hiwire = {
objects: {},
counter: 1
};
Module.hiwire_new_value = function(jsval) {
var objects = hiwire.objects;
while (hiwire.counter in objects) {
hiwire.counter = (hiwire.counter + 1) % 0x8fffffff;
}
var idval = hiwire.counter;
objects[idval] = jsval;
hiwire.counter = (hiwire.counter + 1) % 0x8fffffff;
return idval;
};
Module.hiwire_get_value = function(idval) {
return hiwire.objects[idval];
};
Module.hiwire_decref = function(idval) {
var objects = hiwire.objects;
delete objects[idval];
};
});
EM_JS(int, hiwire_incref, (int idval), {
return Module.hiwire_new_value(Module.hiwire_get_value(idval));
});
EM_JS(void, hiwire_decref, (int idval), {
Module.hiwire_decref(idval);
});
EM_JS(int, hiwire_int, (int val), {
return Module.hiwire_new_value(val);
});
EM_JS(int, hiwire_double, (double val), {
return Module.hiwire_new_value(val);
});
EM_JS(int, hiwire_string_utf8_length, (int ptr, int len), {
var bytes = new Uint8Array(Module.HEAPU8.buffer, ptr, len);
var jsval = new TextDecoder('utf-8').decode(bytes);
return Module.hiwire_new_value(jsval);
});
EM_JS(int, hiwire_string_utf8, (int ptr), {
return Module.hiwire_new_value(UTF8ToString(ptr));
});
EM_JS(int, hiwire_bytes, (int ptr, int len), {
var bytes = new Uint8ClampedArray(Module.HEAPU8.buffer, ptr, len);
return Module.hiwire_new_value(bytes);
});
EM_JS(int, hiwire_undefined, (), {
return Module.hiwire_new_value(undefined);
});
EM_JS(int, hiwire_null, (), {
return Module.hiwire_new_value(null);
});
EM_JS(int, hiwire_true, (), {
return Module.hiwire_new_value(true);
});
EM_JS(int, hiwire_false, (), {
return Module.hiwire_new_value(false);
});
EM_JS(int, hiwire_throw_error, (int idmsg), {
var jsmsg = Module.hiwire_get_value(idmsg);
Module.hiwire_decref(idmsg);
throw new Error(jsmsg);
});
EM_JS(int, hiwire_array, (), {
return Module.hiwire_new_value([]);
});
EM_JS(void, hiwire_push_array, (int idarr, int idval), {
Module.hiwire_get_value(idarr).push(Module.hiwire_get_value(idval));
});
EM_JS(int, hiwire_object, (), {
return Module.hiwire_new_value({});
});
EM_JS(void, hiwire_push_object_pair, (int idobj, int idkey, int idval), {
var jsobj = Module.hiwire_get_value(idobj);
var jskey = Module.hiwire_get_value(idkey);
var jsval = Module.hiwire_get_value(idval);
jsobj[jskey] = jsval;
});
EM_JS(int, hiwire_get_global, (int idname), {
var jsname = UTF8ToString(idname);
return Module.hiwire_new_value(window[jsname]);
});
EM_JS(int, hiwire_get_member_string, (int idobj, int idkey), {
var jsobj = Module.hiwire_get_value(idobj);
var jskey = UTF8ToString(idkey);
return Module.hiwire_new_value(jsobj[jskey]);
});
EM_JS(void, hiwire_set_member_string, (int idobj, int ptrkey, int idval), {
var jsobj = Module.hiwire_get_value(idobj);
var jskey = UTF8ToString(ptrkey);
var jsval = Module.hiwire_get_value(idval);
jsobj[jskey] = jsval;
});
EM_JS(int, hiwire_get_member_int, (int idobj, int idx), {
var jsobj = Module.hiwire_get_value(idobj);
return Module.hiwire_new_value(jsobj[idx]);
});
EM_JS(void, hiwire_set_member_int, (int idobj, int idx, int idval), {
Module.hiwire_get_value(idobj)[idx] = Module.hiwire_get_value(idval);
});
EM_JS(void, hiwire_call, (int idfunc, int idargs), {
var jsfunc = Module.hiwire_get_value(idfunc);
var jsargs = Module.hiwire_get_value(idargs);
return Module.hiwire_new_value(jsfunc.apply(jsfunc, jsargs));
});
EM_JS(void, hiwire_call_member, (int idobj, int ptrname, int idargs), {
var jsobj = Module.hiwire_get_value(idobj);
var jsname = UTF8ToString(ptrname);
var jsargs = Module.hiwire_get_value(idargs);
return Module.hiwire_new_value(jsobj[jsname].apply(jsobj, jsargs));
});
EM_JS(void, hiwire_new, (int idobj, int idargs), {
function newCall(Cls) {
return new (Function.prototype.bind.apply(Cls, arguments));
}
var jsobj = Module.hiwire_get_value(idobj);
var jsargs = Module.hiwire_get_value(idargs);
jsargs.unshift(jsobj);
return Module.hiwire_new_value(newCall.apply(newCall, jsargs));
});
EM_JS(void, hiwire_get_length, (int idobj), {
return Module.hiwire_get_value(idobj).length;
});
EM_JS(void, hiwire_is_function, (int idobj), {
return typeof Module.hiwire_get_value(idobj) === 'function';
});
EM_JS(void, hiwire_to_string, (int idobj), {
return Module.hiwire_new_value(Module.hiwire_get_value(idobj).toString());
});
#ifndef HIWIRE_H
#define HIWIRE_H
// TODO: Document me
void hiwire_setup();
int hiwire_incref(int idval);
void hiwire_decref(int idval);
int hiwire_int(int val);
int hiwire_double(double val);
int hiwire_string_utf8_length(int ptr, int len);
int hiwire_string_utf8(int ptr);
int hiwire_bytes(int ptr, int len);
int hiwire_undefined();
int hiwire_null();
int hiwire_true();
int hiwire_false();
int hiwire_array();
int hiwire_push_array(int idobj, int idval);
int hiwire_object();
int hiwire_push_object_pair(int idobj, int idkey, int idval);
int hiwire_throw_error(int idmsg);
int hiwire_get_global(int ptrname);
int hiwire_get_member_string(int idobj, int ptrname);
void hiwire_set_member_string(int idobj, int ptrname, int idval);
int hiwire_get_member_int(int idobj, int idx);
void hiwire_set_member_int(int idobj, int idx, int idval);
int hiwire_call(int idobj, int idargs);
int hiwire_call_member(int idobj, int ptrname, int idargs);
int hiwire_new(int idobj, int idargs);
int hiwire_get_length(int idobj);
int hiwire_is_function(int idobj);
int hiwire_to_string(int idobj);
#endif /* HIWIRE_H */
#include "js2python.h"
#include <emscripten.h>
#include "jsproxy.h"
#include "pyproxy.h"
// Since we're going *to* Python, just let any Python exceptions at conversion
// bubble out to Python
int _js2python_string(char *val) {
return (int)PyUnicode_FromString(val);
}
int _js2python_number(double val) {
return (int)PyFloat_FromDouble(val);
}
int _js2python_none() {
Py_INCREF(Py_None);
return (int)Py_None;
}
int _js2python_true() {
Py_INCREF(Py_True);
return (int)Py_True;
}
int _js2python_false() {
Py_INCREF(Py_False);
return (int)Py_False;
}
int _js2python_pyproxy(PyObject *val) {
Py_INCREF(val);
return (int)val;
}
int _js2python_bytes(char *bytes, int length) {
return (int)PyBytes_FromStringAndSize(bytes, length);
}
int _js2python_jsproxy(int id) {
return (int)JsProxy_cnew(id);
}
// TODO: Add some meaningful order
EM_JS(int, __js2python, (int id), {
var value = Module.hiwire_get_value(id);
var type = typeof value;
if (type === 'string') {
var charptr = allocate(intArrayFromString(value), 'i8', ALLOC_NORMAL);
var result = __js2python_string(charptr);
_free(charptr);
return result;
} 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) {
var bytes = allocate(value, 'i8', ALLOC_NORMAL);
var result = __js2python_bytes(bytes, value['byteLength']);
_free(bytes);
return result;
} else {
return __js2python_jsproxy(id);
}
});
PyObject *js2python(int id) {
return (PyObject *)__js2python(id);
}
int js2python_init() {
return 0;
}
#include "js2python.hpp"
#include "jsproxy.hpp"
#include "pyproxy.hpp"
using emscripten::val;
static val *Array = NULL;
static val *Object = NULL;
PyObject *jsToPython(val x) {
val xType = x.typeOf();
// String
if (xType.equals(val("string"))) {
std::wstring x_str = x.as<std::wstring>();
return PyUnicode_FromWideChar(&*x_str.begin(), x_str.size());
// Number
} else if (xType.equals(val("number"))) {
double x_double = x.as<double>();
return PyFloat_FromDouble(x_double);
// undefined and null (both map to Python None)
} else if (x.isUndefined() || x.isNull()) {
Py_INCREF(Py_None);
return Py_None;
// true
} else if (x.isTrue()) {
Py_INCREF(Py_True);
return Py_True;
// false
} else if (x.isFalse()) {
Py_INCREF(Py_False);
return Py_False;
// raw PyObject pointer
} else if (!x["$$"].isUndefined() &&
x["$$"]["ptrType"]["name"].equals(val("PyObject*"))) {
PyObject *py_x = x.as<PyObject *>(emscripten::allow_raw_pointers());
Py_INCREF(py_x);
return py_x;
// callable PyObject pointer
} else if (!x["$$"].isUndefined() &&
x["$$"]["ptrType"]["name"].equals(val("PyCallable*"))) {
PyCallable py_x = x.as<PyCallable>();
PyObject *pypy_x = py_x.x;
Py_INCREF(pypy_x);
return pypy_x;
// byte string
} else if (!x["byteLength"].isUndefined()) {
std::string x_str = x.as<std::string>();
return PyBytes_FromStringAndSize(x_str.c_str(), x_str.size());
// everything else: return a JsProxy
} else {
return JsProxy_cnew(x);
}
}
PyObject *jsToPythonArgs(val args) {
if (!Array->call<bool>("isArray", args)) {
PyErr_SetString(PyExc_TypeError, "Invalid args");
return NULL;
}
Py_ssize_t n = (Py_ssize_t)args["length"].as<long>();
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<val>("keys", kwargs);
Py_ssize_t n = (Py_ssize_t)keys["length"].as<long>();
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)) {
Py_DECREF(pykwargs);
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;
}
......@@ -5,9 +5,6 @@
* Utilities to convert Javascript objects to Python objects.
*/
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <Python.h>
/** Convert a Javascript object to a Python object.
......@@ -16,25 +13,9 @@
* occurred during the conversion, and the Python exception API should be used
* to obtain the exception.
*/
PyObject *jsToPython(emscripten::val x);
/** Convert an Array of Javascript arguments to a Python tuple of arguments.
* \param args A Javascript Array of arguments
* \return The tuple of Python objects. New reference. If NULL, a Python exception
* occurred during the conversion, and the Python exception API should be used
* to obtain the exception.
*/
PyObject *jsToPythonArgs(emscripten::val args);
/** Convert an Object of Javascript "keyword arguments" to a Python dictionary of arguments.
* \param kwargs A Javascript Object of arguments
* \return The dict of Python objects. New reference. If NULL, a Python exception
* occurred during the conversion, and the Python exception API should be used
* to obtain the exception.
*/
PyObject *jsToPythonKwargs(emscripten::val kwargs);
PyObject *js2python(int x);
/** Initialize any global variables used by this module. */
int jsToPython_Ready();
int js2python_init();
#endif /* JS2PYTHON_H */
#include "jsimport.hpp"
#include "jsimport.h"
#include "js2python.hpp"
#include <emscripten.h>
using emscripten::val;
#include "hiwire.h"
#include "js2python.h"
static PyObject *original__import__;
PyObject *globals = NULL;
......@@ -21,32 +22,18 @@ static PyObject *JsImport_Call(PyObject *self, PyObject *args, PyObject *kwargs)
PyObject *jsmod = PyModule_New("js");
PyObject *d = PyModule_GetDict(jsmod);
bool is_star = false;
int is_star = 0;
if (n == 1) {
PyObject *firstfromlist = PySequence_GetItem(fromlist, 0);
if (PyUnicode_CompareWithASCIIString(firstfromlist, "*") == 0) {
is_star = true;
is_star = 1;
}
Py_DECREF(firstfromlist);
}
if (is_star) {
// "from js import *" imports everything from "window".
// TODO: Is this even a good idea?
val window = val::global("window");
val keys = val::global("Object")["keys"](window);
int gn = keys["length"].as<int>();
for (Py_ssize_t i = 0; i < gn; ++i) {
PyObject *key = jsToPython(keys[i]);
PyObject *pyval = jsToPython(window[keys[i]]);
if (PyDict_SetItem(d, key, pyval)) {
Py_DECREF(key);
Py_DECREF(pyval);
return NULL;
}
Py_DECREF(key);
Py_DECREF(pyval);
}
PyErr_SetString(PyExc_ImportError, "'import *' not supported for js");
return NULL;
} else {
for (Py_ssize_t i = 0; i < n; ++i) {
PyObject *key = PySequence_GetItem(fromlist, i);
......@@ -58,8 +45,9 @@ static PyObject *JsImport_Call(PyObject *self, PyObject *args, PyObject *kwargs)
Py_DECREF(key);
return NULL;
}
val jsval = val::global(c);
PyObject *pyval = jsToPython(jsval);
int jsval = hiwire_get_global((int)c);
PyObject *pyval = js2python(jsval);
hiwire_decref(jsval);
if (PyDict_SetItem(d, key, pyval)) {
Py_DECREF(key);
Py_DECREF(pyval);
......@@ -91,7 +79,7 @@ static PyObject *JsImport_New() {
return (PyObject *)self;
}
int JsImport_Ready() {
int JsImport_init() {
if (PyType_Ready(&JsImportType)) {
return 1;
}
......
......@@ -6,7 +6,7 @@
#include <Python.h>
/** Install the import hook to support "from js import …". */
int JsImport_Ready();
int JsImport_init();
extern PyObject *globals;
......
This diff is collapsed.
......@@ -6,15 +6,12 @@
*/
#include <Python.h>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
/** Make a new JsProxy.
* \param v The Javascript object.
* \return The Python object wrapping the Javascript object.
*/
PyObject *JsProxy_cnew(emscripten::val v);
PyObject *JsProxy_cnew(int v);
/** Check if a Python object is a JsProxy object.
* \param x The Python object
......@@ -26,9 +23,9 @@ int JsProxy_Check(PyObject *x);
* \param x The JsProxy object. Must confirm that it is a JsProxy object using JsProxy_Check.
* \return The Javascript object.
*/
emscripten::val JsProxy_AsVal(PyObject *x);
int JsProxy_AsJs(PyObject *x);
/** Initialize global state for the JsProxy functionality. */
int JsProxy_Ready();
int JsProxy_init();
#endif /* JSPROXY_H */
#include <emscripten.h>
#include <Python.h>
#include "hiwire.h"
#include "js2python.h"
#include "jsimport.h"
#include "jsproxy.h"
#include "pyimport.h"
#include "pyproxy.h"
#include "python2js.h"
#include "runpython.h"
/*
TODO: This is a workaround for a weird emscripten compiler bug. The
matplotlib/_qhull.so extension makes function pointer calls with these
signatures, but since nothing with that signature exists in the MAIN_MODULE,
it can't link the SIDE_MODULE. Creating these dummy functions here seems to
work around the problem.
*/
void __foo(double x) {
}
void __foo2(double x, double y) {
}
void __foo3(double x, double y, double z) {
}
void __foo4(int a, double b, int c, int d, int e) {
}
/* END WORKAROUND */
int main(int argc, char** argv) {
hiwire_setup();
setenv("PYTHONHOME", "/", 0);
Py_InitializeEx(0);
// TODO cleanup naming of these functions
if (
js2python_init() ||
JsImport_init() ||
JsProxy_init() ||
pyimport_init() ||
pyproxy_init() ||
python2js_init() ||
runpython_init()
) {
return 1;
}
printf("Python initialization complete\n");
emscripten_exit_with_live_runtime();
return 0;
}
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <Python.h>
#include <node.h> // from CPython
#include "jsimport.hpp"
#include "jsproxy.hpp"
#include "js2python.hpp"
#include "pyimport.hpp"
#include "pyproxy.hpp"
#include "python2js.hpp"
#include "runpython.hpp"
using emscripten::val;
val repr(val v) {
PyObject *pyv = jsToPython(v);
PyObject *r = PyObject_Repr(pyv);
if (r == NULL) {
return pythonExcToJs();
}
val result = pythonToJs(r);
Py_DECREF(r);
Py_DECREF(pyv);
return result;
}
EMSCRIPTEN_BINDINGS(python) {
emscripten::function("runPython", &runPython);
emscripten::function("pyimport", &pyimport);
emscripten::function("repr", &repr);
emscripten::class_<PyObject>("PyObject");
emscripten::class_<Py>("Py")
.class_function<bool>("isExtensible", &Py::isExtensible)
.class_function<bool>("has", &Py::has)
.class_function<val>("get", &Py::get)
.class_function<val>("set", &Py::set)
.class_function<val>("deleteProperty", &Py::deleteProperty)
.class_function<val>("ownKeys", &Py::ownKeys)
.class_function<val>("enumerate", &Py::enumerate);
emscripten::class_<PyCallable>("PyCallable")
.function<val>("call", &PyCallable::call);
}
extern "C" {
/*
TODO: This is a workaround for a weird emscripten compiler bug. The
matplotlib/_qhull.so extension makes function pointer calls with these
signatures, but since nothing with that signature exists in the MAIN_MODULE,
it can't link the SIDE_MODULE. Creating these dummy functions here seems to
work around the problem.
*/
void __foo(double) {
}
void __foo2(double, double) {
}
void __foo3(double, double, double) {
}
void __foo4(int, double, int, int, int) {
}
/* END WORKAROUND */
int main(int argc, char** argv) {
setenv("PYTHONHOME", "/", 0);
Py_InitializeEx(0);
if (JsProxy_Ready() ||
jsToPython_Ready() ||
pythonToJs_Ready() ||
JsImport_Ready()) {
return 1;
}
printf("Python initialization complete\n");
emscripten_exit_with_live_runtime();
return 0;
}
}
#include "pyimport.h"
#include <Python.h>
#include <emscripten.h>
#include "python2js.h"
extern PyObject *globals;
int _pyimport(char *name) {
PyObject *pyname = PyUnicode_FromString(name);
PyObject *pyval = PyDict_GetItem(globals, pyname);
if (pyval == NULL) {
Py_DECREF(pyname);
return pythonexc2js();
}
Py_DECREF(pyname);
int idval = python2js(pyval);
Py_DECREF(pyval);
return idval;
}
EM_JS(int, pyimport_init, (), {
Module.pyimport = function(name) {
var pyname = allocate(intArrayFromString(name), 'i8', ALLOC_NORMAL);
var idresult = Module.__pyimport(pyname);
jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
_free(pyname);
return jsresult;
};
return 0;
});
#include "js2python.hpp"
#include "jsimport.hpp"
#include "pyimport.hpp"
#include "python2js.hpp"
using emscripten::val;
val pyimport(val name) {
PyObject *pyname = jsToPython(name);
PyObject *pyval = PyDict_GetItem(globals, pyname);
if (pyval == NULL) {
return pythonExcToJs();
}
return pythonToJs(pyval);
}
#ifndef PYIMPORT_H
#define PYIMPORT_H
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
/** Makes `var foo = pyodide.pyimport('foo')` work in Javascript.
*/
emscripten::val pyimport(emscripten::val name);
int pyimport_init();
#endif /* PYIMPORT_H */
......@@ -75,16 +75,6 @@ var languagePluginLoader = new Promise((resolve, reject) => {
return promise;
};
////////////////////////////////////////////////////////////
// Callable Python object shim
let makeCallableProxy = (obj) => {
var clone = obj.clone();
function callProxy(args) {
return clone.call(Array.from(arguments), {});
};
return callProxy;
};
////////////////////////////////////////////////////////////
// Loading Pyodide
let wasmURL = `${baseURL}pyodide.asm.wasm`;
......@@ -112,7 +102,6 @@ var languagePluginLoader = new Promise((resolve, reject) => {
script.onload = () => {
window.pyodide = pyodide(Module);
window.pyodide.loadPackage = loadPackage;
window.pyodide.makeCallableProxy = makeCallableProxy;
};
document.head.appendChild(script);
};
......@@ -133,16 +122,15 @@ var languagePluginLoader = new Promise((resolve, reject) => {
// Add a custom output handler for Python objects
window.iodide.addOutputHandler({
shouldHandle: (val) => {
return (typeof val === 'object' &&
val['$$'] !== undefined &&
val['$$']['ptrType']['name'] === 'PyObject*');
return (typeof val === 'function' &&
pyodide.PyProxy.isPyProxy(val));
},
render: (val) => {
let div = document.createElement('div');
div.className = 'rendered_html';
var element;
if ('_repr_html_' in val) {
if (val._repr_html_ !== undefined) {
let result = val._repr_html_();
if (typeof result === 'string') {
div.appendChild(new DOMParser().parseFromString(
......@@ -153,7 +141,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
}
} else {
let pre = document.createElement('pre');
pre.textContent = window.pyodide.repr(val);
pre.textContent = val.toString();
div.appendChild(pre);
element = div;
}
......
#include <Python.h>
#include <emscripten.h>
#include "hiwire.h"
#include "js2python.h"
#include "python2js.h"
int _pyproxy_has(int ptrobj, int idkey) {
PyObject *pyobj = (PyObject *)ptrobj;
PyObject *pykey = js2python(idkey);
int result = PyObject_HasAttr(pyobj, pykey) ? hiwire_true(): hiwire_false();
Py_DECREF(pykey);
return result;
}
int _pyproxy_get(int ptrobj, int idkey) {
PyObject *pyobj = (PyObject *)ptrobj;
PyObject *pykey = js2python(idkey);
PyObject *pyattr = PyObject_GetAttr(pyobj, pykey);
Py_DECREF(pykey);
if (pyattr == NULL) {
PyErr_Clear();
return hiwire_undefined();
}
int idattr = python2js(pyattr);
Py_DECREF(pyattr);
return idattr;
};
int _pyproxy_set(int ptrobj, int idkey, int idval) {
PyObject *pyobj = (PyObject *)ptrobj;
PyObject *pykey = js2python(idkey);
PyObject *pyval = js2python(idval);
int result = PyObject_SetAttr(pyobj, pykey, pyval);
Py_DECREF(pykey);
Py_DECREF(pyval);
if (result) {
return pythonexc2js();
}
return idval;
}
int _pyproxy_deleteProperty(int ptrobj, int idkey) {
PyObject *pyobj = (PyObject *)ptrobj;
PyObject *pykey = js2python(idkey);
int ret = PyObject_DelAttr(pyobj, pykey);
Py_DECREF(pykey);
if (ret) {
return pythonexc2js();
}
return hiwire_undefined();
}
int _pyproxy_ownKeys(int ptrobj) {
PyObject *pyobj = (PyObject *)ptrobj;
PyObject *pydir = PyObject_Dir(pyobj);
if (pydir == NULL) {
return pythonexc2js();
}
int iddir = hiwire_array();
Py_ssize_t n = PyList_Size(pydir);
for (Py_ssize_t i = 0; i < n; ++i) {
PyObject *pyentry = PyList_GetItem(pydir, i);
int identry = python2js(pyentry);
hiwire_push_array(iddir, identry);
hiwire_decref(identry);
}
Py_DECREF(pydir);
return iddir;
}
int _pyproxy_enumerate(int ptrobj) {
return _pyproxy_ownKeys(ptrobj);
}
int _pyproxy_apply(int ptrobj, int idargs) {
PyObject *pyobj = (PyObject *)ptrobj;
Py_ssize_t length = hiwire_get_length(idargs);
PyObject *pyargs = PyTuple_New(length);
for (Py_ssize_t i = 0; i < length; ++i) {
int iditem = hiwire_get_member_int(idargs, i);
PyObject *pyitem = js2python(iditem);
PyTuple_SET_ITEM(pyargs, i, pyitem);
hiwire_decref(iditem);
}
PyObject *pyresult = PyObject_Call(pyobj, pyargs, NULL);
if (pyresult == NULL) {
Py_DECREF(pyargs);
return pythonexc2js();
}
int idresult = python2js(pyresult);
Py_DECREF(pyresult);
Py_DECREF(pyargs);
return idresult;
}
EM_JS(int, pyproxy_new, (int ptrobj), {
var target = function() {};
target['$$'] = { ptr: ptrobj, type: 'PyProxy' };
return Module.hiwire_new_value(new Proxy(target, Module.PyProxy));
});
EM_JS(int, pyproxy_init, (), {
Module.PyProxy = {
getPtr: function(jsobj) {
return jsobj['$$']['ptr'];
},
isPyProxy: function(jsobj) {
return jsobj['$$'] !== undefined && jsobj['$$']['type'] === 'PyProxy';
},
isExtensible: function() { return true },
has: function (jsobj, jskey) {
ptrobj = this.getPtr(jsobj);
var idkey = Module.hiwire_new_value(jskey);
var result = __pyproxy_has(ptrobj, idkey) != 0;
Module.hiwire_decref(idkey);
return result;
},
get: function (jsobj, jskey) {
if (jskey === 'toString') {
return function() {
if (window.pyodide.repr === undefined) {
window.pyodide.repr = window.pyodide.pyimport('repr');
}
return window.pyodide.repr(jsobj);
}
} else if (jskey === '$$') {
return jsobj['$$'];
}
ptrobj = this.getPtr(jsobj);
var idkey = Module.hiwire_new_value(jskey);
var idresult = __pyproxy_get(ptrobj, idkey);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idkey);
Module.hiwire_decref(idresult);
return jsresult;
},
set: function (jsobj, jskey, jsval) {
ptrobj = this.getPtr(jsobj);
var idkey = Module.hiwire_new_value(jskey);
var idval = Module.hiwire_new_value(jsval);
var idresult = __pyproxy_set(ptrobj, idkey, idval);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idkey);
Module.hiwire_decref(idval);
Module.hiwire_decref(idresult);
return jsresult;
},
deleteProperty: function (jsobj, jskey) {
ptrobj = this.getPtr(jsobj);
var idkey = Module.hiwire_new_value(jskey);
var idresult = __pyproxy_deleteProperty(ptrobj, idkey);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
Module.hiwire_decref(idkey);
return jsresult;
},
ownKeys: function (jsobj) {
ptrobj = this.getPtr(jsobj);
var idresult = __pyproxy_ownKeys(ptrobj);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
jsresult.push('toString');
jsresult.push('prototype');
return jsresult;
},
enumerate: function (jsobj) {
ptrobj = this.getPtr(jsobj);
var idresult = __pyproxy_enumerate(ptrobj);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
jsresult.push('toString');
jsresult.push('prototype');
return jsresult;
},
apply: function (jsobj, jsthis, jsargs) {
ptrobj = this.getPtr(jsobj);
var idargs = Module.hiwire_new_value(jsargs);
var idresult = __pyproxy_apply(ptrobj, idargs);
var jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
Module.hiwire_decref(idargs);
return jsresult;
},
};
return 0;
});
#include "pyproxy.hpp"
using emscripten::val;
val Py::makeProxy(PyObject *obj) {
Py_INCREF(obj);
return val::global("Proxy").new_(val(obj), val::global("pyodide")["Py"]);
}
bool Py::isExtensible(val obj, val proxy) {
return true;
}
bool Py::has(val obj, val idx) {
PyObject *x = obj.as<PyObject *>(emscripten::allow_raw_pointers());
PyObject *pyidx = jsToPython(idx);
bool result = PyObject_HasAttr(x, pyidx) ? true: false;
Py_DECREF(pyidx);
return result;
}
val Py::get(val obj, val idx, val proxy) {
if (idx.equals(val("$$"))) {
return obj["$$"];
}
PyObject *x = obj.as<PyObject *>(emscripten::allow_raw_pointers());
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;
};
val Py::set(val obj, val idx, val value, val proxy) {
PyObject *x = obj.as<PyObject *>(emscripten::allow_raw_pointers());
PyObject *pyidx = jsToPython(idx);
PyObject *pyvalue = jsToPython(value);
int ret = PyObject_SetAttr(x, pyidx, pyvalue);
Py_DECREF(pyidx);
Py_DECREF(pyvalue);
if (ret) {
return pythonExcToJs();
}
return value;
}
val Py::deleteProperty(val obj, val idx) {
PyObject *x = obj.as<PyObject *>(emscripten::allow_raw_pointers());
PyObject *pyidx = jsToPython(idx);
int ret = PyObject_DelAttr(x, pyidx);
Py_DECREF(pyidx);
if (ret) {
return pythonExcToJs();
}
return val::global("undefined");
}
val Py::ownKeys(val obj) {
PyObject *x = obj.as<PyObject *>(emscripten::allow_raw_pointers());
PyObject *dir = PyObject_Dir(x);
if (dir == NULL) {
return pythonExcToJs();
}
val result = val::global("Array").new_();
result.call<int>("push", val("$$"));
Py_ssize_t n = PyList_Size(dir);
for (Py_ssize_t i = 0; i < n; ++i) {
PyObject *entry = PyList_GetItem(dir, i);
result.call<int>("push", pythonToJs(entry));
}
Py_DECREF(dir);
return result;
}
val Py::enumerate(val obj) {
return Py::ownKeys(obj);
}
val PyCallable::makeCallableProxy(PyObject *obj) {
return val::global("pyodide").call<val>("makeCallableProxy", PyCallable(obj));
}
val PyCallable::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;
}
#ifndef PYPROXY_H
#define PYPROXY_H
/** Makes Python objects usable from Javascript.
*/
// This implements the Javascript Proxy handler interface as defined here:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
int pyproxy_new(int obj);
int pyproxy_init();
#endif /* PYPROXY_H */
#ifndef PYPROXY_H
#define PYPROXY_H
/** Makes Python objects usable from Javascript.
*/
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <Python.h>
#include "js2python.hpp"
#include "python2js.hpp"
// This implements the Javascript Proxy handler interface as defined here:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
class Py {
public:
static emscripten::val makeProxy(PyObject *obj);
static bool isExtensible(
emscripten::val obj, emscripten::val proxy);
static bool has(
emscripten::val obj, emscripten::val idx);
static emscripten::val get(
emscripten::val obj, emscripten::val idx, emscripten::val proxy);
static emscripten::val set(
emscripten::val obj, emscripten::val idx, emscripten::val value,
emscripten::val proxy);
static emscripten::val deleteProperty(
emscripten::val obj, emscripten::val idx);
static emscripten::val ownKeys(
emscripten::val obj);
static emscripten::val enumerate(
emscripten::val obj);
};
class PyCallable {
public:
PyObject *x;
PyCallable(PyObject *x_) : x(x_) {
Py_INCREF(x);
}
PyCallable(const PyCallable& o) : x(o.x) {
Py_INCREF(x);
}
~PyCallable() {
Py_DECREF(x);
}
emscripten::val call(
emscripten::val args, emscripten::val kwargs);
static emscripten::val makeCallableProxy(PyObject *obj);
};
#endif /* PYPROXY_H */
......@@ -118,9 +118,7 @@ For example, say we had the following Python function that we wanted to call fro
%% code {"language":"py"}
# python
def square(x, integer=False):
if integer:
x = int(x)
def square(x):
return x * x
%% md
......@@ -129,14 +127,14 @@ Since calling conventions are a bit different in Python than in Javascript, all
%% js
// javascript
var square = pyodide.pyimport("square")
square([2.5], {integer: true})
square(2.5)
%% md
This is equivalent to the following Python syntax:
%% code {"language":"py"}
# python
square(2.5, integer=True)
square(2.5)
%% md
You can also get the attributes of objects in a similar way. Say we had an instance of the following Python custom class:
......
#include "python2js.h"
#include <emscripten.h>
#include "hiwire.h"
#include "jsproxy.h"
#include "pyproxy.h"
int pythonexc2js() {
PyObject *type;
PyObject *value;
PyObject *traceback;
int no_traceback = 0;
PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback);
int excval = -1;
int exc;
if (type == NULL || type == Py_None ||
value == NULL || value == Py_None) {
excval = hiwire_string_utf8((int)"No exception type or value");
PyErr_Print();
PyErr_Clear();
goto exit;
}
PyObject *tbmod = PyImport_ImportModule("traceback");
if (tbmod == NULL) {
PyObject *repr = PyObject_Repr(value);
if (repr == NULL) {
excval = hiwire_string_utf8((int)"Could not get repr for exception");
} else {
excval = python2js(repr);
Py_DECREF(repr);
}
} else {
PyObject *format_exception;
if (traceback == NULL || traceback == Py_None) {
no_traceback = 1;
format_exception = PyObject_GetAttrString(tbmod, "format_exception_only");
} else {
format_exception = PyObject_GetAttrString(tbmod, "format_exception");
}
if (format_exception == NULL) {
excval = hiwire_string_utf8((int)"Could not get format_exception function");
} else {
PyObject *pylines;
if (no_traceback) {
pylines = PyObject_CallFunctionObjArgs
(format_exception, type, value, NULL);
} else {
pylines = PyObject_CallFunctionObjArgs
(format_exception, type, value, traceback, NULL);
}
if (pylines == NULL) {
excval = hiwire_string_utf8((int)"Error calling traceback.format_exception");
PyErr_Print();
PyErr_Clear();
goto exit;
} else {
PyObject *newline = PyUnicode_FromString("");
PyObject *pystr = PyUnicode_Join(newline, pylines);
printf("Python exception:\n");
printf("%s\n", PyUnicode_AsUTF8(pystr));
excval = python2js(pystr);
Py_DECREF(pystr);
Py_DECREF(newline);
Py_DECREF(pylines);
}
Py_DECREF(format_exception);
}
Py_DECREF(tbmod);
}
exit:
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
PyErr_Clear();
hiwire_throw_error(excval);
return -1;
}
static int is_type_name(PyObject *x, const char *name) {
PyObject *x_type = PyObject_Type(x);
if (x_type == NULL) {
// If we can't get a type, that's probably ok in this case...
PyErr_Clear();
return 0;
}
PyObject *x_type_name = PyObject_Repr(x_type);
Py_DECREF(x_type);
int result = (PyUnicode_CompareWithASCIIString(x_type_name, name) == 0);
Py_DECREF(x_type_name);
return result;
}
int python2js(PyObject *x) {
if (x == Py_None) {
return hiwire_undefined();
} else if (x == Py_True) {
return hiwire_true();
} else if (x == Py_False) {
return hiwire_false();
} else if (PyLong_Check(x)) {
long x_long = PyLong_AsLongLong(x);
if (x_long == -1 && PyErr_Occurred()) {
return pythonexc2js();
}
return hiwire_int(x_long);
} else if (PyFloat_Check(x)) {
double x_double = PyFloat_AsDouble(x);
if (x_double == -1.0 && PyErr_Occurred()) {
return pythonexc2js();
}
return hiwire_double(x_double);
} else if (PyUnicode_Check(x)) {
Py_ssize_t length;
char *chars = PyUnicode_AsUTF8AndSize(x, &length);
if (chars == NULL) {
return pythonexc2js();
}
return hiwire_string_utf8_length((int)(void *)chars, length);
} else if (PyBytes_Check(x)) {
char *x_buff;
Py_ssize_t length;
if (PyBytes_AsStringAndSize(x, &x_buff, &length)) {
return pythonexc2js();
}
return hiwire_bytes((int)(void *)x_buff, length);
} else if (JsProxy_Check(x)) {
return JsProxy_AsJs(x);
} else if (PyList_Check(x) || is_type_name(x, "<class 'numpy.ndarray'>")) {
int jsarray = hiwire_array();
size_t length = PySequence_Size(x);
for (size_t i = 0; i < length; ++i) {
PyObject *pyitem = PySequence_GetItem(x, i);
if (pyitem == NULL) {
// If something goes wrong converting the sequence (as is the case with
// Pandas data frames), fallback to the Python object proxy
hiwire_decref(jsarray);
PyErr_Clear();
Py_INCREF(x);
return pyproxy_new((int)x);
}
int jsitem = python2js(pyitem);
if (jsitem == -1) {
Py_DECREF(pyitem);
hiwire_decref(jsarray);
return pythonexc2js();
}
Py_DECREF(pyitem);
hiwire_push_array(jsarray, jsitem);
hiwire_decref(jsitem);
}
return jsarray;
} else if (PyDict_Check(x)) {
int jsdict = hiwire_object();
PyObject *pykey, *pyval;
Py_ssize_t pos = 0;
while (PyDict_Next(x, &pos, &pykey, &pyval)) {
int jskey = python2js(pykey);
if (jskey == -1) {
hiwire_decref(jsdict);
// TODO: Return a proxy instead here???
return pythonexc2js();
}
int jsval = python2js(pyval);
if (jsval == -1) {
// TODO: Return a proxy instead here???
hiwire_decref(jskey);
hiwire_decref(jsdict);
return pythonexc2js();
}
hiwire_push_object_pair(jsdict, jskey, jsval);
hiwire_decref(jskey);
hiwire_decref(jsval);
}
return jsdict;
} else {
Py_INCREF(x);
return pyproxy_new((int)x);
}
}
int python2js_init() {
return 0;
}
#include "python2js.hpp"
#include "jsproxy.hpp"
#include "pyproxy.hpp"
using emscripten::val;
static val *undefined;
val pythonExcToJs() {
PyObject *type;
PyObject *value;
PyObject *traceback;
bool no_traceback = false;
PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback);
val excval("");
if (type == NULL || type == Py_None ||
value == NULL || value == Py_None) {
excval = val("No exception type or value");
PyErr_Print();
PyErr_Clear();
goto exit;
}
{
PyObject *tbmod = PyImport_ImportModule("traceback");
if (tbmod == NULL) {
excval = pythonToJs(PyObject_Repr(value));
} else {
PyObject *format_exception;
if (traceback == NULL || traceback == Py_None) {
no_traceback = true;
format_exception = PyObject_GetAttrString(tbmod, "format_exception_only");
} else {
format_exception = PyObject_GetAttrString(tbmod, "format_exception");
}
if (format_exception == NULL) {
excval = val("Couldn't get format_exception function");
} else {
PyObject *pylines;
if (no_traceback) {
pylines = PyObject_CallFunctionObjArgs
(format_exception, type, value, NULL);
} else {
pylines = PyObject_CallFunctionObjArgs
(format_exception, type, value, traceback, NULL);
}
if (pylines == NULL) {
excval = val("Error calling traceback.format_exception");
PyErr_Print();
PyErr_Clear();
goto exit;
} else {
PyObject *newline = PyUnicode_FromString("\n");
PyObject *pystr = PyUnicode_Join(newline, pylines);
PyObject_Print(pystr, stderr, 0);
excval = pythonToJs(pystr);
Py_DECREF(pystr);
Py_DECREF(newline);
Py_DECREF(pylines);
}
Py_DECREF(format_exception);
}
Py_DECREF(tbmod);
}
}
exit:
val exc = val::global("Error").new_(excval);
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
PyErr_Clear();
return exc;
}
static bool isTypeName(PyObject *x, const char *name) {
PyObject *x_type = PyObject_Type(x);
if (x_type == NULL) {
// If we can't get a type, that's probably ok in this case...
PyErr_Clear();
return false;
}
PyObject *x_type_name = PyObject_Repr(x_type);
Py_DECREF(x_type);
bool result = (PyUnicode_CompareWithASCIIString(x_type_name, name) == 0);
Py_DECREF(x_type_name);
return result;
}
val pythonToJs(PyObject *x) {
if (PyBytes_Check(x)) {
char *x_buff;
Py_ssize_t length;
PyBytes_AsStringAndSize(x, &x_buff, &length);
return val::global("Uint8ClampedArray").new_(
val(emscripten::typed_memory_view(length, (unsigned char *)x_buff)));
} else if (x == Py_None) {
return val(*undefined);
} else if (x == Py_True) {
return val(true);
} else if (x == Py_False) {
return val(false);
} else if (PyLong_Check(x)) {
long x_long = PyLong_AsLongLong(x);
if (x_long == -1 && PyErr_Occurred()) {
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 val(x_double);
} else if (PyUnicode_Check(x)) {
// TODO: Not clear whether this is UTF-16 or UCS2
// TODO: This is doing two copies. Can we reduce?
Py_ssize_t length;
wchar_t *chars = PyUnicode_AsWideCharString(x, &length);
if (chars == NULL) {
return pythonExcToJs();
}
std::wstring x_str(chars, length);
PyMem_Free(chars);
return val(x_str);
} else if (JsProxy_Check(x)) {
return JsProxy_AsVal(x);
} else if (PyList_Check(x) || isTypeName(x, "<class 'numpy.ndarray'>")) {
val array = val::global("Array");
val x_array = array.new_();
size_t length = PySequence_Size(x);
for (size_t i = 0; i < length; ++i) {
PyObject *item = PySequence_GetItem(x, i);
if (item == NULL) {
// If something goes wrong converting the sequence (as is the case with
// Pandas data frames), fallback to the Python object proxy
PyErr_Clear();
return Py::makeProxy(x);
}
x_array.call<int>("push", pythonToJs(item));
Py_DECREF(item);
}
return x_array;
} else if (PyDict_Check(x)) {
val object = val::global("Object");
val x_object = object.new_();
PyObject *k, *v;
Py_ssize_t pos = 0;
while (PyDict_Next(x, &pos, &k, &v)) {
x_object.set(pythonToJs(k), pythonToJs(v));
}
return x_object;
} else if (PyCallable_Check(x)) {
return PyCallable::makeCallableProxy(x);
} else {
return Py::makeProxy(x);
}
}
int pythonToJs_Ready() {
undefined = new val(val::global("undefined"));
return 0;
}
......@@ -4,24 +4,21 @@
/** Utilities to convert Python objects to Javascript.
*/
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <Python.h>
/** Convert the active Python exception into a Javascript Error object.
* \return A Javascript Error object
*/
emscripten::val pythonExcToJs();
int pythonexc2js();
/** Convert a Python object to a Javascript object.
* \param The Python object
* \return The Javascript object -- might be an Error object in the case of an exception.
*/
emscripten::val pythonToJs(PyObject *x);
int python2js(PyObject *x);
/** Set up the global state for this module.
*/
int pythonToJs_Ready();
int python2js_init();
#endif /* PYTHON2JS_H */
#include "runpython.hpp"
#include <codecvt>
#include <locale>
#include "runpython.h"
#include <emscripten.h>
#include <Python.h>
#include <node.h> // from Python
#include "jsimport.hpp"
#include "python2js.hpp"
#include "python2js.h"
#include "hiwire.h"
using emscripten::val;
extern PyObject *globals;
static bool is_whitespace(char x) {
static int is_whitespace(char x) {
switch (x) {
case ' ':
case '\n':
case '\r':
case '\t':
return true;
return 1;
default:
return false;
return 0;
}
}
val runPython(std::wstring code) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
std::string code_utf8 = conv.to_bytes(code);
std::string::iterator last_line = code_utf8.end();
int _runPython(char *code) {
char *last_line = code;
while (*last_line != 0) {
++last_line;
}
size_t length = last_line - code;
PyCompilerFlags cf;
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
PyEval_MergeCompilerFlags(&cf);
if (code_utf8.size() == 0) {
return pythonToJs(Py_None);
if (length == 0) {
return hiwire_undefined();
}
// Find the last non-whitespace-only line since that will provide the result
// TODO: This way to find the last line will probably break in many ways
last_line--;
for (; last_line != code_utf8.begin() && is_whitespace(*last_line); last_line--) {}
for (; last_line != code_utf8.begin() && *last_line != '\n'; last_line--) {}
for (; last_line != code && is_whitespace(*last_line); last_line--) {}
for (; last_line != code && *last_line != '\n'; last_line--) {}
int do_eval_line = 1;
_node *co;
co = PyParser_SimpleParseStringFlags(&*last_line, Py_eval_input, cf.cf_flags);
struct _node *co;
co = PyParser_SimpleParseStringFlags(last_line, Py_eval_input, cf.cf_flags);
if (co == NULL) {
do_eval_line = 0;
PyErr_Clear();
......@@ -52,14 +52,14 @@ val runPython(std::wstring code) {
PyNode_Free(co);
PyObject *ret;
if (do_eval_line == 0 || last_line != code_utf8.begin()) {
if (do_eval_line == 0 || last_line != code) {
if (do_eval_line) {
*last_line = 0;
last_line++;
}
ret = PyRun_StringFlags(&*code_utf8.begin(), Py_file_input, globals, globals, &cf);
ret = PyRun_StringFlags(code, Py_file_input, globals, globals, &cf);
if (ret == NULL) {
return pythonExcToJs();
return pythonexc2js();
}
Py_DECREF(ret);
}
......@@ -70,18 +70,31 @@ val runPython(std::wstring code) {
ret = Py_None;
break;
case 1:
ret = PyRun_StringFlags(&*last_line, Py_eval_input, globals, globals, &cf);
ret = PyRun_StringFlags(last_line, Py_eval_input, globals, globals, &cf);
break;
case 2:
ret = PyRun_StringFlags(&*last_line, Py_file_input, globals, globals, &cf);
ret = PyRun_StringFlags(last_line, Py_file_input, globals, globals, &cf);
break;
}
if (ret == NULL) {
return pythonExcToJs();
return pythonexc2js();
}
val result = pythonToJs(ret);
int id = python2js(ret);
Py_DECREF(ret);
return result;
return id;
}
EM_JS(int, runpython_init, (), {
Module.runPython = function (code) {
var pycode = allocate(intArrayFromString(code), 'i8', ALLOC_NORMAL);
var idresult = Module.__runPython(pycode);
jsresult = Module.hiwire_get_value(idresult);
Module.hiwire_decref(idresult);
_free(pycode);
return jsresult;
};
return 0;
});
......@@ -4,14 +4,7 @@
/** The primary entry point function that runs Python code.
*/
#include <string>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
/** The primary entry point function that runs Python code.
*/
emscripten::val runPython(std::wstring code);
int runpython_init();
#endif /* RUNPYTHON_H */
......@@ -138,7 +138,6 @@ class FigureCanvasWasm(backend_agg.FigureCanvasAgg):
rubberband.setAttribute('tabindex', '0')
# Event handlers are added to the canvas "on top", even though most of the
# activity happens in the canvas below.
rubberband.addEventListener('click', self.onclick)
rubberband.addEventListener('mousemove', self.onmousemove)
rubberband.addEventListener('mouseup', self.onmouseup)
rubberband.addEventListener('mousedown', self.onmousedown)
......@@ -173,9 +172,9 @@ class FigureCanvasWasm(backend_agg.FigureCanvasAgg):
canvas = self.get_element('canvas')
image_data = ImageData.new(
self.buffer_rgba(),
width, height);
ctx = canvas.getContext("2d");
ctx.putImageData(image_data, 0, 0);
width, height)
ctx = canvas.getContext("2d")
ctx.putImageData(image_data, 0, 0)
self._idle_scheduled = False
def draw_idle(self):
......@@ -201,10 +200,6 @@ class FigureCanvasWasm(backend_agg.FigureCanvasAgg):
button = 3
return x, y, button
def onclick(self, event):
x, y, button = self._convert_mouse_event(event)
self.button_click_event(x, y, button, guiEvent=event)
def onmousemove(self, event):
x, y, button = self._convert_mouse_event(event)
self.motion_notify_event(x, y, guiEvent=event)
......@@ -218,7 +213,7 @@ class FigureCanvasWasm(backend_agg.FigureCanvasAgg):
self.button_press_event(x, y, button, guiEvent=event)
def onmouseenter(self, event):
window.addEventListener('contextmenu', ignore)
return
# When the mouse is over the figure, get keyboard focus
self.get_element('rubberband').focus()
self.enter_notify_event(guiEvent=event)
......
......@@ -76,5 +76,5 @@ if pytest is not None:
try:
yield selenium
finally:
print('\n'.join(selenium.logs))
print('\n'.join(str(x) for x in selenium.logs))
selenium.driver.quit()
......@@ -302,7 +302,7 @@ test_macurl2path
test_mailbox crash
test_mailcap unknown
test_marshal
test_math
test_math floating-point
test_memoryio
test_memoryview
test_metaclass
......
......@@ -2,11 +2,6 @@ def test_matplotlib(selenium):
selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt")
selenium.run("x = plt.plot([1,2,3])")
img = selenium.run(
"plt.gcf()._repr_html_().src"
)
assert img.startswith('data:image/png;base64,')
assert len(img) == 26766
def test_svg(selenium):
......
......@@ -4,4 +4,5 @@ def test_pandas(selenium):
def test_extra_import(selenium):
selenium.load_package("pandas")
selenium.run("from pandas import Series, DataFrame, Panel")
......@@ -2,6 +2,8 @@ import os
import pathlib
import time
from selenium.common.exceptions import JavascriptException
def test_init(selenium):
assert 'Python initialization complete' in selenium.logs
......@@ -19,13 +21,88 @@ def test_print(selenium):
assert 'This should be logged' in selenium.logs
def test_python2js(selenium):
assert selenium.run_js('return pyodide.runPython("None") === undefined')
assert selenium.run_js('return pyodide.runPython("True") === true')
assert selenium.run_js('return pyodide.runPython("False") === false')
assert selenium.run_js('return pyodide.runPython("42") === 42')
assert selenium.run_js('return pyodide.runPython("3.14") === 3.14')
assert selenium.run_js(
'return pyodide.runPython("\'碘化物\'") === "碘化物"')
assert selenium.run_js(
'let x = pyodide.runPython("b\'bytes\'");\n'
'return (x instanceof window.Uint8Array) && '
'(x.length === 5) && '
'(x[0] === 98)')
assert selenium.run_js(
'let x = pyodide.runPython("[1, 2, 3]");\n'
'return (x instanceof window.Array) && (x.length === 3) && '
'(x[0] == 1) && (x[1] == 2) && (x[2] == 3)')
assert selenium.run_js(
'let x = pyodide.runPython("{42: 64}");\n'
'return (typeof x === "object") && '
'(x[42] === 64)')
assert selenium.run_js(
'let x = pyodide.runPython("open(\'/foo.txt\', \'wb\')")\n'
'return (x.tell() === 0)\n')
def test_pythonexc2js(selenium):
try:
selenium.run_js('return pyodide.runPython("5 / 0")')
except JavascriptException as e:
assert('ZeroDivisionError' in str(e))
def test_js2python(selenium):
selenium.run_js(
'window.jsstring = "碘化物";\n'
'window.jsnumber0 = 42;\n'
'window.jsnumber1 = 42.5;\n'
'window.jsundefined = undefined;\n'
'window.jsnull = null;\n'
'window.jstrue = true;\n'
'window.jsfalse = false;\n'
'window.jspython = pyodide.pyimport("open");\n'
'window.jsbytes = new Uint8Array([1, 2, 3]);\n'
'window.jsobject = new XMLHttpRequest();\n'
)
assert selenium.run(
'from js import jsstring\n'
'jsstring == "碘化物"')
assert selenium.run(
'from js import jsnumber0\n'
'jsnumber0 == 42')
assert selenium.run(
'from js import jsnumber1\n'
'jsnumber1 == 42.5')
assert selenium.run(
'from js import jsundefined\n'
'jsundefined is None')
assert selenium.run(
'from js import jstrue\n'
'jstrue is True')
assert selenium.run(
'from js import jsfalse\n'
'jsfalse is False')
assert selenium.run(
'from js import jspython\n'
'jspython is open')
assert selenium.run(
'from js import jsbytes\n'
'jsbytes == b"\x01\x02\x03"')
assert selenium.run(
'from js import jsobject\n'
'str(jsobject) == "[object XMLHttpRequest]"')
def test_import_js(selenium):
result = selenium.run(
"from js import window\nwindow.title = 'Foo'\nwindow.title")
assert result == 'Foo'
def test_py_proxy(selenium):
def test_pyproxy(selenium):
selenium.run(
"class Foo:\n"
" bar = 42\n"
......@@ -40,19 +117,20 @@ def test_py_proxy(selenium):
assert selenium.run("f.baz") == 32
assert set(selenium.run_js(
"return Object.getOwnPropertyNames(pyodide.pyimport('f'))")) == set(
['$$', '__class__', '__delattr__', '__dict__', '__dir__',
['__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', 'bar', 'baz',
'get_value'])
'get_value', 'toString', 'prototype'])
assert selenium.run("hasattr(f, 'baz')") == True
selenium.run_js("delete pyodide.pyimport('f').baz")
assert selenium.run("hasattr(f, 'baz')") == False
assert selenium.run_js("return pyodide.pyimport('f').toString()").startswith('<Foo')
def test_js_proxy(selenium):
def test_jsproxy(selenium):
assert selenium.run(
"from js import document\n"
"el = document.createElement('div')\n"
......@@ -61,6 +139,16 @@ def test_js_proxy(selenium):
) == 1
assert selenium.run(
"document.body.children[0].tagName") == 'DIV'
assert selenium.run(
"repr(document)") == '[object HTMLDocument]'
selenium.run_js(
"window.square = function (x) { return x*x; }")
assert selenium.run(
"from js import square\n"
"square(2)") == 4
assert selenium.run(
"from js import ImageData\n"
"ImageData.new(64, 64)")
def test_open_url(selenium):
......@@ -70,19 +158,12 @@ def test_open_url(selenium):
def test_run_core_python_test(python_test, selenium):
selenium.run(
"import sys\n"
"exitcode = -1\n"
"def exit(n=0):\n"
" global exitcode\n"
" exitcode = n\n"
" raise SystemExit()\n\n"
"sys.exit = exit\n")
selenium.run(
"from test.libregrtest import main\n"
"main(['{}'], verbose=True, verbose3=True)".format(python_test))
exitcode = selenium.run("exitcode")
assert exitcode == 0
try:
selenium.run(
"from test.libregrtest import main\n"
"main(['{}'], verbose=True, verbose3=True)".format(python_test))
except JavascriptException as e:
assert str(e).strip().endswith('SystemExit: 0')
def pytest_generate_tests(metafunc):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment