Add a bare-bones VirtualMachine class to the WASM library

This commit is contained in:
coolreader18
2019-01-30 12:18:59 -06:00
parent d627230434
commit 4020ee7d41
6 changed files with 190 additions and 114 deletions

12
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

100
wasm/lib/src/convert.rs Normal file
View File

@@ -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::<js_sys::Error>() {
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()
}
}

View File

@@ -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::<js_sys::Error>() {
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<Object>) -> Result<JsValue, JsValue
&JsValue::UNDEFINED,
&wasm_builtins::format_print_args(vm, args)?.into(),
)
.map_err(|err| js_to_py(vm, err))?;
.map_err(|err| convert::js_to_py(vm, err))?;
Ok(vm.get_none())
},
)
@@ -208,7 +116,7 @@ pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue
let pair = pair?;
let key = Reflect::get(&pair, &"0".into()).unwrap();
let val = Reflect::get(&pair, &"1".into()).unwrap();
let py_val = js_to_py(&mut vm, val);
let py_val = convert::js_to_py(&mut vm, val);
vm.ctx.set_item(
&injections,
&String::from(js_sys::JsString::from(key)),
@@ -220,8 +128,8 @@ pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue
vm.ctx.set_item(&mut vars, "js_vars", injections);
eval(&mut vm, source, vars)
.map(|value| py_to_js(&mut vm, value))
.map_err(|err| py_str_err(&mut vm, &err).into())
.map(|value| convert::py_to_js(&mut vm, value))
.map_err(|err| convert::py_str_err(&mut vm, &err).into())
}
#[wasm_bindgen(typescript_custom_section)]

68
wasm/lib/src/vm_class.rs Normal file
View File

@@ -0,0 +1,68 @@
use js_sys::TypeError;
use rustpython_vm::VirtualMachine;
use std::cell::RefCell;
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
// It's fine that it's thread local, since WASM doesn't even have threads yet
thread_local! {
static STORED_VMS: RefCell<HashMap<String, VirtualMachine>> = 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
}

View File

@@ -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<St
pub fn builtin_print_html(vm: &mut VirtualMachine, args: PyFuncArgs, selector: &str) -> 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())
}