From 4020ee7d415c8090e398e222bc2b965c5663c70b Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 30 Jan 2019 12:18:59 -0600 Subject: [PATCH 01/33] Add a bare-bones VirtualMachine class to the WASM library --- Cargo.lock | 12 ++-- wasm/lib/Cargo.toml | 4 +- wasm/lib/src/convert.rs | 100 +++++++++++++++++++++++++++++ wasm/lib/src/lib.rs | 116 ++++------------------------------ wasm/lib/src/vm_class.rs | 68 ++++++++++++++++++++ wasm/lib/src/wasm_builtins.rs | 4 +- 6 files changed, 190 insertions(+), 114 deletions(-) create mode 100644 wasm/lib/src/convert.rs create mode 100644 wasm/lib/src/vm_class.rs diff --git a/Cargo.lock b/Cargo.lock index ddc013a5820..80373d4c108 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,7 @@ name = "docopt" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -397,7 +397,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -815,7 +815,7 @@ name = "string_cache" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -930,7 +930,7 @@ name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1004,7 +1004,7 @@ name = "wasm-bindgen-backend" version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1176,7 +1176,7 @@ dependencies = [ "checksum lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba451f7bd819b7afc99d4cf4bdcd5a4861e64955ba9680ac70df3a50625ad6cf" "checksum lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60013fd6be14317d43f47658b1440956a9ca48a9ed0257e0e0a59aac13e43a1f" "checksum lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60c6c48ba857cd700673ce88907cadcdd7e2cd7783ed02378537c5ffd4f6460c" -"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fddaa003a65722a7fb9e26b0ce95921fe4ba590542ced664d8ce2fa26f9f3ac" diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 6c78e064052..bc2ee9d05b2 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/RustPython/RustPython/tree/master/wasm/lib" crate-type = ["cdylib", "rlib"] [dependencies] -rustpython_parser = {path = "../../parser"} -rustpython_vm = {path = "../../vm"} +rustpython_parser = { path = "../../parser" } +rustpython_vm = { path = "../../vm" } cfg-if = "0.1.2" wasm-bindgen = "0.2" js-sys = "0.3" diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs new file mode 100644 index 00000000000..bdb2fca64d7 --- /dev/null +++ b/wasm/lib/src/convert.rs @@ -0,0 +1,100 @@ +use js_sys::{Array, Object, Reflect}; +use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; +use rustpython_vm::VirtualMachine; +use wasm_bindgen::{prelude::*, JsCast}; + +pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { + vm.to_pystr(&py_err) + .unwrap_or_else(|_| "Error, and error getting error message".into()) +} + +pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { + let dumps = rustpython_vm::import::import( + vm, + std::path::PathBuf::default(), + "json", + &Some("dumps".into()), + ) + .expect("Couldn't get json.dumps function"); + match vm.invoke(dumps, pyobject::PyFuncArgs::new(vec![py_obj], vec![])) { + Ok(value) => { + let json = vm.to_pystr(&value).unwrap(); + js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED) + } + Err(_) => JsValue::UNDEFINED, + } +} + +pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { + if js_val.is_object() { + if Array::is_array(&js_val) { + let js_arr: Array = js_val.into(); + let elems = js_arr + .values() + .into_iter() + .map(|val| js_to_py(vm, val.expect("Iteration over array failed"))) + .collect(); + vm.ctx.new_list(elems) + } else { + let dict = vm.new_dict(); + for pair in Object::entries(&Object::from(js_val)).values() { + let pair = pair.expect("Iteration over object failed"); + let key = Reflect::get(&pair, &"0".into()).unwrap(); + let val = Reflect::get(&pair, &"1".into()).unwrap(); + let py_val = js_to_py(vm, val); + vm.ctx + .set_item(&dict, &String::from(js_sys::JsString::from(key)), py_val); + } + dict + } + } else if js_val.is_function() { + let func = js_sys::Function::from(js_val); + vm.ctx.new_rustfunc( + move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { + let func = func.clone(); + let this = Object::new(); + for (k, v) in args.kwargs { + Reflect::set(&this, &k.into(), &py_to_js(vm, v)) + .expect("Couldn't set this property"); + } + let js_args = Array::new(); + for v in args.args { + js_args.push(&py_to_js(vm, v)); + } + func.apply(&this, &js_args) + .map(|val| js_to_py(vm, val)) + .map_err(|err| js_to_py(vm, err)) + }, + ) + } else if let Some(err) = js_val.dyn_ref::() { + let exc_type = match String::from(err.name()).as_str() { + "TypeError" => &vm.ctx.exceptions.type_error, + "ReferenceError" => &vm.ctx.exceptions.name_error, + "SyntaxError" => &vm.ctx.exceptions.syntax_error, + _ => &vm.ctx.exceptions.exception_type, + } + .clone(); + vm.new_exception(exc_type, err.message().into()) + } else if js_val.is_undefined() { + // Because `JSON.stringify(undefined)` returns undefined + vm.get_none() + } else { + let loads = rustpython_vm::import::import( + vm, + std::path::PathBuf::default(), + "json", + &Some("loads".into()), + ) + .expect("Couldn't get json.loads function"); + + let json = match js_sys::JSON::stringify(&js_val) { + Ok(json) => String::from(json), + Err(_) => return vm.get_none(), + }; + let py_json = vm.new_str(json); + + vm.invoke(loads, pyobject::PyFuncArgs::new(vec![py_json], vec![])) + // can safely unwrap because we know it's valid JSON + .unwrap() + } +} diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 4bc0891b2f9..1583457988a 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -1,3 +1,5 @@ +mod convert; +mod vm_class; mod wasm_builtins; extern crate js_sys; @@ -5,112 +7,18 @@ extern crate rustpython_vm; extern crate wasm_bindgen; extern crate web_sys; -use js_sys::{Array, Object, Reflect, TypeError}; +use js_sys::{Object, Reflect, TypeError}; use rustpython_vm::compile; -use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; +use rustpython_vm::pyobject::{PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; -use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen::prelude::*; -// Hack to comment out wasm-bindgen's typescript definitons +pub use vm_class::*; + +// Hack to comment out wasm-bindgen's generated typescript definitons #[wasm_bindgen(typescript_custom_section)] const TS_CMT_START: &'static str = "/*"; -fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { - vm.to_pystr(&py_err) - .unwrap_or_else(|_| "Error, and error getting error message".into()) -} - -fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { - let dumps = rustpython_vm::import::import( - vm, - std::path::PathBuf::default(), - "json", - &Some("dumps".into()), - ) - .expect("Couldn't get json.dumps function"); - match vm.invoke(dumps, pyobject::PyFuncArgs::new(vec![py_obj], vec![])) { - Ok(value) => { - let json = vm.to_pystr(&value).unwrap(); - js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED) - } - Err(_) => JsValue::UNDEFINED, - } -} - -fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { - if js_val.is_object() { - if Array::is_array(&js_val) { - let js_arr: Array = js_val.into(); - let elems = js_arr - .values() - .into_iter() - .map(|val| js_to_py(vm, val.expect("Iteration over array failed"))) - .collect(); - vm.ctx.new_list(elems) - } else { - let dict = vm.new_dict(); - for pair in Object::entries(&Object::from(js_val)).values() { - let pair = pair.expect("Iteration over object failed"); - let key = Reflect::get(&pair, &"0".into()).unwrap(); - let val = Reflect::get(&pair, &"1".into()).unwrap(); - let py_val = js_to_py(vm, val); - vm.ctx - .set_item(&dict, &String::from(js_sys::JsString::from(key)), py_val); - } - dict - } - } else if js_val.is_function() { - let func = js_sys::Function::from(js_val); - vm.ctx.new_rustfunc( - move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { - let func = func.clone(); - let this = Object::new(); - for (k, v) in args.kwargs { - Reflect::set(&this, &k.into(), &py_to_js(vm, v)) - .expect("Couldn't set this property"); - } - let js_args = Array::new(); - for v in args.args { - js_args.push(&py_to_js(vm, v)); - } - func.apply(&this, &js_args) - .map(|val| js_to_py(vm, val)) - .map_err(|err| js_to_py(vm, err)) - }, - ) - } else if let Some(err) = js_val.dyn_ref::() { - let exc_type = match String::from(err.name()).as_str() { - "TypeError" => &vm.ctx.exceptions.type_error, - "ReferenceError" => &vm.ctx.exceptions.name_error, - "SyntaxError" => &vm.ctx.exceptions.syntax_error, - _ => &vm.ctx.exceptions.exception_type, - } - .clone(); - vm.new_exception(exc_type, err.message().into()) - } else if js_val.is_undefined() { - // Because `JSON.stringify(undefined)` returns undefined - vm.get_none() - } else { - let loads = rustpython_vm::import::import( - vm, - std::path::PathBuf::default(), - "json", - &Some("loads".into()), - ) - .expect("Couldn't get json.loads function"); - - let json = match js_sys::JSON::stringify(&js_val) { - Ok(json) => String::from(json), - Err(_) => return vm.get_none(), - }; - let py_json = vm.new_str(json); - - vm.invoke(loads, pyobject::PyFuncArgs::new(vec![py_json], vec![])) - // can safely unwrap because we know it's valid JSON - .unwrap() - } -} - fn base_scope(vm: &mut VirtualMachine) -> PyObjectRef { let builtins = vm.get_builtin_scope(); vm.context().new_scope(Some(builtins)) @@ -182,7 +90,7 @@ pub fn eval_py(source: &str, options: Option) -> Result) -> Result) -> Result> = RefCell::default(); +} + +#[wasm_bindgen(js_name = vms)] +pub struct VMStore; + +#[wasm_bindgen(js_class = vms)] +impl VMStore { + pub fn init(id: String) -> WASMVirtualMachine { + STORED_VMS.with(|cell| { + let mut vms = cell.borrow_mut(); + if !vms.contains_key(&id) { + vms.insert(id.clone(), VirtualMachine::new()); + } + }); + WASMVirtualMachine { id } + } + + pub fn get(id: String) -> JsValue { + STORED_VMS.with(|cell| { + let vms = cell.borrow(); + if !vms.contains_key(&id) { + WASMVirtualMachine { id }.into() + } else { + JsValue::UNDEFINED + } + }) + } + + pub fn destroy(id: String) { + STORED_VMS.with(|cell| { + cell.borrow_mut().remove(&id); + }); + } +} + +#[wasm_bindgen(js_name = VirtualMachine)] +pub struct WASMVirtualMachine { + id: String, +} + +#[wasm_bindgen(js_class = VirtualMachine)] +impl WASMVirtualMachine { + pub fn valid(&self) -> bool { + STORED_VMS.with(|cell| cell.borrow().contains_key(&self.id)) + } + + fn assert_valid(&self) -> Result<(), JsValue> { + if self.valid() { + Ok(()) + } else { + Err(TypeError::new( + "Invalid VirtualMachine, this VM was destroyed while this reference was still held", + ) + .into()) + } + } + + // TODO: Add actually useful methods +} diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index ca3ab013aa5..e6605ea796e 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -8,7 +8,7 @@ extern crate js_sys; extern crate wasm_bindgen; extern crate web_sys; -use crate::js_to_py; +use crate::convert; use js_sys::Array; use rustpython_vm::pyobject::{PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; @@ -47,7 +47,7 @@ pub fn format_print_args(vm: &mut VirtualMachine, args: PyFuncArgs) -> Result PyResult { let output = format_print_args(vm, args)?; - print_to_html(&output, selector).map_err(|err| js_to_py(vm, err))?; + print_to_html(&output, selector).map_err(|err| convert::js_to_py(vm, err))?; Ok(vm.get_none()) } From 13b2f83084e3befea4d98ae690708fe7f8ca0ca5 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 30 Jan 2019 12:30:56 -0600 Subject: [PATCH 02/33] Fix vms.get() --- wasm/lib/src/vm_class.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 527dd92ab2d..7405d92d7f5 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -27,7 +27,7 @@ impl VMStore { pub fn get(id: String) -> JsValue { STORED_VMS.with(|cell| { let vms = cell.borrow(); - if !vms.contains_key(&id) { + if vms.contains_key(&id) { WASMVirtualMachine { id }.into() } else { JsValue::UNDEFINED From f064bcaf57db82bad97444c968f0f4c16f33770e Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 30 Jan 2019 12:33:32 -0600 Subject: [PATCH 03/33] Add ids() function to VMStore and destroy() method to VirtualMachine --- wasm/lib/src/vm_class.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 7405d92d7f5..8ed9c4eb127 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -40,6 +40,12 @@ impl VMStore { cell.borrow_mut().remove(&id); }); } + + pub fn ids() -> Vec { + STORED_VMS.with(|cell| { + cell.borrow().keys().map(|k| k.into()).collect() + }) + } } #[wasm_bindgen(js_name = VirtualMachine)] @@ -64,5 +70,9 @@ impl WASMVirtualMachine { } } + fn destroy(self) { + VMStore::destroy(self.id) + } + // TODO: Add actually useful methods } From d0a4f9d7f54273e5f9fb16d957e7e89636c0f3bf Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 30 Jan 2019 12:40:08 -0600 Subject: [PATCH 04/33] Make destroy() method public --- wasm/lib/src/vm_class.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 8ed9c4eb127..85238724d1a 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -70,8 +70,8 @@ impl WASMVirtualMachine { } } - fn destroy(self) { - VMStore::destroy(self.id) + pub fn destroy(self) { + VMStore::destroy(self.id); } // TODO: Add actually useful methods From b32b732abe3a7c88440ee99aa6a66e97c786417e Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 30 Jan 2019 14:35:00 -0600 Subject: [PATCH 05/33] Run cargo fmt --- wasm/lib/src/vm_class.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 85238724d1a..78241fe2f60 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -42,9 +42,7 @@ impl VMStore { } pub fn ids() -> Vec { - STORED_VMS.with(|cell| { - cell.borrow().keys().map(|k| k.into()).collect() - }) + STORED_VMS.with(|cell| cell.borrow().keys().map(|k| k.into()).collect()) } } From 136a476cd8538b7e569855d8d3753b92588d3570 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 31 Jan 2019 16:13:55 -0600 Subject: [PATCH 06/33] Call assert_valid() in destroy() --- wasm/lib/src/vm_class.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 78241fe2f60..ae5fa0992d3 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -68,7 +68,8 @@ impl WASMVirtualMachine { } } - pub fn destroy(self) { + pub fn destroy(self) -> Result<(), JsValue> { + self.assert_valid()?; VMStore::destroy(self.id); } From f47864f0ac9b1f118054579213947a445bf13ecb Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 31 Jan 2019 16:33:11 -0600 Subject: [PATCH 07/33] Return Ok(()) from destroy() --- wasm/lib/src/vm_class.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index ae5fa0992d3..be732d84390 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -71,6 +71,7 @@ impl WASMVirtualMachine { pub fn destroy(self) -> Result<(), JsValue> { self.assert_valid()?; VMStore::destroy(self.id); + Ok(()) } // TODO: Add actually useful methods From 3bec2260bb79800c854f9fd6a2adc094625b8145 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 4 Feb 2019 18:58:53 -0600 Subject: [PATCH 08/33] Rename VMStore --- wasm/lib/src/vm_class.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index be732d84390..90916396fa3 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -9,10 +9,10 @@ thread_local! { static STORED_VMS: RefCell> = RefCell::default(); } -#[wasm_bindgen(js_name = vms)] +#[wasm_bindgen(js_name = vmStore)] pub struct VMStore; -#[wasm_bindgen(js_class = vms)] +#[wasm_bindgen(js_class = vmStore)] impl VMStore { pub fn init(id: String) -> WASMVirtualMachine { STORED_VMS.with(|cell| { From f7c91c7df00bb01c1e9cbdaa6a2f6ee2fcd2c9b2 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 4 Feb 2019 20:36:19 -0600 Subject: [PATCH 09/33] Add add_to_scope() method for WASM VM --- wasm/lib/src/vm_class.rs | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 90916396fa3..92998b145a5 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -1,12 +1,27 @@ +use convert; use js_sys::TypeError; -use rustpython_vm::VirtualMachine; +use rustpython_vm::{pyobject::PyObjectRef, VirtualMachine}; use std::cell::RefCell; use std::collections::HashMap; use wasm_bindgen::prelude::*; +struct StoredVirtualMachine { + pub vm: VirtualMachine, + pub scope: PyObjectRef, +} + +impl StoredVirtualMachine { + fn new() -> StoredVirtualMachine { + let mut vm = VirtualMachine::new(); + let builtin = vm.get_builtin_scope(); + let scope = vm.context().new_scope(Some(builtin)); + StoredVirtualMachine { vm, scope } + } +} + // It's fine that it's thread local, since WASM doesn't even have threads yet thread_local! { - static STORED_VMS: RefCell> = RefCell::default(); + static STORED_VMS: RefCell> = RefCell::default(); } #[wasm_bindgen(js_name = vmStore)] @@ -18,7 +33,7 @@ impl VMStore { STORED_VMS.with(|cell| { let mut vms = cell.borrow_mut(); if !vms.contains_key(&id) { - vms.insert(id.clone(), VirtualMachine::new()); + vms.insert(id.clone(), StoredVirtualMachine::new()); } }); WASMVirtualMachine { id } @@ -35,9 +50,9 @@ impl VMStore { }) } - pub fn destroy(id: String) { + pub fn destroy(id: &String) { STORED_VMS.with(|cell| { - cell.borrow_mut().remove(&id); + cell.borrow_mut().remove(id); }); } @@ -68,11 +83,24 @@ impl WASMVirtualMachine { } } - pub fn destroy(self) -> Result<(), JsValue> { + pub fn destroy(&self) -> Result<(), JsValue> { self.assert_valid()?; - VMStore::destroy(self.id); + VMStore::destroy(&self.id); Ok(()) } - // TODO: Add actually useful methods + #[wasm_bindgen(js_name = addToScope)] + pub fn add_to_scope(&self, name: String, value: JsValue) -> Result<(), JsValue> { + self.assert_valid()?; + STORED_VMS.with(|cell| { + let mut vms = cell.borrow_mut(); + let StoredVirtualMachine { + ref mut vm, + ref mut scope, + } = vms.get_mut(&self.id).unwrap(); + let value = convert::js_to_py(vm, value); + vm.ctx.set_attr(scope, &name, value); + }); + Ok(()) + } } From de3762cabf8493370f44804f5486e65583e23794 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 5 Feb 2019 00:39:16 -0600 Subject: [PATCH 10/33] Add .run(); fix .addToScope(); add panic-catcher for debugging --- Cargo.lock | 11 +++++++++++ wasm/lib/Cargo.toml | 1 + wasm/lib/src/lib.rs | 9 +++++++++ wasm/lib/src/vm_class.rs | 32 +++++++++++++++++++++++++------- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80373d4c108..5287f6b1364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,15 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "constant_time_eq" version = "0.1.3" @@ -723,6 +732,7 @@ name = "rustpython_wasm" version = "0.1.0" dependencies = [ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", @@ -1151,6 +1161,7 @@ dependencies = [ "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6c5dd2c094474ec60a6acaf31780af270275e3153bafff2db5995b715295762e" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" "checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603" diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index bc2ee9d05b2..f7e2d1850c1 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -15,6 +15,7 @@ rustpython_vm = { path = "../../vm" } cfg-if = "0.1.2" wasm-bindgen = "0.2" js-sys = "0.3" +console_error_panic_hook = "0.1" [dependencies.web-sys] version = "0.3" diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 1583457988a..558cef1023b 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -15,6 +15,15 @@ use wasm_bindgen::prelude::*; pub use vm_class::*; +#[cfg(debug_assertions)] +extern crate console_error_panic_hook; + +#[cfg(debug_assertions)] +#[wasm_bindgen(start)] +pub fn setup_console_error() { + console_error_panic_hook::set_once(); +} + // Hack to comment out wasm-bindgen's generated typescript definitons #[wasm_bindgen(typescript_custom_section)] const TS_CMT_START: &'static str = "/*"; diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 92998b145a5..b4a5a264694 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -1,6 +1,6 @@ use convert; use js_sys::TypeError; -use rustpython_vm::{pyobject::PyObjectRef, VirtualMachine}; +use rustpython_vm::{compile, pyobject::PyObjectRef, VirtualMachine}; use std::cell::RefCell; use std::collections::HashMap; use wasm_bindgen::prelude::*; @@ -50,9 +50,9 @@ impl VMStore { }) } - pub fn destroy(id: &String) { + pub fn destroy(id: String) { STORED_VMS.with(|cell| { - cell.borrow_mut().remove(id); + cell.borrow_mut().remove(&id); }); } @@ -85,7 +85,7 @@ impl WASMVirtualMachine { pub fn destroy(&self) -> Result<(), JsValue> { self.assert_valid()?; - VMStore::destroy(&self.id); + VMStore::destroy(self.id.clone()); Ok(()) } @@ -99,8 +99,26 @@ impl WASMVirtualMachine { ref mut scope, } = vms.get_mut(&self.id).unwrap(); let value = convert::js_to_py(vm, value); - vm.ctx.set_attr(scope, &name, value); - }); - Ok(()) + vm.ctx.set_item(scope, &name, value); + Ok(()) + }) + } + + pub fn run(&self, mut source: String) -> Result { + self.assert_valid()?; + STORED_VMS.with(|cell| { + let mut vms = cell.borrow_mut(); + let StoredVirtualMachine { + ref mut vm, + ref mut scope, + } = vms.get_mut(&self.id).unwrap(); + source.push('\n'); + let code = compile::compile(vm, &source, compile::Mode::Single, None) + .map_err(|err| convert::py_str_err(vm, &err))?; + let result = vm + .run_code_obj(code, scope.clone()) + .map_err(|err| convert::py_str_err(vm, &err))?; + Ok(convert::py_to_js(vm, result)) + }) } } From 9b6516e1756147dcb5abcc427100ec03f8e426f7 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 5 Feb 2019 21:04:24 -0600 Subject: [PATCH 11/33] Allow closures to be passed from Python to JS if using a WASM VM --- vm/src/builtins.rs | 3 ++ vm/src/stdlib/mod.rs | 17 ++++-- wasm/lib/src/convert.rs | 98 +++++++++++++++++++++++++++++++---- wasm/lib/src/lib.rs | 15 +++--- wasm/lib/src/vm_class.rs | 75 +++++++++++++++++---------- wasm/lib/src/wasm_builtins.rs | 2 +- 6 files changed, 159 insertions(+), 51 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 83893ee836a..f620d3cd66d 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -18,6 +18,8 @@ use super::pyobject::{ AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, Scope, TypeProtocol, }; + +#[cfg(not(target_arch = "wasm32"))] use super::stdlib::io::io_open; use super::vm::VirtualMachine; @@ -769,6 +771,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&py_mod, "min", ctx.new_rustfunc(builtin_min)); ctx.set_attr(&py_mod, "object", ctx.object()); ctx.set_attr(&py_mod, "oct", ctx.new_rustfunc(builtin_oct)); + #[cfg(not(target_arch = "wasm32"))] ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(io_open)); ctx.set_attr(&py_mod, "ord", ctx.new_rustfunc(builtin_ord)); ctx.set_attr(&py_mod, "next", ctx.new_rustfunc(builtin_next)); diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 159d1cd53d4..40a16e27bc5 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,9 +1,7 @@ mod ast; -pub mod io; mod json; mod keyword; mod math; -mod os; mod pystruct; mod random; mod re; @@ -14,6 +12,11 @@ mod types; mod weakref; use std::collections::HashMap; +#[cfg(not(target_arch = "wasm32"))] +pub mod io; +#[cfg(not(target_arch = "wasm32"))] +mod os; + use super::pyobject::{PyContext, PyObjectRef}; pub type StdlibInitFunc = fn(&PyContext) -> PyObjectRef; @@ -21,11 +24,9 @@ pub type StdlibInitFunc = fn(&PyContext) -> PyObjectRef; pub fn get_module_inits() -> HashMap { let mut modules = HashMap::new(); modules.insert("ast".to_string(), ast::mk_module as StdlibInitFunc); - modules.insert("io".to_string(), io::mk_module as StdlibInitFunc); modules.insert("json".to_string(), json::mk_module as StdlibInitFunc); modules.insert("keyword".to_string(), keyword::mk_module as StdlibInitFunc); modules.insert("math".to_string(), math::mk_module as StdlibInitFunc); - modules.insert("os".to_string(), os::mk_module as StdlibInitFunc); modules.insert("re".to_string(), re::mk_module as StdlibInitFunc); modules.insert("random".to_string(), random::mk_module as StdlibInitFunc); modules.insert("string".to_string(), string::mk_module as StdlibInitFunc); @@ -37,5 +38,13 @@ pub fn get_module_inits() -> HashMap { ); modules.insert("types".to_string(), types::mk_module as StdlibInitFunc); modules.insert("_weakref".to_string(), weakref::mk_module as StdlibInitFunc); + + // disable some modules on WASM + #[cfg(not(target_arch = "wasm32"))] + { + modules.insert("io".to_string(), io::mk_module as StdlibInitFunc); + modules.insert("os".to_string(), os::mk_module as StdlibInitFunc); + } + modules } diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index bdb2fca64d7..983cf946047 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,14 +1,62 @@ use js_sys::{Array, Object, Reflect}; use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; -use wasm_bindgen::{prelude::*, JsCast}; +use vm_class::{StoredVirtualMachine, WASMVirtualMachine}; +use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { vm.to_pystr(&py_err) .unwrap_or_else(|_| "Error, and error getting error message".into()) } -pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { +pub fn py_to_js( + vm: &mut VirtualMachine, + py_obj: PyObjectRef, + wasm_vm: Option, +) -> JsValue { + if let Some(wasm_vm) = wasm_vm { + if rustpython_vm::obj::objtype::isinstance(&py_obj, &vm.ctx.function_type()) { + let closure = Closure::wrap(Box::new( + move |args: Option, kwargs: Option| -> Result { + let py_obj = py_obj.clone(); + let wasm_vm = wasm_vm.clone(); + wasm_vm.assert_valid()?; + let wasm_vm_clone = wasm_vm.clone(); + wasm_vm.with_unchecked(move |StoredVirtualMachine { ref mut vm, .. }| { + let vm: &mut VirtualMachine = vm; + let mut py_func_args = rustpython_vm::pyobject::PyFuncArgs::default(); + if let Some(ref args) = args { + for arg in args.values() { + py_func_args.args.push(js_to_py( + vm, + arg?, + Some(wasm_vm_clone.clone()), + )); + } + } + if let Some(ref kwargs) = kwargs { + for pair in object_entries(kwargs) { + let (key, val) = pair?; + py_func_args.kwargs.push(( + js_sys::JsString::from(key).into(), + js_to_py(vm, val, Some(wasm_vm_clone.clone())), + )); + } + } + let result = vm.invoke(py_obj.clone(), py_func_args); + pyresult_to_jsresult(vm, result, Some(wasm_vm_clone.clone())) + }) + }, + ) + as Box, Option) -> Result>); + let func = closure.as_ref().clone(); + + // TODO: Come up with a way of managing closure handles + closure.forget(); + + return func; + } + } let dumps = rustpython_vm::import::import( vm, std::path::PathBuf::default(), @@ -25,14 +73,46 @@ pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { } } -pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { +pub fn object_entries(obj: &Object) -> impl Iterator> { + Object::entries(obj).values().into_iter().map(|pair| { + pair.map(|pair| { + let key = Reflect::get(&pair, &"0".into()).unwrap(); + let val = Reflect::get(&pair, &"1".into()).unwrap(); + (key, val) + }) + }) +} + +pub fn pyresult_to_jsresult( + vm: &mut VirtualMachine, + result: PyResult, + wasm_vm: Option, +) -> Result { + result + .map(|value| py_to_js(vm, value, wasm_vm)) + .map_err(|err| py_str_err(vm, &err).into()) +} + +pub fn js_to_py( + vm: &mut VirtualMachine, + js_val: JsValue, + // Accept a WASM VM because if js_val is a function it has to be able to convert + // its arguments to JS, and those arguments might include a closure + wasm_vm: Option, +) -> PyObjectRef { if js_val.is_object() { if Array::is_array(&js_val) { let js_arr: Array = js_val.into(); let elems = js_arr .values() .into_iter() - .map(|val| js_to_py(vm, val.expect("Iteration over array failed"))) + .map(|val| { + js_to_py( + vm, + val.expect("Iteration over array failed"), + wasm_vm.clone(), + ) + }) .collect(); vm.ctx.new_list(elems) } else { @@ -41,7 +121,7 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { let pair = pair.expect("Iteration over object failed"); let key = Reflect::get(&pair, &"0".into()).unwrap(); let val = Reflect::get(&pair, &"1".into()).unwrap(); - let py_val = js_to_py(vm, val); + let py_val = js_to_py(vm, val, wasm_vm.clone()); vm.ctx .set_item(&dict, &String::from(js_sys::JsString::from(key)), py_val); } @@ -54,16 +134,16 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { let func = func.clone(); let this = Object::new(); for (k, v) in args.kwargs { - Reflect::set(&this, &k.into(), &py_to_js(vm, v)) + Reflect::set(&this, &k.into(), &py_to_js(vm, v, wasm_vm.clone())) .expect("Couldn't set this property"); } let js_args = Array::new(); for v in args.args { - js_args.push(&py_to_js(vm, v)); + js_args.push(&py_to_js(vm, v, wasm_vm.clone())); } func.apply(&this, &js_args) - .map(|val| js_to_py(vm, val)) - .map_err(|err| js_to_py(vm, err)) + .map(|val| js_to_py(vm, val, wasm_vm.clone())) + .map_err(|err| js_to_py(vm, err, wasm_vm.clone())) }, ) } else if let Some(err) = js_val.dyn_ref::() { diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index a3715db8d0a..f1c70b08945 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -99,7 +99,7 @@ pub fn eval_py(source: &str, options: Option) -> Result) -> Result) -> Result> = RefCell::default(); + static STORED_VMS: Rc>> = Rc::default(); } #[wasm_bindgen(js_name = vmStore)] @@ -62,17 +63,37 @@ impl VMStore { } #[wasm_bindgen(js_name = VirtualMachine)] +#[derive(Clone)] pub struct WASMVirtualMachine { id: String, } #[wasm_bindgen(js_class = VirtualMachine)] impl WASMVirtualMachine { + pub(crate) fn with_unchecked(&self, f: F) -> R + where + F: FnOnce(&mut StoredVirtualMachine) -> R, + { + STORED_VMS.with(|cell| { + let mut vms = cell.borrow_mut(); + let stored_vm = vms.get_mut(&self.id).unwrap(); + f(stored_vm) + }) + } + + pub(crate) fn with(&self, f: F) -> Result + where + F: FnOnce(&mut StoredVirtualMachine) -> R, + { + self.assert_valid()?; + Ok(self.with_unchecked(f)) + } + pub fn valid(&self) -> bool { STORED_VMS.with(|cell| cell.borrow().contains_key(&self.id)) } - fn assert_valid(&self) -> Result<(), JsValue> { + pub fn assert_valid(&self) -> Result<(), JsValue> { if self.valid() { Ok(()) } else { @@ -91,34 +112,32 @@ impl WASMVirtualMachine { #[wasm_bindgen(js_name = addToScope)] pub fn add_to_scope(&self, name: String, value: JsValue) -> Result<(), JsValue> { - self.assert_valid()?; - STORED_VMS.with(|cell| { - let mut vms = cell.borrow_mut(); - let StoredVirtualMachine { - ref mut vm, - ref mut scope, - } = vms.get_mut(&self.id).unwrap(); - let value = convert::js_to_py(vm, value); - vm.ctx.set_item(scope, &name, value); - Ok(()) - }) + self.with( + move |StoredVirtualMachine { + ref mut vm, + ref mut scope, + }| { + let value = convert::js_to_py(vm, value, Some(self.clone())); + vm.ctx.set_item(scope, &name, value); + }, + ) } pub fn run(&self, mut source: String) -> Result { self.assert_valid()?; - STORED_VMS.with(|cell| { - let mut vms = cell.borrow_mut(); - let StoredVirtualMachine { - ref mut vm, - ref mut scope, - } = vms.get_mut(&self.id).unwrap(); - source.push('\n'); - let code = compile::compile(vm, &source, compile::Mode::Single, None) - .map_err(|err| convert::py_str_err(vm, &err))?; - let result = vm - .run_code_obj(code, scope.clone()) - .map_err(|err| convert::py_str_err(vm, &err))?; - Ok(convert::py_to_js(vm, result)) - }) + self.with_unchecked( + |StoredVirtualMachine { + ref mut vm, + ref mut scope, + }| { + source.push('\n'); + let code = compile::compile(vm, &source, &compile::Mode::Single, None) + .map_err(|err| convert::py_str_err(vm, &err))?; + let result = vm + .run_code_obj(code, scope.clone()) + .map_err(|err| convert::py_str_err(vm, &err))?; + Ok(convert::py_to_js(vm, result, Some(self.clone()))) + }, + ) } } diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index e6605ea796e..226c1107cd9 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -47,7 +47,7 @@ pub fn format_print_args(vm: &mut VirtualMachine, args: PyFuncArgs) -> Result PyResult { let output = format_print_args(vm, args)?; - print_to_html(&output, selector).map_err(|err| convert::js_to_py(vm, err))?; + print_to_html(&output, selector).map_err(|err| convert::js_to_py(vm, err, None))?; Ok(vm.get_none()) } From 68517676541b3d6a6ee3d1987f80ba86b24e81c2 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 16 Feb 2019 20:18:27 -0600 Subject: [PATCH 12/33] Fix RefCell borrowing errors --- wasm/lib/src/convert.rs | 55 ++++++++++++++--------------- wasm/lib/src/vm_class.rs | 74 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 983cf946047..52d7ef09ad6 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,7 +1,7 @@ use js_sys::{Array, Object, Reflect}; use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; -use vm_class::{StoredVirtualMachine, WASMVirtualMachine}; +use vm_class::{AccessibleVM, WASMVirtualMachine}; use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { @@ -16,39 +16,36 @@ pub fn py_to_js( ) -> JsValue { if let Some(wasm_vm) = wasm_vm { if rustpython_vm::obj::objtype::isinstance(&py_obj, &vm.ctx.function_type()) { - let closure = Closure::wrap(Box::new( + let closure = move |args: Option, kwargs: Option| -> Result { let py_obj = py_obj.clone(); - let wasm_vm = wasm_vm.clone(); wasm_vm.assert_valid()?; - let wasm_vm_clone = wasm_vm.clone(); - wasm_vm.with_unchecked(move |StoredVirtualMachine { ref mut vm, .. }| { - let vm: &mut VirtualMachine = vm; - let mut py_func_args = rustpython_vm::pyobject::PyFuncArgs::default(); - if let Some(ref args) = args { - for arg in args.values() { - py_func_args.args.push(js_to_py( - vm, - arg?, - Some(wasm_vm_clone.clone()), - )); - } + let acc_vm = AccessibleVM::from(wasm_vm.clone()); + let vm = acc_vm + .upgrade() + .expect("acc. VM to be invalid when WASM vm is valid"); + let mut py_func_args = rustpython_vm::pyobject::PyFuncArgs::default(); + if let Some(ref args) = args { + for arg in args.values() { + py_func_args + .args + .push(js_to_py(vm, arg?, Some(wasm_vm.clone()))); } - if let Some(ref kwargs) = kwargs { - for pair in object_entries(kwargs) { - let (key, val) = pair?; - py_func_args.kwargs.push(( - js_sys::JsString::from(key).into(), - js_to_py(vm, val, Some(wasm_vm_clone.clone())), - )); - } + } + if let Some(ref kwargs) = kwargs { + for pair in object_entries(kwargs) { + let (key, val) = pair?; + py_func_args.kwargs.push(( + js_sys::JsString::from(key).into(), + js_to_py(vm, val, Some(wasm_vm.clone())), + )); } - let result = vm.invoke(py_obj.clone(), py_func_args); - pyresult_to_jsresult(vm, result, Some(wasm_vm_clone.clone())) - }) - }, - ) - as Box, Option) -> Result>); + } + let result = vm.invoke(py_obj.clone(), py_func_args); + pyresult_to_jsresult(vm, result, Some(wasm_vm.clone())) + }; + let closure = Closure::wrap(Box::new(closure) + as Box, Option) -> Result>); let func = closure.as_ref().clone(); // TODO: Come up with a way of managing closure handles diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 0e8fb4361b1..ab464eeab9f 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -1,9 +1,13 @@ use convert; use js_sys::TypeError; -use rustpython_vm::{compile, pyobject::PyObjectRef, VirtualMachine}; +use rustpython_vm::{ + compile, + pyobject::{PyObjectRef, PyRef}, + VirtualMachine, +}; use std::cell::RefCell; use std::collections::HashMap; -use std::rc::Rc; +use std::rc::{Rc, Weak}; use wasm_bindgen::prelude::*; pub(crate) struct StoredVirtualMachine { @@ -22,7 +26,8 @@ impl StoredVirtualMachine { // It's fine that it's thread local, since WASM doesn't even have threads yet thread_local! { - static STORED_VMS: Rc>> = Rc::default(); + static STORED_VMS: PyRef>> = Rc::default(); + static ACTIVE_VMS: PyRef> = Rc::default(); } #[wasm_bindgen(js_name = vmStore)] @@ -34,7 +39,10 @@ impl VMStore { STORED_VMS.with(|cell| { let mut vms = cell.borrow_mut(); if !vms.contains_key(&id) { - vms.insert(id.clone(), StoredVirtualMachine::new()); + vms.insert( + id.clone(), + Rc::new(RefCell::new(StoredVirtualMachine::new())), + ); } }); WASMVirtualMachine { id } @@ -53,7 +61,18 @@ impl VMStore { pub fn destroy(id: String) { STORED_VMS.with(|cell| { - cell.borrow_mut().remove(&id); + use std::collections::hash_map::Entry; + match cell.borrow_mut().entry(id) { + Entry::Occupied(o) => { + let (_k, stored_vm) = o.remove_entry(); + // for f in stored_vm.drop_handlers.iter() { + // f(); + // } + // deallocate the VM + drop(stored_vm); + } + Entry::Vacant(_v) => {} + } }); } @@ -62,6 +81,47 @@ impl VMStore { } } +pub(crate) struct AccessibleVM { + weak: Weak>, + id: String, +} + +impl AccessibleVM { + pub fn from_id(id: String) -> AccessibleVM { + let weak = STORED_VMS + .with(|cell| Rc::downgrade(cell.borrow().get(&id).expect("WASM VM to be valid"))); + AccessibleVM { weak, id } + } + + pub fn upgrade(&self) -> Option<&mut VirtualMachine> { + let vm_cell = self.weak.upgrade()?; + match vm_cell.try_borrow_mut() { + Ok(mut vm) => { + ACTIVE_VMS.with(|cell| { + cell.borrow_mut().insert(self.id.clone(), &mut vm.vm); + }); + } + Err(_) => {} + }; + Some(ACTIVE_VMS.with(|cell| { + let vms = cell.borrow(); + let ptr = vms.get(&self.id).expect("id to be in ACTIVE_VMS"); + unsafe { &mut **ptr } + })) + } +} + +impl From for AccessibleVM { + fn from(vm: WASMVirtualMachine) -> AccessibleVM { + AccessibleVM::from_id(vm.id) + } +} +impl From<&WASMVirtualMachine> for AccessibleVM { + fn from(vm: &WASMVirtualMachine) -> AccessibleVM { + AccessibleVM::from_id(vm.id.clone()) + } +} + #[wasm_bindgen(js_name = VirtualMachine)] #[derive(Clone)] pub struct WASMVirtualMachine { @@ -76,8 +136,8 @@ impl WASMVirtualMachine { { STORED_VMS.with(|cell| { let mut vms = cell.borrow_mut(); - let stored_vm = vms.get_mut(&self.id).unwrap(); - f(stored_vm) + let mut stored_vm = vms.get_mut(&self.id).unwrap().borrow_mut(); + f(&mut stored_vm) }) } From c83ff47f3a7152b2f2253221f9b97713f778dab9 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 16 Feb 2019 20:33:29 -0600 Subject: [PATCH 13/33] Fix error with new compile() and set_attr/item() --- Cargo.lock | 4 ++-- wasm/lib/src/lib.rs | 8 +++----- wasm/lib/src/vm_class.rs | 13 +++++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87ab0364bbb..d260eed5698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -737,7 +737,7 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1232,7 +1232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba451f7bd819b7afc99d4cf4bdcd5a4861e64955ba9680ac70df3a50625ad6cf" "checksum lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60013fd6be14317d43f47658b1440956a9ca48a9ed0257e0e0a59aac13e43a1f" "checksum lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60c6c48ba857cd700673ce88907cadcdd7e2cd7783ed02378537c5ffd4f6460c" -"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum lexical 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4fac65df7e751b57bb3a334c346239cb4ce2601907d698726ceeb82a54ba4ef" "checksum lexical-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "025babf624c0c2b4bed1373efd684d5d0b2eecd61138d26ec3eec77bf0f2e33d" "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 3e3d35b55ab..7adad474177 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -12,17 +12,15 @@ use rustpython_vm::compile; use rustpython_vm::pyobject::{PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; use std::error::Error; -use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen::prelude::*; pub use vm_class::*; -#[cfg(debug_assertions)] extern crate console_error_panic_hook; -#[cfg(debug_assertions)] #[wasm_bindgen(start)] pub fn setup_console_error() { - console_error_panic_hook::set_once(); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); } // Hack to comment out wasm-bindgen's generated typescript definitons @@ -142,7 +140,7 @@ pub fn eval_py(source: &str, options: Option) -> Result".to_string(), + vm.ctx.code_type(), + ) + .map_err(|err| SyntaxError::new(&format!("Error parsing Python code: {}", err)))?; let result = vm .run_code_obj(code, scope.clone()) .map_err(|err| convert::py_str_err(vm, &err))?; From 3e22c4f146dc193093a9793252e1239c15c2e8a7 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 16 Feb 2019 21:43:51 -0600 Subject: [PATCH 14/33] Add print to default vm scope --- wasm/lib/src/vm_class.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 204c8507b18..f36812b446c 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -9,6 +9,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::{Rc, Weak}; use wasm_bindgen::prelude::*; +use wasm_builtins; pub(crate) struct StoredVirtualMachine { pub vm: VirtualMachine, @@ -20,10 +21,19 @@ impl StoredVirtualMachine { let mut vm = VirtualMachine::new(); let builtin = vm.get_builtin_scope(); let scope = vm.context().new_scope(Some(builtin)); + setup_vm_scope(&mut vm, &scope); StoredVirtualMachine { vm, scope } } } +fn setup_vm_scope(vm: &mut VirtualMachine, scope: &PyObjectRef) { + vm.ctx.set_attr( + scope, + "print", + vm.ctx.new_rustfunc(wasm_builtins::builtin_print_console), + ); +} + // It's fine that it's thread local, since WASM doesn't even have threads yet thread_local! { static STORED_VMS: PyRef>> = Rc::default(); From a86069d9f211b6efacc5df0b97a4ee3cb014d6cf Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 16 Feb 2019 23:41:41 -0600 Subject: [PATCH 15/33] Store the WASM id in the VirtualMachine, add a (broken) fetch builtin --- vm/src/lib.rs | 2 +- vm/src/macros.rs | 5 +++ vm/src/vm.rs | 2 + wasm/lib/Cargo.toml | 9 +++- wasm/lib/src/convert.rs | 58 ++++++++---------------- wasm/lib/src/lib.rs | 7 +-- wasm/lib/src/vm_class.rs | 50 +++++++++++---------- wasm/lib/src/wasm_builtins.rs | 83 ++++++++++++++++++++++++++++++++--- 8 files changed, 144 insertions(+), 72 deletions(-) diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 874f3e2a2cf..f785e294d72 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -28,7 +28,7 @@ extern crate rustpython_parser; // This is above everything else so that the defined macros are available everywhere #[macro_use] -mod macros; +pub mod macros; mod builtins; pub mod bytecode; diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 033e7fbc744..0938e5fb6e9 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -1,14 +1,17 @@ // see: https://danielkeep.github.io/tlborm/book/blk-counting.html +#[macro_export] macro_rules! replace_expr { ($_t:tt $sub:expr) => { $sub }; } +#[macro_export] macro_rules! count_tts { ($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*}; } +#[macro_export] macro_rules! type_check { ($vm:ident, $args:ident, $arg_count:ident, $arg_name:ident, $arg_type:expr) => { // None indicates that we have no type requirement (i.e. we accept any type) @@ -30,6 +33,7 @@ macro_rules! type_check { }; } +#[macro_export] macro_rules! arg_check { ( $vm: ident, $args:ident ) => { // Zero-arg case @@ -85,6 +89,7 @@ macro_rules! arg_check { }; } +#[macro_export] macro_rules! no_kwargs { ( $vm: ident, $args:ident ) => { // Zero-arg case diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 45b88e45d32..894f9e075d9 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -39,6 +39,7 @@ pub struct VirtualMachine { pub stdlib_inits: HashMap, pub ctx: PyContext, pub current_frame: Option, + pub wasm_id: Option, } impl VirtualMachine { @@ -61,6 +62,7 @@ impl VirtualMachine { stdlib_inits, ctx, current_frame: None, + wasm_id: None, } } diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index f7e2d1850c1..69880155b6d 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -19,4 +19,11 @@ console_error_panic_hook = "0.1" [dependencies.web-sys] version = "0.3" -features = [ "console", "Document", "Element", "HtmlTextAreaElement", "Window" ] +features = [ + "console", + "Document", + "Element", + "HtmlTextAreaElement", + "Window", + "Response" +] diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 52d7ef09ad6..d8510db4fcc 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -9,12 +9,11 @@ pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { .unwrap_or_else(|_| "Error, and error getting error message".into()) } -pub fn py_to_js( - vm: &mut VirtualMachine, - py_obj: PyObjectRef, - wasm_vm: Option, -) -> JsValue { - if let Some(wasm_vm) = wasm_vm { +pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { + if let Some(ref wasm_id) = vm.wasm_id { + let wasm_vm = WASMVirtualMachine { + id: wasm_id.clone(), + }; if rustpython_vm::obj::objtype::isinstance(&py_obj, &vm.ctx.function_type()) { let closure = move |args: Option, kwargs: Option| -> Result { @@ -27,22 +26,19 @@ pub fn py_to_js( let mut py_func_args = rustpython_vm::pyobject::PyFuncArgs::default(); if let Some(ref args) = args { for arg in args.values() { - py_func_args - .args - .push(js_to_py(vm, arg?, Some(wasm_vm.clone()))); + py_func_args.args.push(js_to_py(vm, arg?)); } } if let Some(ref kwargs) = kwargs { for pair in object_entries(kwargs) { let (key, val) = pair?; - py_func_args.kwargs.push(( - js_sys::JsString::from(key).into(), - js_to_py(vm, val, Some(wasm_vm.clone())), - )); + py_func_args + .kwargs + .push((js_sys::JsString::from(key).into(), js_to_py(vm, val))); } } let result = vm.invoke(py_obj.clone(), py_func_args); - pyresult_to_jsresult(vm, result, Some(wasm_vm.clone())) + pyresult_to_jsresult(vm, result) }; let closure = Closure::wrap(Box::new(closure) as Box, Option) -> Result>); @@ -80,36 +76,20 @@ pub fn object_entries(obj: &Object) -> impl Iterator, -) -> Result { +pub fn pyresult_to_jsresult(vm: &mut VirtualMachine, result: PyResult) -> Result { result - .map(|value| py_to_js(vm, value, wasm_vm)) + .map(|value| py_to_js(vm, value)) .map_err(|err| py_str_err(vm, &err).into()) } -pub fn js_to_py( - vm: &mut VirtualMachine, - js_val: JsValue, - // Accept a WASM VM because if js_val is a function it has to be able to convert - // its arguments to JS, and those arguments might include a closure - wasm_vm: Option, -) -> PyObjectRef { +pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { if js_val.is_object() { if Array::is_array(&js_val) { let js_arr: Array = js_val.into(); let elems = js_arr .values() .into_iter() - .map(|val| { - js_to_py( - vm, - val.expect("Iteration over array failed"), - wasm_vm.clone(), - ) - }) + .map(|val| js_to_py(vm, val.expect("Iteration over array failed"))) .collect(); vm.ctx.new_list(elems) } else { @@ -118,7 +98,7 @@ pub fn js_to_py( let pair = pair.expect("Iteration over object failed"); let key = Reflect::get(&pair, &"0".into()).unwrap(); let val = Reflect::get(&pair, &"1".into()).unwrap(); - let py_val = js_to_py(vm, val, wasm_vm.clone()); + let py_val = js_to_py(vm, val); vm.ctx .set_item(&dict, &String::from(js_sys::JsString::from(key)), py_val); } @@ -131,16 +111,16 @@ pub fn js_to_py( let func = func.clone(); let this = Object::new(); for (k, v) in args.kwargs { - Reflect::set(&this, &k.into(), &py_to_js(vm, v, wasm_vm.clone())) + Reflect::set(&this, &k.into(), &py_to_js(vm, v)) .expect("Couldn't set this property"); } let js_args = Array::new(); for v in args.args { - js_args.push(&py_to_js(vm, v, wasm_vm.clone())); + js_args.push(&py_to_js(vm, v)); } func.apply(&this, &js_args) - .map(|val| js_to_py(vm, val, wasm_vm.clone())) - .map_err(|err| js_to_py(vm, err, wasm_vm.clone())) + .map(|val| js_to_py(vm, val)) + .map_err(|err| js_to_py(vm, err)) }, ) } else if let Some(err) = js_val.dyn_ref::() { diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 7adad474177..4e35cc72fc7 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -3,6 +3,7 @@ mod vm_class; mod wasm_builtins; extern crate js_sys; +#[macro_use] extern crate rustpython_vm; extern crate wasm_bindgen; extern crate web_sys; @@ -107,7 +108,7 @@ pub fn eval_py(source: &str, options: Option) -> Result) -> Result) -> Result StoredVirtualMachine { + fn new(id: String, inject_builtins: bool) -> StoredVirtualMachine { let mut vm = VirtualMachine::new(); let builtin = vm.get_builtin_scope(); let scope = vm.context().new_scope(Some(builtin)); - setup_vm_scope(&mut vm, &scope); + if inject_builtins { + setup_wasm_builtins(&mut vm, &scope); + } + vm.wasm_id = Some(id); StoredVirtualMachine { vm, scope } } } -fn setup_vm_scope(vm: &mut VirtualMachine, scope: &PyObjectRef) { - vm.ctx.set_attr( - scope, - "print", - vm.ctx.new_rustfunc(wasm_builtins::builtin_print_console), - ); -} - // It's fine that it's thread local, since WASM doesn't even have threads yet thread_local! { static STORED_VMS: PyRef>> = Rc::default(); @@ -45,14 +40,13 @@ pub struct VMStore; #[wasm_bindgen(js_class = vmStore)] impl VMStore { - pub fn init(id: String) -> WASMVirtualMachine { + pub fn init(id: String, inject_builtins: Option) -> WASMVirtualMachine { STORED_VMS.with(|cell| { let mut vms = cell.borrow_mut(); if !vms.contains_key(&id) { - vms.insert( - id.clone(), - Rc::new(RefCell::new(StoredVirtualMachine::new())), - ); + let stored_vm = + StoredVirtualMachine::new(id.clone(), inject_builtins.unwrap_or(true)); + vms.insert(id.clone(), Rc::new(RefCell::new(stored_vm))); } }); WASMVirtualMachine { id } @@ -91,6 +85,7 @@ impl VMStore { } } +#[derive(Clone)] pub(crate) struct AccessibleVM { weak: Weak>, id: String, @@ -103,6 +98,14 @@ impl AccessibleVM { AccessibleVM { weak, id } } + pub fn from_vm(vm: &VirtualMachine) -> AccessibleVM { + AccessibleVM::from_id( + vm.wasm_id + .clone() + .expect("VM passed to from_vm to have wasm_id be Some()"), + ) + } + pub fn upgrade(&self) -> Option<&mut VirtualMachine> { let vm_cell = self.weak.upgrade()?; match vm_cell.try_borrow_mut() { @@ -135,7 +138,7 @@ impl From<&WASMVirtualMachine> for AccessibleVM { #[wasm_bindgen(js_name = VirtualMachine)] #[derive(Clone)] pub struct WASMVirtualMachine { - id: String, + pub(crate) id: String, } #[wasm_bindgen(js_class = VirtualMachine)] @@ -144,11 +147,12 @@ impl WASMVirtualMachine { where F: FnOnce(&mut StoredVirtualMachine) -> R, { - STORED_VMS.with(|cell| { + let stored_vm = STORED_VMS.with(|cell| { let mut vms = cell.borrow_mut(); - let mut stored_vm = vms.get_mut(&self.id).unwrap().borrow_mut(); - f(&mut stored_vm) - }) + vms.get_mut(&self.id).unwrap().clone() + }); + let mut stored_vm = stored_vm.borrow_mut(); + f(&mut stored_vm) } pub(crate) fn with(&self, f: F) -> Result @@ -187,7 +191,7 @@ impl WASMVirtualMachine { ref mut vm, ref mut scope, }| { - let value = convert::js_to_py(vm, value, Some(self.clone())); + let value = convert::js_to_py(vm, value); vm.ctx.set_attr(scope, &name, value); }, ) @@ -211,7 +215,7 @@ impl WASMVirtualMachine { let result = vm .run_code_obj(code, scope.clone()) .map_err(|err| convert::py_str_err(vm, &err))?; - Ok(convert::py_to_js(vm, result, Some(self.clone()))) + Ok(convert::py_to_js(vm, result)) }, ) } diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index 26350534218..13b59630890 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -8,18 +8,22 @@ extern crate js_sys; extern crate wasm_bindgen; extern crate web_sys; -use crate::convert; +use crate::{convert, vm_class::AccessibleVM}; use js_sys::Array; use rustpython_vm::obj::{objstr, objtype}; use rustpython_vm::pyobject::{IdProtocol, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use rustpython_vm::VirtualMachine; -use wasm_bindgen::{JsCast, JsValue}; -use web_sys::{console, window, HtmlTextAreaElement}; +use wasm_bindgen::{prelude::*, JsCast}; +use web_sys::{console, HtmlTextAreaElement}; + +fn window() -> web_sys::Window { + web_sys::window().expect("Window to be available") +} // The HTML id of the textarea element that act as our STDOUT pub fn print_to_html(text: &str, selector: &str) -> Result<(), JsValue> { - let document = window().unwrap().document().unwrap(); + let document = window().document().expect("Document to be available"); let element = document .query_selector(selector)? .ok_or_else(|| js_sys::TypeError::new("Couldn't get element"))?; @@ -85,7 +89,7 @@ pub fn format_print_args(vm: &mut VirtualMachine, args: PyFuncArgs) -> Result PyResult { let output = format_print_args(vm, args)?; - print_to_html(&output, selector).map_err(|err| convert::js_to_py(vm, err, None))?; + print_to_html(&output, selector).map_err(|err| convert::js_to_py(vm, err))?; Ok(vm.get_none()) } @@ -97,3 +101,72 @@ pub fn builtin_print_console(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyRes console::log(&arr); Ok(vm.get_none()) } + +enum FetchResponseFormat { + Json, + Text, +} + +impl FetchResponseFormat { + fn from_str(vm: &mut VirtualMachine, s: &str) -> Result { + match s { + "json" => Ok(FetchResponseFormat::Json), + "text" => Ok(FetchResponseFormat::Text), + _ => Err(vm.new_type_error("Unkown fetch response_format".into())), + } + } + fn get_response(&self, response: &web_sys::Response) -> js_sys::Promise { + let prom = match self { + FetchResponseFormat::Json => response.json(), + FetchResponseFormat::Text => response.text(), + }; + prom.expect("response method not to throw error") + } +} + +fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (url, Some(vm.ctx.str_type())), + (handler, Some(vm.ctx.function_type())) + ], + optional = [(response_format, Some(vm.ctx.str_type()))] + ); + let response_format = match response_format { + Some(s) => FetchResponseFormat::from_str(vm, &objstr::get_value(s))?, + None => FetchResponseFormat::Text, + }; + let window = window(); + let response_prom = window.fetch_with_str(&objstr::get_value(url)); + let handler = handler.clone(); + let acc_vm = AccessibleVM::from_vm(vm); + response_prom.then(&Closure::wrap(Box::new(move |val: JsValue| { + let response = val + .dyn_into::() + .expect("val to be of type Response"); + web_sys::console::log_1(&"hey".into()); + let prom = response_format.get_response(&response); + let acc_vm = acc_vm.clone(); + let handler = handler.clone(); + prom.then(&Closure::wrap(Box::new(move |val: JsValue| { + let acc_vm = acc_vm.clone(); + let handler = handler.clone(); + let vm = acc_vm + .upgrade() + .expect("[Achievement unlucked] How did you get here?"); + let val = convert::js_to_py(vm, val); + let mut args = PyFuncArgs::default(); + args.args.push(val); + let _ = vm.invoke(handler, args); + }))); + }))); + Ok(vm.get_none()) +} + +pub fn setup_wasm_builtins(vm: &mut VirtualMachine, scope: &PyObjectRef) { + let ctx = vm.context(); + ctx.set_attr(scope, "print", ctx.new_rustfunc(builtin_print_console)); + ctx.set_attr(scope, "fetch", ctx.new_rustfunc(builtin_fetch)); +} From f00c3c8a7bc767413d12c7c22f13ef6ce628bcec Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 00:24:58 -0600 Subject: [PATCH 16/33] Fix fetch builtin --- wasm/lib/src/wasm_builtins.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index 13b59630890..47d1f356c27 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -142,15 +142,14 @@ fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let response_prom = window.fetch_with_str(&objstr::get_value(url)); let handler = handler.clone(); let acc_vm = AccessibleVM::from_vm(vm); - response_prom.then(&Closure::wrap(Box::new(move |val: JsValue| { + let closure = Closure::wrap(Box::new(move |val: JsValue| { let response = val .dyn_into::() .expect("val to be of type Response"); - web_sys::console::log_1(&"hey".into()); let prom = response_format.get_response(&response); let acc_vm = acc_vm.clone(); let handler = handler.clone(); - prom.then(&Closure::wrap(Box::new(move |val: JsValue| { + let closure = Closure::wrap(Box::new(move |val: JsValue| { let acc_vm = acc_vm.clone(); let handler = handler.clone(); let vm = acc_vm @@ -160,8 +159,19 @@ fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut args = PyFuncArgs::default(); args.args.push(val); let _ = vm.invoke(handler, args); - }))); - }))); + }) as Box); + + prom.then(&closure); + + // TODO: What to do about this: + closure.forget(); + }) as Box); + + response_prom.then(&closure); + + // TODO: and this: + closure.forget(); + Ok(vm.get_none()) } From 00bc9e919918bfbc6a61687bb3f31de3ae6d676a Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 01:03:05 -0600 Subject: [PATCH 17/33] Convert fetch to use wasm_bindgen_futures --- Cargo.lock | 19 ++++++++ wasm/lib/Cargo.toml | 2 + wasm/lib/src/lib.rs | 2 + wasm/lib/src/wasm_builtins.rs | 85 ++++++++++++++++++++--------------- 4 files changed, 72 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d260eed5698..dfab5148a2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,6 +301,11 @@ name = "fuchsia-zircon-sys" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "generic-array" version = "0.9.0" @@ -760,10 +765,12 @@ version = "0.1.0" dependencies = [ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "console_error_panic_hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-futures 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1062,6 +1069,16 @@ dependencies = [ "wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.29" @@ -1222,6 +1239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" "checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" @@ -1309,6 +1327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "91f95b8f30407b9ca0c2de157281d3828bbed1fc1f55bea6eb54f40c52ec75ec" "checksum wasm-bindgen-backend 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "ab7c242ebcb45bae45340986c48d1853eb2c1c52ff551f7724951b62a2c51429" +"checksum wasm-bindgen-futures 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d1784e7401a90119b2a4e8ec9c8d37c3594c3e3bb9ba24533ee1969eebaf0485" "checksum wasm-bindgen-macro 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "6e353f83716dec9a3597b5719ef88cb6c9e461ec16528f38aa023d3224b4e569" "checksum wasm-bindgen-macro-support 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "3cc90b65fe69c3dd5a09684517dc79f42b847baa2d479c234d125e0a629d9b0a" "checksum wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "a71a37df4f5845025f96f279d20bbe5b19cbcb77f5410a3a90c6c544d889a162" diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 69880155b6d..93bd215193a 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -14,7 +14,9 @@ rustpython_parser = { path = "../../parser" } rustpython_vm = { path = "../../vm" } cfg-if = "0.1.2" wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.3" js-sys = "0.3" +futures = "0.1" console_error_panic_hook = "0.1" [dependencies.web-sys] diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 4e35cc72fc7..a6dfde7eb93 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -5,7 +5,9 @@ mod wasm_builtins; extern crate js_sys; #[macro_use] extern crate rustpython_vm; +extern crate futures; extern crate wasm_bindgen; +extern crate wasm_bindgen_futures; extern crate web_sys; use js_sys::{Object, Reflect, TypeError}; diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index 47d1f356c27..b6764d60e71 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -2,18 +2,23 @@ //! //! This is required because some feature like I/O works differently in the browser comparing to //! desktop. -//! Implements functions listed here: https://docs.python.org/3/library/builtins.html -//! +//! Implements functions listed here: https://docs.python.org/3/library/builtins.html and some +//! others. + +extern crate futures; extern crate js_sys; extern crate wasm_bindgen; +extern crate wasm_bindgen_futures; extern crate web_sys; use crate::{convert, vm_class::AccessibleVM}; -use js_sys::Array; +use futures::{future, Future}; +use js_sys::{Array, Promise}; use rustpython_vm::obj::{objstr, objtype}; use rustpython_vm::pyobject::{IdProtocol, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use rustpython_vm::VirtualMachine; use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen_futures::{future_to_promise, JsFuture}; use web_sys::{console, HtmlTextAreaElement}; fn window() -> web_sys::Window { @@ -115,12 +120,11 @@ impl FetchResponseFormat { _ => Err(vm.new_type_error("Unkown fetch response_format".into())), } } - fn get_response(&self, response: &web_sys::Response) -> js_sys::Promise { - let prom = match self { + fn get_response(&self, response: &web_sys::Response) -> Result { + match self { FetchResponseFormat::Json => response.json(), FetchResponseFormat::Text => response.text(), - }; - prom.expect("response method not to throw error") + } } } @@ -132,45 +136,54 @@ fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { (url, Some(vm.ctx.str_type())), (handler, Some(vm.ctx.function_type())) ], - optional = [(response_format, Some(vm.ctx.str_type()))] + optional = [ + (response_format, Some(vm.ctx.str_type())), + (reject_handler, Some(vm.ctx.function_type())) + ] ); + let response_format = match response_format { Some(s) => FetchResponseFormat::from_str(vm, &objstr::get_value(s))?, None => FetchResponseFormat::Text, }; + let window = window(); - let response_prom = window.fetch_with_str(&objstr::get_value(url)); + let request_prom = window.fetch_with_str(&objstr::get_value(url)); + let handler = handler.clone(); + let reject_handler = reject_handler.cloned(); + let acc_vm = AccessibleVM::from_vm(vm); - let closure = Closure::wrap(Box::new(move |val: JsValue| { - let response = val - .dyn_into::() - .expect("val to be of type Response"); - let prom = response_format.get_response(&response); - let acc_vm = acc_vm.clone(); - let handler = handler.clone(); - let closure = Closure::wrap(Box::new(move |val: JsValue| { - let acc_vm = acc_vm.clone(); - let handler = handler.clone(); + + let future = JsFuture::from(request_prom) + .and_then(move |val| { + let response = val + .dyn_into::() + .expect("val to be of type Response"); + response_format.get_response(&response) + }) + .and_then(|prom| JsFuture::from(prom)) + .then(move |val| { let vm = acc_vm .upgrade() - .expect("[Achievement unlucked] How did you get here?"); - let val = convert::js_to_py(vm, val); - let mut args = PyFuncArgs::default(); - args.args.push(val); - let _ = vm.invoke(handler, args); - }) as Box); - - prom.then(&closure); - - // TODO: What to do about this: - closure.forget(); - }) as Box); - - response_prom.then(&closure); - - // TODO: and this: - closure.forget(); + .expect("that the VM *not* be destroyed while promise is being resolved"); + match val { + Ok(val) => { + let val = convert::js_to_py(vm, val); + let args = PyFuncArgs::new(vec![val], vec![]); + let _ = vm.invoke(handler, args); + } + Err(val) => { + if let Some(reject_handler) = reject_handler { + let val = convert::js_to_py(vm, val); + let args = PyFuncArgs::new(vec![val], vec![]); + let _ = vm.invoke(reject_handler, args); + } + } + } + future::ok(JsValue::UNDEFINED) + }); + future_to_promise(future); Ok(vm.get_none()) } From d0b4751ab2f84de0825b18d4e8724ff02eed0a13 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 08:08:22 -0600 Subject: [PATCH 18/33] Remove the VM pointer from the map when it's dropped --- wasm/lib/src/convert.rs | 2 +- wasm/lib/src/vm_class.rs | 41 +++++++++++++++++++++++++++++++---- wasm/lib/src/wasm_builtins.rs | 2 +- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index d8510db4fcc..379fc86c16f 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -20,7 +20,7 @@ pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { let py_obj = py_obj.clone(); wasm_vm.assert_valid()?; let acc_vm = AccessibleVM::from(wasm_vm.clone()); - let vm = acc_vm + let vm = &mut acc_vm .upgrade() .expect("acc. VM to be invalid when WASM vm is valid"); let mut py_func_args = rustpython_vm::pyobject::PyFuncArgs::default(); diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index c9c210451fc..319f116b456 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -106,20 +106,26 @@ impl AccessibleVM { ) } - pub fn upgrade(&self) -> Option<&mut VirtualMachine> { + pub fn upgrade(&self) -> Option { let vm_cell = self.weak.upgrade()?; - match vm_cell.try_borrow_mut() { + let top_level = match vm_cell.try_borrow_mut() { Ok(mut vm) => { ACTIVE_VMS.with(|cell| { cell.borrow_mut().insert(self.id.clone(), &mut vm.vm); }); + true } - Err(_) => {} + Err(_) => false, }; Some(ACTIVE_VMS.with(|cell| { let vms = cell.borrow(); let ptr = vms.get(&self.id).expect("id to be in ACTIVE_VMS"); - unsafe { &mut **ptr } + let vm = unsafe { &mut **ptr }; + AccessibleVMPtr { + id: self.id.clone(), + top_level, + inner: vm, + } })) } } @@ -135,6 +141,33 @@ impl From<&WASMVirtualMachine> for AccessibleVM { } } +pub(crate) struct AccessibleVMPtr<'a> { + id: String, + top_level: bool, + inner: &'a mut VirtualMachine, +} + +impl std::ops::Deref for AccessibleVMPtr<'_> { + type Target = VirtualMachine; + fn deref(&self) -> &VirtualMachine { + &self.inner + } +} +impl std::ops::DerefMut for AccessibleVMPtr<'_> { + fn deref_mut(&mut self) -> &mut VirtualMachine { + &mut self.inner + } +} + +impl Drop for AccessibleVMPtr<'_> { + fn drop(&mut self) { + if self.top_level { + // remove the (now invalid) pointer from the map + ACTIVE_VMS.with(|cell| cell.borrow_mut().remove(&self.id)); + } + } +} + #[wasm_bindgen(js_name = VirtualMachine)] #[derive(Clone)] pub struct WASMVirtualMachine { diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index b6764d60e71..2f1d843ff8a 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -164,7 +164,7 @@ fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }) .and_then(|prom| JsFuture::from(prom)) .then(move |val| { - let vm = acc_vm + let vm = &mut acc_vm .upgrade() .expect("that the VM *not* be destroyed while promise is being resolved"); match val { From 7405c842c8e37019b9feb619d2f86c4f61e9bc11 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 08:49:19 -0600 Subject: [PATCH 19/33] Add some more options to fetch() --- wasm/lib/Cargo.toml | 3 +++ wasm/lib/src/convert.rs | 5 +++++ wasm/lib/src/wasm_builtins.rs | 30 +++++++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 93bd215193a..2a8000a5a9b 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -27,5 +27,8 @@ features = [ "Element", "HtmlTextAreaElement", "Window", + "Headers", + "Request", + "RequestInit", "Response" ] diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 379fc86c16f..3b4109a8b66 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -9,6 +9,11 @@ pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { .unwrap_or_else(|_| "Error, and error getting error message".into()) } +pub fn js_py_typeerror(vm: &mut VirtualMachine, js_err: JsValue) -> PyObjectRef { + let msg = js_sys::JsString::from(js_err); + vm.new_type_error(msg.into()) +} + pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { if let Some(ref wasm_id) = vm.wasm_id { let wasm_vm = WASMVirtualMachine { diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index 2f1d843ff8a..a42edeeea52 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -13,7 +13,7 @@ extern crate web_sys; use crate::{convert, vm_class::AccessibleVM}; use futures::{future, Future}; -use js_sys::{Array, Promise}; +use js_sys::{Array, JsString, Promise}; use rustpython_vm::obj::{objstr, objtype}; use rustpython_vm::pyobject::{IdProtocol, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use rustpython_vm::VirtualMachine; @@ -136,9 +136,12 @@ fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { (url, Some(vm.ctx.str_type())), (handler, Some(vm.ctx.function_type())) ], + // TODO: use named parameters for these optional = [ + (reject_handler, Some(vm.ctx.function_type())), (response_format, Some(vm.ctx.str_type())), - (reject_handler, Some(vm.ctx.function_type())) + (method, Some(vm.ctx.str_type())), + (headers, Some(vm.ctx.dict_type())) ] ); @@ -147,8 +150,29 @@ fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { None => FetchResponseFormat::Text, }; + let mut opts = web_sys::RequestInit::new(); + + match method { + Some(s) => opts.method(&objstr::get_value(s)), + None => opts.method("GET"), + }; + + let request = web_sys::Request::new_with_str_and_init(&objstr::get_value(url), &opts) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + + if let Some(headers) = headers { + use rustpython_vm::obj::objdict; + let h = request.headers(); + for (key, value) in objdict::get_key_value_pairs(headers) { + let key = objstr::get_value(&vm.to_str(&key)?); + let value = objstr::get_value(&vm.to_str(&value)?); + h.set(&key, &value) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + } + } + let window = window(); - let request_prom = window.fetch_with_str(&objstr::get_value(url)); + let request_prom = window.fetch_with_request(&request); let handler = handler.clone(); let reject_handler = reject_handler.cloned(); From b043f2199fc9777ba6620d5e012af1b37cf33e6e Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 09:14:06 -0600 Subject: [PATCH 20/33] Fix js_py_typeerror --- wasm/lib/src/convert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 3b4109a8b66..ed6f7d51604 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -10,7 +10,7 @@ pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { } pub fn js_py_typeerror(vm: &mut VirtualMachine, js_err: JsValue) -> PyObjectRef { - let msg = js_sys::JsString::from(js_err); + let msg = js_err.unchecked_into::().to_string(); vm.new_type_error(msg.into()) } From 416f088b32647f4d07b8af3cc4bdd0c5cd9aef16 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 11:22:29 -0600 Subject: [PATCH 21/33] Convert `ArrayBuffer`s and `TypedArray`s to Python bytearrays --- wasm/lib/src/convert.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index ed6f7d51604..80d18d2b7ec 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,4 +1,4 @@ -use js_sys::{Array, Object, Reflect}; +use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; use vm_class::{AccessibleVM, WASMVirtualMachine}; @@ -137,6 +137,19 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { } .clone(); vm.new_exception(exc_type, err.message().into()) + } else if ArrayBuffer::is_view(&js_val) || js_val.is_instance_of::() { + // unchecked_ref because if it's not an ArrayByffer it could either be a TypedArray + // or a DataView, but they all have a `buffer` property + let mut u8_array = js_sys::Uint8Array::new( + &js_val + .dyn_ref::() + .cloned() + .unwrap_or_else(|| js_val.unchecked_ref::().buffer()), + ); + let mut vec = Vec::with_capacity(u8_array.length() as usize); + // TODO: use Uint8Array::copy_to once updating js_sys doesn't break everything + u8_array.for_each(&mut |byte, _, _| vec.push(byte)); + vm.ctx.new_bytearray(vec) } else if js_val.is_undefined() { // Because `JSON.stringify(undefined)` returns undefined vm.get_none() From bead3f67d01bde2779fd27c5762c7a8fa1241a4d Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 11:50:05 -0600 Subject: [PATCH 22/33] Fix conversion from ArrayBuffer, allow array_buffer in fetch --- wasm/lib/src/convert.rs | 26 +++++++++++++------------- wasm/lib/src/wasm_builtins.rs | 5 ++++- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 80d18d2b7ec..cc13ab84201 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -97,6 +97,19 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { .map(|val| js_to_py(vm, val.expect("Iteration over array failed"))) .collect(); vm.ctx.new_list(elems) + } else if ArrayBuffer::is_view(&js_val) || js_val.is_instance_of::() { + // unchecked_ref because if it's not an ArrayByffer it could either be a TypedArray + // or a DataView, but they all have a `buffer` property + let u8_array = js_sys::Uint8Array::new( + &js_val + .dyn_ref::() + .cloned() + .unwrap_or_else(|| js_val.unchecked_ref::().buffer()), + ); + let mut vec = Vec::with_capacity(u8_array.length() as usize); + // TODO: use Uint8Array::copy_to once updating js_sys doesn't break everything + u8_array.for_each(&mut |byte, _, _| vec.push(byte)); + vm.ctx.new_bytes(vec) } else { let dict = vm.new_dict(); for pair in Object::entries(&Object::from(js_val)).values() { @@ -137,19 +150,6 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { } .clone(); vm.new_exception(exc_type, err.message().into()) - } else if ArrayBuffer::is_view(&js_val) || js_val.is_instance_of::() { - // unchecked_ref because if it's not an ArrayByffer it could either be a TypedArray - // or a DataView, but they all have a `buffer` property - let mut u8_array = js_sys::Uint8Array::new( - &js_val - .dyn_ref::() - .cloned() - .unwrap_or_else(|| js_val.unchecked_ref::().buffer()), - ); - let mut vec = Vec::with_capacity(u8_array.length() as usize); - // TODO: use Uint8Array::copy_to once updating js_sys doesn't break everything - u8_array.for_each(&mut |byte, _, _| vec.push(byte)); - vm.ctx.new_bytearray(vec) } else if js_val.is_undefined() { // Because `JSON.stringify(undefined)` returns undefined vm.get_none() diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index a42edeeea52..e1fee0f8d47 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -13,7 +13,7 @@ extern crate web_sys; use crate::{convert, vm_class::AccessibleVM}; use futures::{future, Future}; -use js_sys::{Array, JsString, Promise}; +use js_sys::{Array, Promise}; use rustpython_vm::obj::{objstr, objtype}; use rustpython_vm::pyobject::{IdProtocol, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use rustpython_vm::VirtualMachine; @@ -110,6 +110,7 @@ pub fn builtin_print_console(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyRes enum FetchResponseFormat { Json, Text, + ArrayBuffer, } impl FetchResponseFormat { @@ -117,6 +118,7 @@ impl FetchResponseFormat { match s { "json" => Ok(FetchResponseFormat::Json), "text" => Ok(FetchResponseFormat::Text), + "array_buffer" => Ok(FetchResponseFormat::ArrayBuffer), _ => Err(vm.new_type_error("Unkown fetch response_format".into())), } } @@ -124,6 +126,7 @@ impl FetchResponseFormat { match self { FetchResponseFormat::Json => response.json(), FetchResponseFormat::Text => response.text(), + FetchResponseFormat::ArrayBuffer => response.array_buffer(), } } } From 524470789f7b4f27742badafb8d2049f7ff91331 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 13:19:19 -0600 Subject: [PATCH 23/33] Add eval() method, rename run() to exec() --- wasm/lib/src/vm_class.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 319f116b456..f2bde30d0ff 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -230,7 +230,7 @@ impl WASMVirtualMachine { ) } - pub fn run(&self, mut source: String) -> Result { + fn run(&self, mut source: String, mode: compile::Mode) -> Result { self.assert_valid()?; self.with_unchecked( |StoredVirtualMachine { @@ -238,13 +238,11 @@ impl WASMVirtualMachine { ref mut scope, }| { source.push('\n'); - let code = compile::compile( - &source, - &compile::Mode::Exec, - "".to_string(), - vm.ctx.code_type(), - ) - .map_err(|err| SyntaxError::new(&format!("Error parsing Python code: {}", err)))?; + let code = + compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()) + .map_err(|err| { + SyntaxError::new(&format!("Error parsing Python code: {}", err)) + })?; let result = vm .run_code_obj(code, scope.clone()) .map_err(|err| convert::py_str_err(vm, &err))?; @@ -252,4 +250,12 @@ impl WASMVirtualMachine { }, ) } + + pub fn exec(&self, source: String) -> Result { + self.run(source, compile::Mode::Exec) + } + + pub fn eval(&self, source: String) -> Result { + self.run(source, compile::Mode::Eval) + } } From 74e713197a6d7dd1dfa5ea421cb9239b0862860f Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 18:22:11 -0600 Subject: [PATCH 24/33] Clean up some code --- wasm/lib/src/convert.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index cc13ab84201..b1d3d08b37c 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -112,10 +112,8 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { vm.ctx.new_bytes(vec) } else { let dict = vm.new_dict(); - for pair in Object::entries(&Object::from(js_val)).values() { - let pair = pair.expect("Iteration over object failed"); - let key = Reflect::get(&pair, &"0".into()).unwrap(); - let val = Reflect::get(&pair, &"1".into()).unwrap(); + for pair in object_entries(&Object::from(js_val)) { + let (key, val) = pair.expect("Iteration over object failed"); let py_val = js_to_py(vm, val); vm.ctx .set_item(&dict, &String::from(js_sys::JsString::from(key)), py_val); From 101ee77dcc7e4219fcc932637d6371102ba9b134 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 17 Feb 2019 18:25:14 -0600 Subject: [PATCH 25/33] Improve error messages --- wasm/lib/src/convert.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index b1d3d08b37c..78dc3495f81 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -113,7 +113,7 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { } else { let dict = vm.new_dict(); for pair in object_entries(&Object::from(js_val)) { - let (key, val) = pair.expect("Iteration over object failed"); + let (key, val) = pair.expect("iteration over object to not fail"); let py_val = js_to_py(vm, val); vm.ctx .set_item(&dict, &String::from(js_sys::JsString::from(key)), py_val); @@ -128,7 +128,7 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { let this = Object::new(); for (k, v) in args.kwargs { Reflect::set(&this, &k.into(), &py_to_js(vm, v)) - .expect("Couldn't set this property"); + .expect("property to be settable"); } let js_args = Array::new(); for v in args.args { @@ -158,7 +158,7 @@ pub fn js_to_py(vm: &mut VirtualMachine, js_val: JsValue) -> PyObjectRef { "json", &Some("loads".into()), ) - .expect("Couldn't get json.loads function"); + .expect("json.loads function to be available"); let json = match js_sys::JSON::stringify(&js_val) { Ok(json) => String::from(json), From 0d3d09072e2c16c274cb5b730197d31953919016 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 18 Feb 2019 00:37:00 -0600 Subject: [PATCH 26/33] Convert pyEval to use a WASM VM, allowing closures and stuff Like variables carried across executions --- wasm/lib/src/lib.rs | 89 ++++------------------------------------ wasm/lib/src/vm_class.rs | 63 ++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 90 deletions(-) diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index a6dfde7eb93..19ba0947034 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -11,14 +11,12 @@ extern crate wasm_bindgen_futures; extern crate web_sys; use js_sys::{Object, Reflect, TypeError}; -use rustpython_vm::compile; -use rustpython_vm::pyobject::{PyFuncArgs, PyObjectRef, PyResult}; -use rustpython_vm::VirtualMachine; -use std::error::Error; use wasm_bindgen::prelude::*; pub use vm_class::*; +const PY_EVAL_VM_ID: &str = "__py_eval_vm"; + extern crate console_error_panic_hook; #[wasm_bindgen(start)] @@ -30,32 +28,6 @@ pub fn setup_console_error() { #[wasm_bindgen(typescript_custom_section)] const TS_CMT_START: &'static str = "/*"; -fn base_scope(vm: &mut VirtualMachine) -> PyObjectRef { - let builtins = vm.get_builtin_scope(); - vm.context().new_scope(Some(builtins)) -} - -fn eval(vm: &mut VirtualMachine, source: &str, vars: PyObjectRef) -> PyResult { - // HACK: if the code doesn't end with newline it crashes. - let mut source = source.to_string(); - if !source.ends_with('\n') { - source.push('\n'); - } - - let code_obj = compile::compile( - &source, - &compile::Mode::Exec, - "".to_string(), - vm.ctx.code_type(), - ) - .map_err(|err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.description().to_string()) - })?; - - vm.run_code_obj(code_obj, vars) -} - #[wasm_bindgen(js_name = pyEval)] /// Evaluate Python code /// @@ -72,7 +44,7 @@ fn eval(vm: &mut VirtualMachine, source: &str, vars: PyObjectRef) -> PyResult { /// receive the Python kwargs as the `this` argument. /// - `stdout?`: `(out: string) => void`: A function to replace the native print /// function, by default `console.log`. -pub fn eval_py(source: &str, options: Option) -> Result { +pub fn eval_py(source: String, options: Option) -> Result { let options = options.unwrap_or_else(Object::new); let js_vars = { let prop = Reflect::get(&options, &"vars".into())?; @@ -92,61 +64,16 @@ pub fn eval_py(source: &str, options: Option) -> Result PyResult> = match stdout { - Some(val) => { - if let Some(selector) = val.as_string() { - Box::new( - move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { - wasm_builtins::builtin_print_html(vm, args, &selector) - }, - ) - } else if val.is_function() { - let func = js_sys::Function::from(val); - Box::new( - move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { - func.call1( - &JsValue::UNDEFINED, - &wasm_builtins::format_print_args(vm, args)?.into(), - ) - .map_err(|err| convert::js_to_py(vm, err))?; - Ok(vm.get_none()) - }, - ) - } else { - return Err(TypeError::new("stdout must be a function or a css selector").into()); - } - } - None => Box::new(wasm_builtins::builtin_print_console), - }; - vm.ctx.set_attr( - &vm.builtins, - "print", - vm.ctx.new_rustfunc_from_box(print_fn), - ); + let vm = VMStore::init(PY_EVAL_VM_ID.into(), Some(true)); - let vars = base_scope(&mut vm); + vm.set_stdout(stdout.unwrap_or(JsValue::UNDEFINED))?; - let injections = vm.new_dict(); - - if let Some(js_vars) = js_vars.clone() { - for pair in convert::object_entries(&js_vars) { - let (key, val) = pair?; - let py_val = convert::js_to_py(&mut vm, val); - vm.ctx.set_item( - &injections, - &String::from(js_sys::JsString::from(key)), - py_val, - ); - } + if let Some(js_vars) = js_vars { + vm.add_to_scope("js_vars".into(), js_vars.into())?; } - vm.ctx.set_attr(&vars, "js_vars", injections); - - let result = eval(&mut vm, source, vars); - convert::pyresult_to_jsresult(&mut vm, result) + vm.exec(source) } #[wasm_bindgen(typescript_custom_section)] diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index f2bde30d0ff..810340b8c23 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -2,14 +2,14 @@ use convert; use js_sys::{SyntaxError, TypeError}; use rustpython_vm::{ compile, - pyobject::{PyObjectRef, PyRef}, + pyobject::{PyFuncArgs, PyObjectRef, PyRef, PyResult}, VirtualMachine, }; use std::cell::RefCell; use std::collections::HashMap; use std::rc::{Rc, Weak}; use wasm_bindgen::prelude::*; -use wasm_builtins::setup_wasm_builtins; +use wasm_builtins::{self, setup_wasm_builtins}; pub(crate) struct StoredVirtualMachine { pub vm: VirtualMachine, @@ -52,17 +52,24 @@ impl VMStore { WASMVirtualMachine { id } } - pub fn get(id: String) -> JsValue { + pub(crate) fn _get(id: String) -> Option { STORED_VMS.with(|cell| { let vms = cell.borrow(); if vms.contains_key(&id) { - WASMVirtualMachine { id }.into() + Some(WASMVirtualMachine { id }) } else { - JsValue::UNDEFINED + None } }) } + pub fn get(id: String) -> JsValue { + match Self::_get(id) { + Some(wasm_vm) => wasm_vm.into(), + None => JsValue::UNDEFINED, + } + } + pub fn destroy(id: String) { STORED_VMS.with(|cell| { use std::collections::hash_map::Entry; @@ -230,6 +237,46 @@ impl WASMVirtualMachine { ) } + pub fn set_stdout(&self, stdout: JsValue) -> Result<(), JsValue> { + self.with( + move |StoredVirtualMachine { + ref mut vm, + ref mut scope, + }| { + let print_fn: Box PyResult> = + if let Some(selector) = stdout.as_string() { + Box::new( + move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { + wasm_builtins::builtin_print_html(vm, args, &selector) + }, + ) + } else if stdout.is_function() { + let func = js_sys::Function::from(stdout); + Box::new( + move |vm: &mut VirtualMachine, args: PyFuncArgs| -> PyResult { + func.call1( + &JsValue::UNDEFINED, + &wasm_builtins::format_print_args(vm, args)?.into(), + ) + .map_err(|err| convert::js_to_py(vm, err))?; + Ok(vm.get_none()) + }, + ) + } else if stdout.is_undefined() || stdout.is_null() { + Box::new(wasm_builtins::builtin_print_console) + } else { + return Err(TypeError::new( + "stdout must be null, a function or a css selector", + ) + .into()); + }; + vm.ctx + .set_attr(scope, "print", vm.ctx.new_rustfunc_from_box(print_fn)); + Ok(()) + }, + )? + } + fn run(&self, mut source: String, mode: compile::Mode) -> Result { self.assert_valid()?; self.with_unchecked( @@ -243,10 +290,8 @@ impl WASMVirtualMachine { .map_err(|err| { SyntaxError::new(&format!("Error parsing Python code: {}", err)) })?; - let result = vm - .run_code_obj(code, scope.clone()) - .map_err(|err| convert::py_str_err(vm, &err))?; - Ok(convert::py_to_js(vm, result)) + let result = vm.run_code_obj(code, scope.clone()); + convert::pyresult_to_jsresult(vm, result) }, ) } From 634571fd2ecac71a7e9fc32807d9acd13b8ab183 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 18 Feb 2019 12:16:43 -0600 Subject: [PATCH 27/33] Remove WASM fetch builtin --- wasm/lib/src/lib.rs | 10 ++- wasm/lib/src/vm_class.rs | 4 +- wasm/lib/src/wasm_builtins.rs | 116 +--------------------------------- 3 files changed, 8 insertions(+), 122 deletions(-) diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 19ba0947034..240e7861c39 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -1,13 +1,11 @@ -mod convert; -mod vm_class; -mod wasm_builtins; +pub mod convert; +pub mod vm_class; +pub mod wasm_builtins; +extern crate futures; extern crate js_sys; -#[macro_use] extern crate rustpython_vm; -extern crate futures; extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; extern crate web_sys; use js_sys::{Object, Reflect, TypeError}; diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 810340b8c23..8a6593f671e 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -93,7 +93,7 @@ impl VMStore { } #[derive(Clone)] -pub(crate) struct AccessibleVM { +pub struct AccessibleVM { weak: Weak>, id: String, } @@ -148,7 +148,7 @@ impl From<&WASMVirtualMachine> for AccessibleVM { } } -pub(crate) struct AccessibleVMPtr<'a> { +pub struct AccessibleVMPtr<'a> { id: String, top_level: bool, inner: &'a mut VirtualMachine, diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index e1fee0f8d47..211eedb4154 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -8,17 +8,14 @@ extern crate futures; extern crate js_sys; extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; extern crate web_sys; -use crate::{convert, vm_class::AccessibleVM}; -use futures::{future, Future}; -use js_sys::{Array, Promise}; +use crate::convert; +use js_sys::Array; use rustpython_vm::obj::{objstr, objtype}; use rustpython_vm::pyobject::{IdProtocol, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use rustpython_vm::VirtualMachine; use wasm_bindgen::{prelude::*, JsCast}; -use wasm_bindgen_futures::{future_to_promise, JsFuture}; use web_sys::{console, HtmlTextAreaElement}; fn window() -> web_sys::Window { @@ -107,116 +104,7 @@ pub fn builtin_print_console(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyRes Ok(vm.get_none()) } -enum FetchResponseFormat { - Json, - Text, - ArrayBuffer, -} - -impl FetchResponseFormat { - fn from_str(vm: &mut VirtualMachine, s: &str) -> Result { - match s { - "json" => Ok(FetchResponseFormat::Json), - "text" => Ok(FetchResponseFormat::Text), - "array_buffer" => Ok(FetchResponseFormat::ArrayBuffer), - _ => Err(vm.new_type_error("Unkown fetch response_format".into())), - } - } - fn get_response(&self, response: &web_sys::Response) -> Result { - match self { - FetchResponseFormat::Json => response.json(), - FetchResponseFormat::Text => response.text(), - FetchResponseFormat::ArrayBuffer => response.array_buffer(), - } - } -} - -fn builtin_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (url, Some(vm.ctx.str_type())), - (handler, Some(vm.ctx.function_type())) - ], - // TODO: use named parameters for these - optional = [ - (reject_handler, Some(vm.ctx.function_type())), - (response_format, Some(vm.ctx.str_type())), - (method, Some(vm.ctx.str_type())), - (headers, Some(vm.ctx.dict_type())) - ] - ); - - let response_format = match response_format { - Some(s) => FetchResponseFormat::from_str(vm, &objstr::get_value(s))?, - None => FetchResponseFormat::Text, - }; - - let mut opts = web_sys::RequestInit::new(); - - match method { - Some(s) => opts.method(&objstr::get_value(s)), - None => opts.method("GET"), - }; - - let request = web_sys::Request::new_with_str_and_init(&objstr::get_value(url), &opts) - .map_err(|err| convert::js_py_typeerror(vm, err))?; - - if let Some(headers) = headers { - use rustpython_vm::obj::objdict; - let h = request.headers(); - for (key, value) in objdict::get_key_value_pairs(headers) { - let key = objstr::get_value(&vm.to_str(&key)?); - let value = objstr::get_value(&vm.to_str(&value)?); - h.set(&key, &value) - .map_err(|err| convert::js_py_typeerror(vm, err))?; - } - } - - let window = window(); - let request_prom = window.fetch_with_request(&request); - - let handler = handler.clone(); - let reject_handler = reject_handler.cloned(); - - let acc_vm = AccessibleVM::from_vm(vm); - - let future = JsFuture::from(request_prom) - .and_then(move |val| { - let response = val - .dyn_into::() - .expect("val to be of type Response"); - response_format.get_response(&response) - }) - .and_then(|prom| JsFuture::from(prom)) - .then(move |val| { - let vm = &mut acc_vm - .upgrade() - .expect("that the VM *not* be destroyed while promise is being resolved"); - match val { - Ok(val) => { - let val = convert::js_to_py(vm, val); - let args = PyFuncArgs::new(vec![val], vec![]); - let _ = vm.invoke(handler, args); - } - Err(val) => { - if let Some(reject_handler) = reject_handler { - let val = convert::js_to_py(vm, val); - let args = PyFuncArgs::new(vec![val], vec![]); - let _ = vm.invoke(reject_handler, args); - } - } - } - future::ok(JsValue::UNDEFINED) - }); - future_to_promise(future); - - Ok(vm.get_none()) -} - pub fn setup_wasm_builtins(vm: &mut VirtualMachine, scope: &PyObjectRef) { let ctx = vm.context(); ctx.set_attr(scope, "print", ctx.new_rustfunc(builtin_print_console)); - ctx.set_attr(scope, "fetch", ctx.new_rustfunc(builtin_fetch)); } From 8e5073e524f17e7c6a98fe2e304a48d4bfc4fac3 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 19 Feb 2019 17:58:57 -0600 Subject: [PATCH 28/33] Convert `bytes` and `bytearray`s to `Uint8Array`s --- wasm/lib/src/convert.rs | 45 ++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 78dc3495f81..944e446fd8a 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,4 +1,5 @@ use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; +use rustpython_vm::obj::{objbytes, objtype}; use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; use vm_class::{AccessibleVM, WASMVirtualMachine}; @@ -16,10 +17,10 @@ pub fn js_py_typeerror(vm: &mut VirtualMachine, js_err: JsValue) -> PyObjectRef pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { if let Some(ref wasm_id) = vm.wasm_id { - let wasm_vm = WASMVirtualMachine { - id: wasm_id.clone(), - }; - if rustpython_vm::obj::objtype::isinstance(&py_obj, &vm.ctx.function_type()) { + if objtype::isinstance(&py_obj, &vm.ctx.function_type()) { + let wasm_vm = WASMVirtualMachine { + id: wasm_id.clone(), + }; let closure = move |args: Option, kwargs: Option| -> Result { let py_obj = py_obj.clone(); @@ -55,19 +56,31 @@ pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { return func; } } - let dumps = rustpython_vm::import::import( - vm, - std::path::PathBuf::default(), - "json", - &Some("dumps".into()), - ) - .expect("Couldn't get json.dumps function"); - match vm.invoke(dumps, pyobject::PyFuncArgs::new(vec![py_obj], vec![])) { - Ok(value) => { - let json = vm.to_pystr(&value).unwrap(); - js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED) + if objtype::isinstance(&py_obj, &vm.ctx.bytes_type()) + || objtype::isinstance(&py_obj, &vm.ctx.bytearray_type()) + { + let bytes = objbytes::get_value(&py_obj); + let arr = Uint8Array::new_with_length(bytes.len() as u32); + for (i, byte) in bytes.iter().enumerate() { + Reflect::set(&arr, &(i as u32).into(), &(*byte).into()) + .expect("setting Uint8Array value failed"); + } + arr.into() + } else { + let dumps = rustpython_vm::import::import( + vm, + std::path::PathBuf::default(), + "json", + &Some("dumps".into()), + ) + .expect("Couldn't get json.dumps function"); + match vm.invoke(dumps, pyobject::PyFuncArgs::new(vec![py_obj], vec![])) { + Ok(value) => { + let json = vm.to_pystr(&value).unwrap(); + js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED) + } + Err(_) => JsValue::UNDEFINED, } - Err(_) => JsValue::UNDEFINED, } } From 8c222af65bfc11ddc0cffcd3cfc99fd1f37e21b3 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 20 Feb 2019 22:33:52 -0600 Subject: [PATCH 29/33] Include details about thread_local! for WASM --- wasm/lib/src/vm_class.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 8a6593f671e..3d63beaa610 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -29,7 +29,9 @@ impl StoredVirtualMachine { } } -// It's fine that it's thread local, since WASM doesn't even have threads yet +// It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local! probably +// gets compiled down to a normal-ish static varible, like Atomic* types: +// https://rustwasm.github.io/2018/10/24/multithreading-rust-and-wasm.html#atomic-instructions thread_local! { static STORED_VMS: PyRef>> = Rc::default(); static ACTIVE_VMS: PyRef> = Rc::default(); From 9e176902b95dd33765d0e2baf35fac69da310f09 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 21 Feb 2019 23:25:05 -0600 Subject: [PATCH 30/33] Use crate:: imports --- wasm/lib/src/convert.rs | 2 +- wasm/lib/src/lib.rs | 2 +- wasm/lib/src/vm_class.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 944e446fd8a..be3a8ad80f5 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,8 +1,8 @@ +use crate::vm_class::{AccessibleVM, WASMVirtualMachine}; use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; use rustpython_vm::obj::{objbytes, objtype}; use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; -use vm_class::{AccessibleVM, WASMVirtualMachine}; use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; pub fn py_str_err(vm: &mut VirtualMachine, py_err: &PyObjectRef) -> String { diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 240e7861c39..2c69866e90a 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -11,7 +11,7 @@ extern crate web_sys; use js_sys::{Object, Reflect, TypeError}; use wasm_bindgen::prelude::*; -pub use vm_class::*; +pub use crate::vm_class::*; const PY_EVAL_VM_ID: &str = "__py_eval_vm"; diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 8a6593f671e..6139dd3c8f6 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -1,4 +1,5 @@ -use convert; +use crate::convert; +use crate::wasm_builtins::{self, setup_wasm_builtins}; use js_sys::{SyntaxError, TypeError}; use rustpython_vm::{ compile, @@ -9,7 +10,6 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::{Rc, Weak}; use wasm_bindgen::prelude::*; -use wasm_builtins::{self, setup_wasm_builtins}; pub(crate) struct StoredVirtualMachine { pub vm: VirtualMachine, From 7fa0a0cee3a658bb9fd824e0894fefb4458ccdef Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 22 Feb 2019 22:59:15 -0600 Subject: [PATCH 31/33] Fix open in new make_module --- vm/src/builtins.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index c1b1364f78d..a14925add0e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -749,7 +749,6 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "min" => ctx.new_rustfunc(builtin_min), "object" => ctx.object(), "oct" => ctx.new_rustfunc(builtin_oct), - "open" => ctx.new_rustfunc(io_open), "ord" => ctx.new_rustfunc(builtin_ord), "next" => ctx.new_rustfunc(builtin_next), "pow" => ctx.new_rustfunc(builtin_pow), From 09e2a7a493671a4078b8aab904e0eb64bb4d40b6 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Feb 2019 00:02:12 -0600 Subject: [PATCH 32/33] Add js_name for .set_stdout() --- wasm/lib/src/vm_class.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index f17f1bf72b0..5af38ef39b2 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -239,6 +239,7 @@ impl WASMVirtualMachine { ) } + #[wasm_bindgen(js_name = setStdout)] pub fn set_stdout(&self, stdout: JsValue) -> Result<(), JsValue> { self.with( move |StoredVirtualMachine { From e0f222cf4f9d3fe85022ecbc5a25cf47b23a9b30 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Feb 2019 00:20:15 -0600 Subject: [PATCH 33/33] Don't hold on to a PyObjectRef from a python -> js closure --- wasm/lib/src/convert.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index be3a8ad80f5..df63c959e66 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -21,10 +21,16 @@ pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { let wasm_vm = WASMVirtualMachine { id: wasm_id.clone(), }; + let mut py_obj = Some(py_obj); let closure = move |args: Option, kwargs: Option| -> Result { - let py_obj = py_obj.clone(); - wasm_vm.assert_valid()?; + let py_obj = match wasm_vm.assert_valid() { + Ok(_) => py_obj.clone().expect("py_obj to be valid if VM is valid"), + Err(err) => { + py_obj = None; + return Err(err); + } + }; let acc_vm = AccessibleVM::from(wasm_vm.clone()); let vm = &mut acc_vm .upgrade() @@ -47,7 +53,7 @@ pub fn py_to_js(vm: &mut VirtualMachine, py_obj: PyObjectRef) -> JsValue { pyresult_to_jsresult(vm, result) }; let closure = Closure::wrap(Box::new(closure) - as Box, Option) -> Result>); + as Box, Option) -> Result>); let func = closure.as_ref().clone(); // TODO: Come up with a way of managing closure handles