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] 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 ddc013a58..80373d4c1 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 6c78e0640..bc2ee9d05 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 000000000..bdb2fca64 --- /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 4bc0891b2..158345798 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 ca3ab013a..e6605ea79 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()) }