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] 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 874f3e2a2..f785e294d 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 033e7fbc7..0938e5fb6 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 45b88e45d..894f9e075 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 f7e2d1850..69880155b 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 52d7ef09a..d8510db4f 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 7adad4741..4e35cc72f 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 263505342..13b596308 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)); +}