summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ports/webassembly/Makefile56
-rw-r--r--ports/webassembly/api.js146
-rw-r--r--ports/webassembly/lexer_dedent.c105
-rw-r--r--ports/webassembly/lexer_dedent.h36
-rw-r--r--ports/webassembly/main.c56
-rw-r--r--ports/webassembly/modjs.c55
-rw-r--r--ports/webassembly/modjsffi.c80
-rw-r--r--ports/webassembly/mpconfigport.h8
-rw-r--r--ports/webassembly/objjsproxy.c330
-rw-r--r--ports/webassembly/objpyproxy.js191
-rw-r--r--ports/webassembly/proxy_c.c281
-rw-r--r--ports/webassembly/proxy_c.h58
-rw-r--r--ports/webassembly/proxy_js.js222
13 files changed, 1615 insertions, 9 deletions
diff --git a/ports/webassembly/Makefile b/ports/webassembly/Makefile
index e8f27d862..2a5669392 100644
--- a/ports/webassembly/Makefile
+++ b/ports/webassembly/Makefile
@@ -45,7 +45,34 @@ CFLAGS += -std=c99 -Wall -Werror -Wdouble-promotion -Wfloat-conversion
CFLAGS += -Os -DNDEBUG
CFLAGS += $(INC)
+EXPORTED_FUNCTIONS_EXTRA += ,\
+ _mp_js_do_exec,\
+ _mp_js_do_import,\
+ _mp_js_register_js_module,\
+ _proxy_c_init,\
+ _proxy_c_to_js_call,\
+ _proxy_c_to_js_delete_attr,\
+ _proxy_c_to_js_dir,\
+ _proxy_c_to_js_get_array,\
+ _proxy_c_to_js_get_dict,\
+ _proxy_c_to_js_get_type,\
+ _proxy_c_to_js_has_attr,\
+ _proxy_c_to_js_lookup_attr,\
+ _proxy_c_to_js_store_attr,\
+ _proxy_convert_mp_to_js_obj_cside
+
+EXPORTED_RUNTIME_METHODS_EXTRA += ,\
+ PATH,\
+ PATH_FS,\
+ UTF8ToString,\
+ getValue,\
+ lengthBytesUTF8,\
+ setValue,\
+ stringToUTF8
+
JSFLAGS += -s EXPORTED_FUNCTIONS="\
+ _free,\
+ _malloc,\
_mp_js_init,\
_mp_js_init_repl,\
_mp_js_do_str,\
@@ -58,6 +85,7 @@ JSFLAGS += -s EXPORTED_RUNTIME_METHODS="\
FS$(EXPORTED_RUNTIME_METHODS_EXTRA)"
JSFLAGS += --js-library library.js
JSFLAGS += -s SUPPORT_LONGJMP=emscripten
+JSFLAGS += -s MODULARIZE -s EXPORT_NAME=_createMicroPythonModule
################################################################################
# Source files and libraries.
@@ -71,13 +99,21 @@ SRC_SHARED = $(addprefix shared/,\
)
SRC_C += \
+ lexer_dedent.c \
main.c \
+ modjs.c \
+ modjsffi.c \
mphalport.c \
+ objjsproxy.c \
+ proxy_c.c \
# List of sources for qstr extraction.
SRC_QSTR += $(SRC_C) $(SRC_SHARED)
-SRC_JS ?= wrapper.js
+SRC_JS += \
+ api.js \
+ objpyproxy.js \
+ proxy_js.js \
OBJ += $(PY_O)
OBJ += $(addprefix $(BUILD)/, $(SRC_SHARED:.c=.o))
@@ -86,23 +122,25 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
################################################################################
# Main targets.
-.PHONY: all min test
+.PHONY: all min test test_min
-all: $(BUILD)/micropython.js
+all: $(BUILD)/micropython.mjs
-$(BUILD)/micropython.js: $(OBJ) library.js $(SRC_JS)
+$(BUILD)/micropython.mjs: $(OBJ) library.js $(SRC_JS)
$(ECHO) "LINK $@"
$(Q)emcc $(LDFLAGS) -o $@ $(OBJ) $(JSFLAGS)
$(Q)cat $(SRC_JS) >> $@
-$(BUILD)/micropython.min.js: $(BUILD)/micropython.js
+$(BUILD)/micropython.min.mjs: $(BUILD)/micropython.mjs
$(TERSER) $< --compress --module -o $@
-min: $(BUILD)/micropython.min.js
+min: $(BUILD)/micropython.min.mjs
+
+test: $(BUILD)/micropython.mjs $(TOP)/tests/run-tests.py
+ cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py --target webassembly
-test: $(BUILD)/micropython.js $(TOP)/tests/run-tests.py
- $(eval DIRNAME=ports/$(notdir $(CURDIR)))
- cd $(TOP)/tests && MICROPY_MICROPYTHON=../ports/webassembly/node_run.sh ./run-tests.py -j1
+test_min: $(BUILD)/micropython.min.mjs $(TOP)/tests/run-tests.py
+ cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py --target webassembly
################################################################################
# Remaining make rules.
diff --git a/ports/webassembly/api.js b/ports/webassembly/api.js
new file mode 100644
index 000000000..dfe756176
--- /dev/null
+++ b/ports/webassembly/api.js
@@ -0,0 +1,146 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// Options:
+// - heapsize: size in bytes of the MicroPython GC heap.
+// - url: location to load `micropython.mjs`.
+// - stdin: function to return input characters.
+// - stdout: function that takes one argument, and is passed lines of stdout
+// output as they are produced. By default this is handled by Emscripten
+// and in a browser goes to console, in node goes to process.stdout.write.
+// - stderr: same behaviour as stdout but for error output.
+// - linebuffer: whether to buffer line-by-line to stdout/stderr.
+export async function loadMicroPython(options) {
+ const { heapsize, url, stdin, stdout, stderr, linebuffer } = Object.assign(
+ { heapsize: 1024 * 1024, linebuffer: true },
+ options,
+ );
+ const Module = {};
+ Module.locateFile = (path, scriptDirectory) =>
+ url || scriptDirectory + path;
+ Module._textDecoder = new TextDecoder();
+ if (stdin !== undefined) {
+ Module.stdin = stdin;
+ }
+ if (stdout !== undefined) {
+ if (linebuffer) {
+ Module._stdoutBuffer = [];
+ Module.stdout = (c) => {
+ if (c === 10) {
+ stdout(
+ Module._textDecoder.decode(
+ new Uint8Array(Module._stdoutBuffer),
+ ),
+ );
+ Module._stdoutBuffer = [];
+ } else {
+ Module._stdoutBuffer.push(c);
+ }
+ };
+ } else {
+ Module.stdout = (c) => stdout(new Uint8Array([c]));
+ }
+ }
+ if (stderr !== undefined) {
+ if (linebuffer) {
+ Module._stderrBuffer = [];
+ Module.stderr = (c) => {
+ if (c === 10) {
+ stderr(
+ Module._textDecoder.decode(
+ new Uint8Array(Module._stderrBuffer),
+ ),
+ );
+ Module._stderrBuffer = [];
+ } else {
+ Module._stderrBuffer.push(c);
+ }
+ };
+ } else {
+ Module.stderr = (c) => stderr(new Uint8Array([c]));
+ }
+ }
+ const moduleLoaded = new Promise((r) => {
+ Module.postRun = r;
+ });
+ _createMicroPythonModule(Module);
+ await moduleLoaded;
+ globalThis.Module = Module;
+ proxy_js_init();
+ const pyimport = (name) => {
+ const value = Module._malloc(3 * 4);
+ Module.ccall(
+ "mp_js_do_import",
+ "null",
+ ["string", "pointer"],
+ [name, value],
+ );
+ return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+ };
+ Module.ccall("mp_js_init", "null", ["number"], [heapsize]);
+ Module.ccall("proxy_c_init", "null", [], []);
+ return {
+ _module: Module,
+ PyProxy: PyProxy,
+ FS: Module.FS,
+ globals: {
+ __dict__: pyimport("__main__").__dict__,
+ get(key) {
+ return this.__dict__[key];
+ },
+ set(key, value) {
+ this.__dict__[key] = value;
+ },
+ delete(key) {
+ delete this.__dict__[key];
+ },
+ },
+ registerJsModule(name, module) {
+ const value = Module._malloc(3 * 4);
+ proxy_convert_js_to_mp_obj_jsside(module, value);
+ Module.ccall(
+ "mp_js_register_js_module",
+ "null",
+ ["string", "pointer"],
+ [name, value],
+ );
+ Module._free(value);
+ },
+ pyimport: pyimport,
+ runPython(code) {
+ const value = Module._malloc(3 * 4);
+ Module.ccall(
+ "mp_js_do_exec",
+ "number",
+ ["string", "pointer"],
+ [code, value],
+ );
+ return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+ },
+ };
+}
+
+globalThis.loadMicroPython = loadMicroPython;
diff --git a/ports/webassembly/lexer_dedent.c b/ports/webassembly/lexer_dedent.c
new file mode 100644
index 000000000..555caea89
--- /dev/null
+++ b/ports/webassembly/lexer_dedent.c
@@ -0,0 +1,105 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "lexer_dedent.h"
+
+typedef struct _mp_reader_mem_dedent_t {
+ size_t free_len; // if >0 mem is freed on close by: m_free(beg, free_len)
+ const byte *beg;
+ const byte *cur;
+ const byte *end;
+ size_t dedent_prefix;
+} mp_reader_mem_dedent_t;
+
+// Work out the amount of common whitespace among all non-empty lines.
+static size_t dedent(const byte *text, size_t len) {
+ size_t min_prefix = -1;
+ size_t cur_prefix = 0;
+ bool start_of_line = true;
+ for (const byte *t = text; t < text + len; ++t) {
+ if (*t == '\n') {
+ start_of_line = true;
+ cur_prefix = 0;
+ } else if (start_of_line) {
+ if (unichar_isspace(*t)) {
+ ++cur_prefix;
+ } else {
+ if (cur_prefix < min_prefix) {
+ min_prefix = cur_prefix;
+ if (min_prefix == 0) {
+ return min_prefix;
+ }
+ }
+ start_of_line = false;
+ }
+ }
+ }
+ return min_prefix;
+}
+
+static mp_uint_t mp_reader_mem_dedent_readbyte(void *data) {
+ mp_reader_mem_dedent_t *reader = (mp_reader_mem_dedent_t *)data;
+ if (reader->cur < reader->end) {
+ byte c = *reader->cur++;
+ if (c == '\n') {
+ for (size_t i = 0; i < reader->dedent_prefix; ++i) {
+ if (*reader->cur == '\n') {
+ break;
+ }
+ ++reader->cur;
+ }
+ }
+ return c;
+ } else {
+ return MP_READER_EOF;
+ }
+}
+
+static void mp_reader_mem_dedent_close(void *data) {
+ mp_reader_mem_dedent_t *reader = (mp_reader_mem_dedent_t *)data;
+ if (reader->free_len > 0) {
+ m_del(char, (char *)reader->beg, reader->free_len);
+ }
+ m_del_obj(mp_reader_mem_dedent_t, reader);
+}
+
+static void mp_reader_new_mem_dedent(mp_reader_t *reader, const byte *buf, size_t len, size_t free_len) {
+ mp_reader_mem_dedent_t *rm = m_new_obj(mp_reader_mem_dedent_t);
+ rm->free_len = free_len;
+ rm->beg = buf;
+ rm->cur = buf;
+ rm->end = buf + len;
+ rm->dedent_prefix = dedent(buf, len);
+ reader->data = rm;
+ reader->readbyte = mp_reader_mem_dedent_readbyte;
+ reader->close = mp_reader_mem_dedent_close;
+}
+
+mp_lexer_t *mp_lexer_new_from_str_len_dedent(qstr src_name, const char *str, size_t len, size_t free_len) {
+ mp_reader_t reader;
+ mp_reader_new_mem_dedent(&reader, (const byte *)str, len, free_len);
+ return mp_lexer_new(src_name, reader);
+}
diff --git a/ports/webassembly/lexer_dedent.h b/ports/webassembly/lexer_dedent.h
new file mode 100644
index 000000000..a8cc2526b
--- /dev/null
+++ b/ports/webassembly/lexer_dedent.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef MICROPY_INCLUDED_WEBASSEMBLY_LEXER_DEDENT_H
+#define MICROPY_INCLUDED_WEBASSEMBLY_LEXER_DEDENT_H
+
+#include "py/lexer.h"
+
+// This function creates a new "dedenting lexer" which automatically dedents the input
+// source code if every non-empty line in that source starts with a common whitespace
+// prefix. It does this dedenting inplace as the memory is read.
+mp_lexer_t *mp_lexer_new_from_str_len_dedent(qstr src_name, const char *str, size_t len, size_t free_len);
+
+#endif // MICROPY_INCLUDED_WEBASSEMBLY_LEXER_DEDENT_H
diff --git a/ports/webassembly/main.c b/ports/webassembly/main.c
index ebde8ac70..c1c7a8884 100644
--- a/ports/webassembly/main.c
+++ b/ports/webassembly/main.c
@@ -40,7 +40,9 @@
#include "shared/runtime/pyexec.h"
#include "emscripten.h"
+#include "lexer_dedent.h"
#include "library.h"
+#include "proxy_c.h"
#if MICROPY_ENABLE_COMPILER
int do_str(const char *src, mp_parse_input_kind_t input_kind) {
@@ -113,6 +115,60 @@ void mp_js_init_repl() {
pyexec_event_repl_init();
}
+void mp_js_register_js_module(const char *name, uint32_t *value) {
+ mp_obj_t module_name = MP_OBJ_NEW_QSTR(qstr_from_str(name));
+ mp_obj_t module = proxy_convert_js_to_mp_obj_cside(value);
+ mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map;
+ mp_map_lookup(mp_loaded_modules_map, module_name, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = module;
+}
+
+void mp_js_do_import(const char *name, uint32_t *out) {
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_obj_t ret = mp_import_name(qstr_from_str(name), mp_const_none, MP_OBJ_NEW_SMALL_INT(0));
+ // Return the leaf of the import, eg for "a.b.c" return "c".
+ const char *m = name;
+ const char *n = name;
+ for (;; ++n) {
+ if (*n == '\0' || *n == '.') {
+ if (m != name) {
+ ret = mp_load_attr(ret, qstr_from_strn(m, n - m));
+ }
+ m = n + 1;
+ if (*n == '\0') {
+ break;
+ }
+ }
+ }
+ nlr_pop();
+ proxy_convert_mp_to_js_obj_cside(ret, out);
+ } else {
+ // uncaught exception
+ proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+ }
+}
+
+void mp_js_do_exec(const char *src, uint32_t *out) {
+ // Collect at the top-level, where there are no root pointers from stack/registers.
+ gc_collect_start();
+ gc_collect_end();
+
+ mp_parse_input_kind_t input_kind = MP_PARSE_FILE_INPUT;
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_lexer_t *lex = mp_lexer_new_from_str_len_dedent(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
+ qstr source_name = lex->source_name;
+ mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
+ mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false);
+ mp_obj_t ret = mp_call_function_0(module_fun);
+ nlr_pop();
+ proxy_convert_mp_to_js_obj_cside(ret, out);
+ } else {
+ // uncaught exception
+ proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+ }
+}
+
#if MICROPY_GC_SPLIT_HEAP_AUTO
// The largest new region that is available to become Python heap.
diff --git a/ports/webassembly/modjs.c b/ports/webassembly/modjs.c
new file mode 100644
index 000000000..bed09086a
--- /dev/null
+++ b/ports/webassembly/modjs.c
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/objmodule.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+#if MICROPY_PY_JS
+
+/******************************************************************************/
+// js module
+
+void mp_module_js_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+ mp_obj_jsproxy_t global_this;
+ global_this.ref = 0;
+ mp_obj_jsproxy_attr(MP_OBJ_FROM_PTR(&global_this), attr, dest);
+}
+
+static const mp_rom_map_elem_t mp_module_js_globals_table[] = {
+ { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_js) },
+};
+static MP_DEFINE_CONST_DICT(mp_module_js_globals, mp_module_js_globals_table);
+
+const mp_obj_module_t mp_module_js = {
+ .base = { &mp_type_module },
+ .globals = (mp_obj_dict_t *)&mp_module_js_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_js, mp_module_js);
+MP_REGISTER_MODULE_DELEGATION(mp_module_js, mp_module_js_attr);
+
+#endif // MICROPY_PY_JS
diff --git a/ports/webassembly/modjsffi.c b/ports/webassembly/modjsffi.c
new file mode 100644
index 000000000..d4e61e368
--- /dev/null
+++ b/ports/webassembly/modjsffi.c
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "emscripten.h"
+#include "py/objmodule.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+#if MICROPY_PY_JSFFI
+
+/******************************************************************************/
+// jsffi module
+
+EM_JS(void, proxy_convert_mp_to_js_then_js_to_mp_obj_jsside, (uint32_t * out), {
+ const ret = proxy_convert_mp_to_js_obj_jsside(out);
+ proxy_convert_js_to_mp_obj_jsside_force_double_proxy(ret, out);
+});
+
+static mp_obj_t mp_jsffi_create_proxy(mp_obj_t arg) {
+ uint32_t out[3];
+ proxy_convert_mp_to_js_obj_cside(arg, out);
+ proxy_convert_mp_to_js_then_js_to_mp_obj_jsside(out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_create_proxy_obj, mp_jsffi_create_proxy);
+
+EM_JS(void, proxy_convert_mp_to_js_then_js_to_js_then_js_to_mp_obj_jsside, (uint32_t * out), {
+ const ret = proxy_convert_mp_to_js_obj_jsside(out);
+ const js_obj = PyProxy.toJs(ret);
+ proxy_convert_js_to_mp_obj_jsside(js_obj, out);
+});
+
+static mp_obj_t mp_jsffi_to_js(mp_obj_t arg) {
+ uint32_t out[3];
+ proxy_convert_mp_to_js_obj_cside(arg, out);
+ proxy_convert_mp_to_js_then_js_to_js_then_js_to_mp_obj_jsside(out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_to_js_obj, mp_jsffi_to_js);
+
+static const mp_rom_map_elem_t mp_module_jsffi_globals_table[] = {
+ { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jsffi) },
+
+ { MP_ROM_QSTR(MP_QSTR_JsProxy), MP_ROM_PTR(&mp_type_jsproxy) },
+ { MP_ROM_QSTR(MP_QSTR_create_proxy), MP_ROM_PTR(&mp_jsffi_create_proxy_obj) },
+ { MP_ROM_QSTR(MP_QSTR_to_js), MP_ROM_PTR(&mp_jsffi_to_js_obj) },
+};
+static MP_DEFINE_CONST_DICT(mp_module_jsffi_globals, mp_module_jsffi_globals_table);
+
+const mp_obj_module_t mp_module_jsffi = {
+ .base = { &mp_type_module },
+ .globals = (mp_obj_dict_t *)&mp_module_jsffi_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_jsffi, mp_module_jsffi);
+
+#endif // MICROPY_PY_JSFFI
diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h
index abfbbca79..fc7ba2f82 100644
--- a/ports/webassembly/mpconfigport.h
+++ b/ports/webassembly/mpconfigport.h
@@ -64,6 +64,14 @@
#define MICROPY_VFS_POSIX (MICROPY_VFS)
#define MICROPY_PY_SYS_PLATFORM "webassembly"
+#ifndef MICROPY_PY_JS
+#define MICROPY_PY_JS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
+
+#ifndef MICROPY_PY_JSFFI
+#define MICROPY_PY_JSFFI (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
+
#define MICROPY_EVENT_POLL_HOOK \
do { \
extern void mp_handle_pending(bool); \
diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c
new file mode 100644
index 000000000..a28b791cf
--- /dev/null
+++ b/ports/webassembly/objjsproxy.c
@@ -0,0 +1,330 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "emscripten.h"
+#include "py/objmodule.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+// *FORMAT-OFF*
+EM_JS(bool, lookup_attr, (int jsref, const char *str, uint32_t * out), {
+ const base = proxy_js_ref[jsref];
+ const attr = UTF8ToString(str);
+ if (attr in base) {
+ let value = base[attr];
+ if (typeof value == "function") {
+ if (base !== globalThis) {
+ value = value.bind(base);
+ }
+ }
+ proxy_convert_js_to_mp_obj_jsside(value, out);
+ return true;
+ } else {
+ return false;
+ }
+});
+// *FORMAT-ON*
+
+EM_JS(void, store_attr, (int jsref, const char *attr_ptr, uint32_t * value_ref), {
+ const attr = UTF8ToString(attr_ptr);
+ const value = proxy_convert_mp_to_js_obj_jsside(value_ref);
+ proxy_js_ref[jsref][attr] = value;
+});
+
+EM_JS(void, call0, (int f_ref, uint32_t * out), {
+ // Because of JavaScript "this" semantics, we must extract the target function
+ // to a variable before calling it, so "this" is bound to the correct value.
+ //
+ // In detail:
+ // In JavaScript, proxy_js_ref[f_ref] acts like a function call
+ // proxy_js_ref.at(f_ref), and "this" will be bound to proxy_js_ref if
+ // there is a chain of calls, such as proxy_js_ref.at(f_ref)().
+ // But proxy_js_ref is not "this" in the context of the call, so we
+ // must extract the function to an independent variable and then call
+ // that variable, so that "this" is correct (it will be "undefined").
+
+ const f = proxy_js_ref[f_ref];
+ const ret = f();
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, call1, (int f_ref, uint32_t * a0, uint32_t * out), {
+ const a0_js = proxy_convert_mp_to_js_obj_jsside(a0);
+ const f = proxy_js_ref[f_ref];
+ const ret = f(a0_js);
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, call2, (int f_ref, uint32_t * a0, uint32_t * a1, uint32_t * out), {
+ const a0_js = proxy_convert_mp_to_js_obj_jsside(a0);
+ const a1_js = proxy_convert_mp_to_js_obj_jsside(a1);
+ const f = proxy_js_ref[f_ref];
+ const ret = f(a0_js, a1_js);
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, calln, (int f_ref, uint32_t n_args, uint32_t * value, uint32_t * out), {
+ const f = proxy_js_ref[f_ref];
+ const a = [];
+ for (let i = 0; i < n_args; ++i) {
+ const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
+ a.push(v);
+ }
+ const ret = f(... a);
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, call0_kwarg, (int f_ref, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
+ const f = proxy_js_ref[f_ref];
+ const a = {};
+ for (let i = 0; i < n_kw; ++i) {
+ const k = UTF8ToString(getValue(key + i * 4, "i32"));
+ const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
+ a[k] = v;
+ }
+ const ret = f(a);
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, call1_kwarg, (int f_ref, uint32_t * arg0, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
+ const f = proxy_js_ref[f_ref];
+ const a0 = proxy_convert_mp_to_js_obj_jsside(arg0);
+ const a = {};
+ for (let i = 0; i < n_kw; ++i) {
+ const k = UTF8ToString(getValue(key + i * 4, "i32"));
+ const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
+ a[k] = v;
+ }
+ const ret = f(a0, a);
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, js_reflect_construct, (int f_ref, uint32_t n_args, uint32_t * args, uint32_t * out), {
+ const f = proxy_js_ref[f_ref];
+ const as = [];
+ for (let i = 0; i < n_args; ++i) {
+ as.push(proxy_convert_mp_to_js_obj_jsside(args + i * 4));
+ }
+ const ret = Reflect.construct(f, as);
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, js_get_len, (int f_ref), {
+ return proxy_js_ref[f_ref].length;
+});
+
+EM_JS(void, js_subscr_int, (int f_ref, int idx, uint32_t * out), {
+ const f = proxy_js_ref[f_ref];
+ const ret = f[idx];
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, js_subscr_load, (int f_ref, uint32_t * index_ref, uint32_t * out), {
+ const target = proxy_js_ref[f_ref];
+ const index = python_index_semantics(target, proxy_convert_mp_to_js_obj_jsside(index_ref));
+ const ret = target[index];
+ proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, js_subscr_store, (int f_ref, uint32_t * idx, uint32_t * value), {
+ const f = proxy_js_ref[f_ref];
+ f[proxy_convert_mp_to_js_obj_jsside(idx)] = proxy_convert_mp_to_js_obj_jsside(value);
+});
+
+static void jsproxy_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+ mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_printf(print, "<JsProxy %d>", self->ref);
+}
+
+static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+
+ if (n_kw == 0) {
+ mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, false);
+ } else {
+ mp_arg_check_num(n_args, n_kw, 0, 1, true);
+ uint32_t key[n_kw];
+ uint32_t value[PVN * n_kw];
+ for (int i = 0; i < n_kw; ++i) {
+ key[i] = (uintptr_t)mp_obj_str_get_str(args[n_args + i * 2]);
+ proxy_convert_mp_to_js_obj_cside(args[n_args + i * 2 + 1], &value[i * PVN]);
+ }
+ uint32_t out[3];
+ if (n_args == 0) {
+ call0_kwarg(self->ref, n_kw, key, value, out);
+ } else {
+ // n_args == 1
+ uint32_t arg0[PVN];
+ proxy_convert_mp_to_js_obj_cside(args[0], arg0);
+ call1_kwarg(self->ref, arg0, n_kw, key, value, out);
+ }
+ return proxy_convert_js_to_mp_obj_cside(out);
+ }
+
+ if (n_args == 0) {
+ uint32_t out[3];
+ call0(self->ref, out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+ } else if (n_args == 1) {
+ uint32_t arg0[PVN];
+ uint32_t out[PVN];
+ proxy_convert_mp_to_js_obj_cside(args[0], arg0);
+ call1(self->ref, arg0, out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+ } else if (n_args == 2) {
+ uint32_t arg0[PVN];
+ proxy_convert_mp_to_js_obj_cside(args[0], arg0);
+ uint32_t arg1[PVN];
+ proxy_convert_mp_to_js_obj_cside(args[1], arg1);
+ uint32_t out[3];
+ call2(self->ref, arg0, arg1, out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+ } else {
+ uint32_t value[PVN * n_args];
+ for (int i = 0; i < n_args; ++i) {
+ proxy_convert_mp_to_js_obj_cside(args[i], &value[i * PVN]);
+ }
+ uint32_t out[3];
+ calln(self->ref, n_args, value, out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+ }
+}
+
+static mp_obj_t jsproxy_reflect_construct(size_t n_args, const mp_obj_t *args) {
+ int arg0 = mp_obj_jsproxy_get_ref(args[0]);
+ n_args -= 1;
+ args += 1;
+ uint32_t args_conv[n_args];
+ for (unsigned int i = 0; i < n_args; ++i) {
+ proxy_convert_mp_to_js_obj_cside(args[i], &args_conv[i * PVN]);
+ }
+ uint32_t out[3];
+ js_reflect_construct(arg0, n_args, args_conv, out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(jsproxy_reflect_construct_obj, 1, jsproxy_reflect_construct);
+
+static mp_obj_t jsproxy_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
+ mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+ if (value == MP_OBJ_SENTINEL) {
+ // Load subscript.
+ uint32_t idx[PVN], out[PVN];
+ proxy_convert_mp_to_js_obj_cside(index, idx);
+ js_subscr_load(self->ref, idx, out);
+ return proxy_convert_js_to_mp_obj_cside(out);
+ } else if (value == MP_OBJ_NULL) {
+ // Delete subscript.
+ return MP_OBJ_NULL; // not supported
+ } else {
+ // Store subscript.
+ uint32_t idx[PVN], val[PVN];
+ proxy_convert_mp_to_js_obj_cside(index, idx);
+ proxy_convert_mp_to_js_obj_cside(value, val);
+ js_subscr_store(self->ref, idx, val);
+ return mp_const_none;
+ }
+}
+
+void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+ mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+ if (dest[0] == MP_OBJ_NULL) {
+ // Load attribute.
+ uint32_t out[PVN];
+ if (lookup_attr(self->ref, qstr_str(attr), out)) {
+ dest[0] = proxy_convert_js_to_mp_obj_cside(out);
+ } else if (attr == MP_QSTR_new) {
+ // Special case to handle construction of JS objects.
+ // JS objects don't have a ".new" attribute, doing "Obj.new" is a Pyodide idiom for "new Obj".
+ // It translates to the JavaScript "Reflect.construct(Obj, Array(...args))".
+ dest[0] = MP_OBJ_FROM_PTR(&jsproxy_reflect_construct_obj);
+ dest[1] = self_in;
+ }
+ } else if (dest[1] == MP_OBJ_NULL) {
+ // Delete attribute.
+ } else {
+ // Store attribute.
+ uint32_t value[PVN];
+ proxy_convert_mp_to_js_obj_cside(dest[1], value);
+ store_attr(self->ref, qstr_str(attr), value);
+ dest[0] = MP_OBJ_NULL;
+ }
+}
+
+/******************************************************************************/
+// jsproxy iterator
+
+typedef struct _jsproxy_it_t {
+ mp_obj_base_t base;
+ mp_fun_1_t iternext;
+ int ref;
+ uint16_t cur;
+ uint16_t len;
+} jsproxy_it_t;
+
+static mp_obj_t jsproxy_it_iternext(mp_obj_t self_in) {
+ jsproxy_it_t *self = MP_OBJ_TO_PTR(self_in);
+ if (self->cur < self->len) {
+ uint32_t out[3];
+ js_subscr_int(self->ref, self->cur, out);
+ self->cur += 1;
+ return proxy_convert_js_to_mp_obj_cside(out);
+ } else {
+ return MP_OBJ_STOP_ITERATION;
+ }
+}
+
+static mp_obj_t jsproxy_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) {
+ assert(sizeof(jsproxy_it_t) <= sizeof(mp_obj_iter_buf_t));
+ jsproxy_it_t *o = (jsproxy_it_t *)iter_buf;
+ o->base.type = &mp_type_polymorph_iter;
+ o->iternext = jsproxy_it_iternext;
+ o->ref = mp_obj_jsproxy_get_ref(o_in);
+ o->cur = 0;
+ o->len = js_get_len(o->ref);
+ return MP_OBJ_FROM_PTR(o);
+}
+
+/******************************************************************************/
+
+MP_DEFINE_CONST_OBJ_TYPE(
+ mp_type_jsproxy,
+ MP_QSTR_JsProxy,
+ MP_TYPE_FLAG_ITER_IS_GETITER,
+ print, jsproxy_print,
+ call, jsproxy_call,
+ attr, mp_obj_jsproxy_attr,
+ subscr, jsproxy_subscr,
+ iter, jsproxy_getiter
+ );
+
+mp_obj_t mp_obj_new_jsproxy(int ref) {
+ mp_obj_jsproxy_t *o = mp_obj_malloc(mp_obj_jsproxy_t, &mp_type_jsproxy);
+ o->ref = ref;
+ return MP_OBJ_FROM_PTR(o);
+}
diff --git a/ports/webassembly/objpyproxy.js b/ports/webassembly/objpyproxy.js
new file mode 100644
index 000000000..52670b66e
--- /dev/null
+++ b/ports/webassembly/objpyproxy.js
@@ -0,0 +1,191 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+class PyProxy {
+ constructor(ref) {
+ this._ref = ref;
+ }
+
+ // Convert js_obj -- which is possibly a PyProxy -- to a JavaScript object.
+ static toJs(js_obj) {
+ if (!(js_obj instanceof PyProxy)) {
+ return js_obj;
+ }
+
+ const type = Module.ccall(
+ "proxy_c_to_js_get_type",
+ "number",
+ ["number"],
+ [js_obj._ref],
+ );
+
+ if (type === 1 || type === 2) {
+ // List or tuple.
+ const array_ref = Module._malloc(2 * 4);
+ const item = Module._malloc(3 * 4);
+ Module.ccall(
+ "proxy_c_to_js_get_array",
+ "null",
+ ["number", "pointer"],
+ [js_obj._ref, array_ref],
+ );
+ const len = Module.getValue(array_ref, "i32");
+ const items_ptr = Module.getValue(array_ref + 4, "i32");
+ const js_array = [];
+ for (let i = 0; i < len; ++i) {
+ Module.ccall(
+ "proxy_convert_mp_to_js_obj_cside",
+ "null",
+ ["pointer", "pointer"],
+ [Module.getValue(items_ptr + i * 4, "i32"), item],
+ );
+ const js_item = proxy_convert_mp_to_js_obj_jsside(item);
+ js_array.push(PyProxy.toJs(js_item));
+ }
+ Module._free(array_ref);
+ Module._free(item);
+ return js_array;
+ }
+
+ if (type === 3) {
+ // Dict.
+ const map_ref = Module._malloc(2 * 4);
+ const item = Module._malloc(3 * 4);
+ Module.ccall(
+ "proxy_c_to_js_get_dict",
+ "null",
+ ["number", "pointer"],
+ [js_obj._ref, map_ref],
+ );
+ const alloc = Module.getValue(map_ref, "i32");
+ const table_ptr = Module.getValue(map_ref + 4, "i32");
+ const js_dict = {};
+ for (let i = 0; i < alloc; ++i) {
+ const mp_key = Module.getValue(table_ptr + i * 8, "i32");
+ if (mp_key > 8) {
+ // Convert key to JS object.
+ Module.ccall(
+ "proxy_convert_mp_to_js_obj_cside",
+ "null",
+ ["pointer", "pointer"],
+ [mp_key, item],
+ );
+ const js_key = proxy_convert_mp_to_js_obj_jsside(item);
+
+ // Convert value to JS object.
+ const mp_value = Module.getValue(
+ table_ptr + i * 8 + 4,
+ "i32",
+ );
+ Module.ccall(
+ "proxy_convert_mp_to_js_obj_cside",
+ "null",
+ ["pointer", "pointer"],
+ [mp_value, item],
+ );
+ const js_value = proxy_convert_mp_to_js_obj_jsside(item);
+
+ // Populate JS dict.
+ js_dict[js_key] = PyProxy.toJs(js_value);
+ }
+ }
+ Module._free(map_ref);
+ Module._free(item);
+ return js_dict;
+ }
+
+ // Cannot convert to JS, leave as a PyProxy.
+ return js_obj;
+ }
+}
+
+// This handler's goal is to allow minimal introspection
+// of Python references from the JS world/utilities.
+const py_proxy_handler = {
+ isExtensible() {
+ return true;
+ },
+ ownKeys(target) {
+ const value = Module._malloc(3 * 4);
+ Module.ccall(
+ "proxy_c_to_js_dir",
+ "null",
+ ["number", "pointer"],
+ [target._ref, value],
+ );
+ const dir = proxy_convert_mp_to_js_obj_jsside_with_free(value);
+ return PyProxy.toJs(dir).filter((attr) => !attr.startsWith("__"));
+ },
+ getOwnPropertyDescriptor(target, prop) {
+ return {
+ value: target[prop],
+ enumerable: true,
+ writable: true,
+ configurable: true,
+ };
+ },
+ has(target, prop) {
+ return Module.ccall(
+ "proxy_c_to_js_has_attr",
+ "number",
+ ["number", "string"],
+ [target._ref, prop],
+ );
+ },
+ get(target, prop) {
+ if (prop === "_ref") {
+ return target._ref;
+ }
+ const value = Module._malloc(3 * 4);
+ Module.ccall(
+ "proxy_c_to_js_lookup_attr",
+ "number",
+ ["number", "string", "pointer"],
+ [target._ref, prop, value],
+ );
+ return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+ },
+ set(target, prop, value) {
+ const value_conv = Module._malloc(3 * 4);
+ proxy_convert_js_to_mp_obj_jsside(value, value_conv);
+ const ret = Module.ccall(
+ "proxy_c_to_js_store_attr",
+ "number",
+ ["number", "string", "number"],
+ [target._ref, prop, value_conv],
+ );
+ Module._free(value_conv);
+ return ret;
+ },
+ deleteProperty(target, prop) {
+ return Module.ccall(
+ "proxy_c_to_js_delete_attr",
+ "number",
+ ["number", "string"],
+ [target._ref, prop],
+ );
+ },
+};
diff --git a/ports/webassembly/proxy_c.c b/ports/webassembly/proxy_c.c
new file mode 100644
index 000000000..809dd44dd
--- /dev/null
+++ b/ports/webassembly/proxy_c.c
@@ -0,0 +1,281 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "py/builtin.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+// These constants should match the constants in proxy_js.js.
+
+enum {
+ PROXY_KIND_MP_EXCEPTION = -1,
+ PROXY_KIND_MP_NULL = 0,
+ PROXY_KIND_MP_NONE = 1,
+ PROXY_KIND_MP_BOOL = 2,
+ PROXY_KIND_MP_INT = 3,
+ PROXY_KIND_MP_FLOAT = 4,
+ PROXY_KIND_MP_STR = 5,
+ PROXY_KIND_MP_CALLABLE = 6,
+ PROXY_KIND_MP_OBJECT = 7,
+ PROXY_KIND_MP_JSPROXY = 8,
+};
+
+enum {
+ PROXY_KIND_JS_NULL = 1,
+ PROXY_KIND_JS_BOOLEAN = 2,
+ PROXY_KIND_JS_INTEGER = 3,
+ PROXY_KIND_JS_DOUBLE = 4,
+ PROXY_KIND_JS_STRING = 5,
+ PROXY_KIND_JS_OBJECT = 6,
+ PROXY_KIND_JS_PYPROXY = 7,
+};
+
+void proxy_c_init(void) {
+ MP_STATE_PORT(proxy_c_ref) = mp_obj_new_list(0, NULL);
+ mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), MP_OBJ_NULL);
+}
+
+MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_ref);
+
+static inline mp_obj_t proxy_c_get_obj(uint32_t c_ref) {
+ return ((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->items[c_ref];
+}
+
+mp_obj_t proxy_convert_js_to_mp_obj_cside(uint32_t *value) {
+ if (value[0] == PROXY_KIND_JS_NULL) {
+ return mp_const_none;
+ } else if (value[0] == PROXY_KIND_JS_BOOLEAN) {
+ return mp_obj_new_bool(value[1]);
+ } else if (value[0] == PROXY_KIND_JS_INTEGER) {
+ return mp_obj_new_int(value[1]);
+ } else if (value[0] == PROXY_KIND_JS_DOUBLE) {
+ return mp_obj_new_float_from_d(*(double *)&value[1]);
+ } else if (value[0] == PROXY_KIND_JS_STRING) {
+ mp_obj_t s = mp_obj_new_str((void *)value[2], value[1]);
+ free((void *)value[2]);
+ return s;
+ } else if (value[0] == PROXY_KIND_JS_PYPROXY) {
+ return proxy_c_get_obj(value[1]);
+ } else {
+ // PROXY_KIND_JS_OBJECT
+ return mp_obj_new_jsproxy(value[1]);
+ }
+}
+
+void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out) {
+ uint32_t kind;
+ if (obj == MP_OBJ_NULL) {
+ kind = PROXY_KIND_MP_NULL;
+ } else if (obj == mp_const_none) {
+ kind = PROXY_KIND_MP_NONE;
+ } else if (mp_obj_is_bool(obj)) {
+ kind = PROXY_KIND_MP_BOOL;
+ out[1] = mp_obj_is_true(obj);
+ } else if (mp_obj_is_int(obj)) {
+ kind = PROXY_KIND_MP_INT;
+ out[1] = mp_obj_get_int_truncated(obj); // TODO support big int
+ } else if (mp_obj_is_float(obj)) {
+ kind = PROXY_KIND_MP_FLOAT;
+ *(double *)&out[1] = mp_obj_get_float(obj);
+ } else if (mp_obj_is_str(obj)) {
+ kind = PROXY_KIND_MP_STR;
+ size_t len;
+ const char *str = mp_obj_str_get_data(obj, &len);
+ out[1] = len;
+ out[2] = (uintptr_t)str;
+ } else if (mp_obj_is_jsproxy(obj)) {
+ kind = PROXY_KIND_MP_JSPROXY;
+ out[1] = mp_obj_jsproxy_get_ref(obj);
+ } else {
+ if (mp_obj_is_callable(obj)) {
+ kind = PROXY_KIND_MP_CALLABLE;
+ } else {
+ kind = PROXY_KIND_MP_OBJECT;
+ }
+ size_t id = ((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->len;
+ mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), obj);
+ out[1] = id;
+ }
+ out[0] = kind;
+}
+
+void proxy_convert_mp_to_js_exc_cside(void *exc, uint32_t *out) {
+ out[0] = PROXY_KIND_MP_EXCEPTION;
+ vstr_t vstr;
+ mp_print_t print;
+ vstr_init_print(&vstr, 64, &print);
+ vstr_add_str(&vstr, qstr_str(mp_obj_get_type(MP_OBJ_FROM_PTR(exc))->name));
+ vstr_add_char(&vstr, '\x04');
+ mp_obj_print_exception(&print, MP_OBJ_FROM_PTR(exc));
+ char *s = malloc(vstr_len(&vstr) + 1);
+ memcpy(s, vstr_str(&vstr), vstr_len(&vstr));
+ out[1] = vstr_len(&vstr);
+ out[2] = (uintptr_t)s;
+ vstr_clear(&vstr);
+}
+
+void proxy_c_to_js_call(uint32_t c_ref, uint32_t n_args, uint32_t *args_value, uint32_t *out) {
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_obj_t args[4] = { mp_const_none, mp_const_none, mp_const_none, mp_const_none };
+ for (size_t i = 0; i < n_args; ++i) {
+ args[i] = proxy_convert_js_to_mp_obj_cside(args_value + i * 3);
+ }
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ mp_obj_t member = mp_call_function_n_kw(obj, n_args, 0, args);
+ nlr_pop();
+ proxy_convert_mp_to_js_obj_cside(member, out);
+ } else {
+ // uncaught exception
+ proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+ }
+}
+
+void proxy_c_to_js_dir(uint32_t c_ref, uint32_t *out) {
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ mp_obj_t dir;
+ if (mp_obj_is_dict_or_ordereddict(obj)) {
+ mp_map_t *map = mp_obj_dict_get_map(obj);
+ dir = mp_obj_new_list(0, NULL);
+ for (size_t i = 0; i < map->alloc; i++) {
+ if (mp_map_slot_is_filled(map, i)) {
+ mp_obj_list_append(dir, map->table[i].key);
+ }
+ }
+ } else {
+ mp_obj_t args[1] = { obj };
+ dir = mp_builtin_dir_obj.fun.var(1, args);
+ }
+ nlr_pop();
+ return proxy_convert_mp_to_js_obj_cside(dir, out);
+ } else {
+ // uncaught exception
+ return proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+ }
+}
+
+bool proxy_c_to_js_has_attr(uint32_t c_ref, const char *attr_in) {
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ qstr attr = qstr_from_str(attr_in);
+ if (mp_obj_is_dict_or_ordereddict(obj)) {
+ mp_map_t *map = mp_obj_dict_get_map(obj);
+ mp_map_elem_t *elem = mp_map_lookup(map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
+ return elem != NULL;
+ } else {
+ mp_obj_t dest[2];
+ mp_load_method_protected(obj, attr, dest, true);
+ if (dest[0] != MP_OBJ_NULL) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void proxy_c_to_js_lookup_attr(uint32_t c_ref, const char *attr_in, uint32_t *out) {
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ qstr attr = qstr_from_str(attr_in);
+ mp_obj_t member;
+ if (mp_obj_is_dict_or_ordereddict(obj)) {
+ member = mp_obj_dict_get(obj, MP_OBJ_NEW_QSTR(attr));
+ } else {
+ member = mp_load_attr(obj, attr);
+ }
+ nlr_pop();
+ return proxy_convert_mp_to_js_obj_cside(member, out);
+ } else {
+ // uncaught exception
+ return proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+ }
+}
+
+static bool proxy_c_to_js_store_helper(uint32_t c_ref, const char *attr_in, mp_obj_t value) {
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ qstr attr = qstr_from_str(attr_in);
+
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ if (mp_obj_is_dict_or_ordereddict(obj)) {
+ if (value == MP_OBJ_NULL) {
+ mp_obj_dict_delete(obj, MP_OBJ_NEW_QSTR(attr));
+ } else {
+ mp_obj_dict_store(obj, MP_OBJ_NEW_QSTR(attr), value);
+ }
+ } else {
+ mp_store_attr(obj, attr, value);
+ }
+ nlr_pop();
+ return true;
+ } else {
+ // uncaught exception
+ return false;
+ }
+}
+
+bool proxy_c_to_js_store_attr(uint32_t c_ref, const char *attr_in, uint32_t *value_in) {
+ mp_obj_t value = proxy_convert_js_to_mp_obj_cside(value_in);
+ return proxy_c_to_js_store_helper(c_ref, attr_in, value);
+}
+
+bool proxy_c_to_js_delete_attr(uint32_t c_ref, const char *attr_in) {
+ return proxy_c_to_js_store_helper(c_ref, attr_in, MP_OBJ_NULL);
+}
+
+uint32_t proxy_c_to_js_get_type(uint32_t c_ref) {
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ const mp_obj_type_t *type = mp_obj_get_type(obj);
+ if (type == &mp_type_tuple) {
+ return 1;
+ } else if (type == &mp_type_list) {
+ return 2;
+ } else if (type == &mp_type_dict) {
+ return 3;
+ } else {
+ return 4;
+ }
+}
+
+void proxy_c_to_js_get_array(uint32_t c_ref, uint32_t *out) {
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ size_t len;
+ mp_obj_t *items;
+ mp_obj_get_array(obj, &len, &items);
+ out[0] = len;
+ out[1] = (uintptr_t)items;
+}
+
+void proxy_c_to_js_get_dict(uint32_t c_ref, uint32_t *out) {
+ mp_obj_t obj = proxy_c_get_obj(c_ref);
+ mp_map_t *map = mp_obj_dict_get_map(obj);
+ out[0] = map->alloc;
+ out[1] = (uintptr_t)map->table;
+}
diff --git a/ports/webassembly/proxy_c.h b/ports/webassembly/proxy_c.h
new file mode 100644
index 000000000..3e68d2504
--- /dev/null
+++ b/ports/webassembly/proxy_c.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef MICROPY_INCLUDED_WEBASSEMBLY_PROXY_C_H
+#define MICROPY_INCLUDED_WEBASSEMBLY_PROXY_C_H
+
+#include "py/obj.h"
+
+// proxy value number of items
+#define PVN (3)
+
+typedef struct _mp_obj_jsproxy_t {
+ mp_obj_base_t base;
+ int ref;
+} mp_obj_jsproxy_t;
+
+extern const mp_obj_type_t mp_type_jsproxy;
+
+void proxy_c_init(void);
+mp_obj_t proxy_convert_js_to_mp_obj_cside(uint32_t *value);
+void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out);
+void proxy_convert_mp_to_js_exc_cside(void *exc, uint32_t *out);
+
+mp_obj_t mp_obj_new_jsproxy(int ref);
+void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
+
+static inline bool mp_obj_is_jsproxy(mp_obj_t o) {
+ return mp_obj_get_type(o) == &mp_type_jsproxy;
+}
+
+static inline int mp_obj_jsproxy_get_ref(mp_obj_t o) {
+ mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(o);
+ return self->ref;
+}
+
+#endif // MICROPY_INCLUDED_WEBASSEMBLY_PROXY_C_H
diff --git a/ports/webassembly/proxy_js.js b/ports/webassembly/proxy_js.js
new file mode 100644
index 000000000..1835bdfdf
--- /dev/null
+++ b/ports/webassembly/proxy_js.js
@@ -0,0 +1,222 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// These constants should match the constants in proxy_c.c.
+
+const PROXY_KIND_MP_EXCEPTION = -1;
+const PROXY_KIND_MP_NULL = 0;
+const PROXY_KIND_MP_NONE = 1;
+const PROXY_KIND_MP_BOOL = 2;
+const PROXY_KIND_MP_INT = 3;
+const PROXY_KIND_MP_FLOAT = 4;
+const PROXY_KIND_MP_STR = 5;
+const PROXY_KIND_MP_CALLABLE = 6;
+const PROXY_KIND_MP_OBJECT = 7;
+const PROXY_KIND_MP_JSPROXY = 8;
+
+const PROXY_KIND_JS_NULL = 1;
+const PROXY_KIND_JS_BOOLEAN = 2;
+const PROXY_KIND_JS_INTEGER = 3;
+const PROXY_KIND_JS_DOUBLE = 4;
+const PROXY_KIND_JS_STRING = 5;
+const PROXY_KIND_JS_OBJECT = 6;
+const PROXY_KIND_JS_PYPROXY = 7;
+
+class PythonError extends Error {
+ constructor(exc_type, exc_details) {
+ super(exc_details);
+ this.name = "PythonError";
+ this.type = exc_type;
+ }
+}
+
+function proxy_js_init() {
+ globalThis.proxy_js_ref = [globalThis];
+}
+
+function proxy_call_python(target, argumentsList) {
+ let args = 0;
+
+ // Strip trailing "undefined" arguments.
+ while (
+ argumentsList.length > 0 &&
+ argumentsList[argumentsList.length - 1] === undefined
+ ) {
+ argumentsList.pop();
+ }
+
+ if (argumentsList.length > 0) {
+ // TODO use stackAlloc/stackRestore?
+ args = Module._malloc(argumentsList.length * 3 * 4);
+ for (const i in argumentsList) {
+ proxy_convert_js_to_mp_obj_jsside(
+ argumentsList[i],
+ args + i * 3 * 4,
+ );
+ }
+ }
+ const value = Module._malloc(3 * 4);
+ Module.ccall(
+ "proxy_c_to_js_call",
+ "null",
+ ["number", "number", "number", "pointer"],
+ [target, argumentsList.length, args, value],
+ );
+ if (argumentsList.length > 0) {
+ Module._free(args);
+ }
+ return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+}
+
+function proxy_convert_js_to_mp_obj_jsside(js_obj, out) {
+ let kind;
+ if (js_obj === null) {
+ kind = PROXY_KIND_JS_NULL;
+ } else if (typeof js_obj === "boolean") {
+ kind = PROXY_KIND_JS_BOOLEAN;
+ Module.setValue(out + 4, js_obj, "i32");
+ } else if (typeof js_obj === "number") {
+ if (Number.isInteger(js_obj)) {
+ kind = PROXY_KIND_JS_INTEGER;
+ Module.setValue(out + 4, js_obj, "i32");
+ } else {
+ kind = PROXY_KIND_JS_DOUBLE;
+ // double must be stored to an address that's a multiple of 8
+ const temp = (out + 4) & ~7;
+ Module.setValue(temp, js_obj, "double");
+ const double_lo = Module.getValue(temp, "i32");
+ const double_hi = Module.getValue(temp + 4, "i32");
+ Module.setValue(out + 4, double_lo, "i32");
+ Module.setValue(out + 8, double_hi, "i32");
+ }
+ } else if (typeof js_obj === "string") {
+ kind = PROXY_KIND_JS_STRING;
+ const len = Module.lengthBytesUTF8(js_obj);
+ const buf = Module._malloc(len + 1);
+ Module.stringToUTF8(js_obj, buf, len + 1);
+ Module.setValue(out + 4, len, "i32");
+ Module.setValue(out + 8, buf, "i32");
+ } else if (js_obj instanceof PyProxy) {
+ kind = PROXY_KIND_JS_PYPROXY;
+ Module.setValue(out + 4, js_obj._ref, "i32");
+ } else {
+ kind = PROXY_KIND_JS_OBJECT;
+ const id = proxy_js_ref.length;
+ proxy_js_ref[id] = js_obj;
+ Module.setValue(out + 4, id, "i32");
+ }
+ Module.setValue(out + 0, kind, "i32");
+}
+
+function proxy_convert_js_to_mp_obj_jsside_force_double_proxy(js_obj, out) {
+ if (js_obj instanceof PyProxy) {
+ const kind = PROXY_KIND_JS_OBJECT;
+ const id = proxy_js_ref.length;
+ proxy_js_ref[id] = js_obj;
+ Module.setValue(out + 4, id, "i32");
+ Module.setValue(out + 0, kind, "i32");
+ } else {
+ proxy_convert_js_to_mp_obj_jsside(js_obj, out);
+ }
+}
+
+function proxy_convert_mp_to_js_obj_jsside(value) {
+ const kind = Module.getValue(value, "i32");
+ let obj;
+ if (kind === PROXY_KIND_MP_EXCEPTION) {
+ // Exception
+ const str_len = Module.getValue(value + 4, "i32");
+ const str_ptr = Module.getValue(value + 8, "i32");
+ const str = Module.UTF8ToString(str_ptr, str_len);
+ Module._free(str_ptr);
+ const str_split = str.split("\x04");
+ throw new PythonError(str_split[0], str_split[1]);
+ }
+ if (kind === PROXY_KIND_MP_NULL) {
+ // MP_OBJ_NULL
+ throw new Error("NULL object");
+ }
+ if (kind === PROXY_KIND_MP_NONE) {
+ // None
+ obj = null;
+ } else if (kind === PROXY_KIND_MP_BOOL) {
+ // bool
+ obj = Module.getValue(value + 4, "i32") ? true : false;
+ } else if (kind === PROXY_KIND_MP_INT) {
+ // int
+ obj = Module.getValue(value + 4, "i32");
+ } else if (kind === PROXY_KIND_MP_FLOAT) {
+ // float
+ // double must be loaded from an address that's a multiple of 8
+ const temp = (value + 4) & ~7;
+ const double_lo = Module.getValue(value + 4, "i32");
+ const double_hi = Module.getValue(value + 8, "i32");
+ Module.setValue(temp, double_lo, "i32");
+ Module.setValue(temp + 4, double_hi, "i32");
+ obj = Module.getValue(temp, "double");
+ } else if (kind === PROXY_KIND_MP_STR) {
+ // str
+ const str_len = Module.getValue(value + 4, "i32");
+ const str_ptr = Module.getValue(value + 8, "i32");
+ obj = Module.UTF8ToString(str_ptr, str_len);
+ } else if (kind === PROXY_KIND_MP_JSPROXY) {
+ // js proxy
+ const id = Module.getValue(value + 4, "i32");
+ obj = proxy_js_ref[id];
+ } else {
+ // obj
+ const id = Module.getValue(value + 4, "i32");
+ if (kind === PROXY_KIND_MP_CALLABLE) {
+ obj = (...args) => {
+ return proxy_call_python(id, args);
+ };
+ } else {
+ // PROXY_KIND_MP_OBJECT
+ const target = new PyProxy(id);
+ obj = new Proxy(target, py_proxy_handler);
+ }
+ }
+ return obj;
+}
+
+function proxy_convert_mp_to_js_obj_jsside_with_free(value) {
+ const ret = proxy_convert_mp_to_js_obj_jsside(value);
+ Module._free(value);
+ return ret;
+}
+
+function python_index_semantics(target, index_in) {
+ let index = index_in;
+ if (typeof index === "number") {
+ if (index < 0) {
+ index += target.length;
+ }
+ if (index < 0 || index >= target.length) {
+ throw new PythonError("IndexError", "index out of range");
+ }
+ }
+ return index;
+}