webassembly/objpyproxy: Implement JS iterator protocol for Py iterables.

This allows using JavaScript for..of on Python iterables.

Signed-off-by: Damien George <damien@micropython.org>
pull/14435/head
Damien George 2024-05-06 13:47:48 +10:00
rodzic e860e32e24
commit c056840ee8
5 zmienionych plików z 97 dodań i 4 usunięć

Wyświetl plik

@ -60,8 +60,10 @@ EXPORTED_FUNCTIONS_EXTRA += ,\
_proxy_c_to_js_dir,\
_proxy_c_to_js_get_array,\
_proxy_c_to_js_get_dict,\
_proxy_c_to_js_get_iter,\
_proxy_c_to_js_get_type,\
_proxy_c_to_js_has_attr,\
_proxy_c_to_js_iternext,\
_proxy_c_to_js_lookup_attr,\
_proxy_c_to_js_resume,\
_proxy_c_to_js_store_attr,\

Wyświetl plik

@ -162,10 +162,37 @@ const py_proxy_handler = {
if (prop === "then") {
return null;
}
if (prop === Symbol.iterator) {
// Get the Python object iterator, and return a JavaScript generator.
const iter_ref = Module.ccall(
"proxy_c_to_js_get_iter",
"number",
["number"],
[target._ref],
);
return function* () {
const value = Module._malloc(3 * 4);
while (true) {
const valid = Module.ccall(
"proxy_c_to_js_iternext",
"number",
["number", "pointer"],
[iter_ref, value],
);
if (!valid) {
break;
}
yield proxy_convert_mp_to_js_obj_jsside(value);
}
Module._free(value);
};
}
const value = Module._malloc(3 * 4);
Module.ccall(
"proxy_c_to_js_lookup_attr",
"number",
"null",
["number", "string", "pointer"],
[target._ref, prop, value],
);

Wyświetl plik

@ -65,6 +65,12 @@ void proxy_c_init(void) {
MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_ref);
static inline size_t proxy_c_add_obj(mp_obj_t obj) {
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);
return id;
}
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];
}
@ -122,9 +128,7 @@ void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out) {
} 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[1] = proxy_c_add_obj(obj);
}
out[0] = kind;
}
@ -284,6 +288,38 @@ void proxy_c_to_js_get_dict(uint32_t c_ref, uint32_t *out) {
out[1] = (uintptr_t)map->table;
}
/******************************************************************************/
// Bridge Python iterator to JavaScript iterator protocol.
uint32_t proxy_c_to_js_get_iter(uint32_t c_ref) {
mp_obj_t obj = proxy_c_get_obj(c_ref);
mp_obj_t iter = mp_getiter(obj, NULL);
return proxy_c_add_obj(iter);
}
bool proxy_c_to_js_iternext(uint32_t c_ref, uint32_t *out) {
mp_obj_t obj = proxy_c_get_obj(c_ref);
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
mp_obj_t iter = mp_iternext_allow_raise(obj);
if (iter == MP_OBJ_STOP_ITERATION) {
nlr_pop();
return false;
}
nlr_pop();
proxy_convert_mp_to_js_obj_cside(iter, out);
return true;
} else {
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
return false;
} else {
// uncaught exception
proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
return true;
}
}
}
/******************************************************************************/
// Bridge Python generator to JavaScript thenable.

Wyświetl plik

@ -0,0 +1,21 @@
// Test accessing Python iterables from JavaScript via the JavaScript iterator protocol.
const mp = await (await import(process.argv[2])).loadMicroPython();
mp.runPython(`
s = "abc"
l = [1, 2, 3]
`);
// Iterate a Python string.
for (const value of mp.globals.get("s")) {
console.log(value);
}
// Iterate a Python list.
for (const value of mp.globals.get("l")) {
console.log(value);
}
// Iterate a Python list from a built-in JavaScript constructor.
mp.runPython("import js; print(js.Set.new([1, 2, 3]).has(3))");

Wyświetl plik

@ -0,0 +1,7 @@
a
b
c
1
2
3
True