mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-09 22:49:57 +09:00
Add a bare-bones VirtualMachine class to the WASM library
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
100
wasm/lib/src/convert.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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
68
wasm/lib/src/vm_class.rs
Normal 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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user