mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Store the WASM id in the VirtualMachine, add a (broken) fetch builtin
This commit is contained in:
8 files changed
+144
-72
No files matched your search
+1
-1
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -39,6 +39,7 @@ pub struct VirtualMachine {
|
||||
pub stdlib_inits: HashMap<String, stdlib::StdlibInitFunc>,
|
||||
pub ctx: PyContext,
|
||||
pub current_frame: Option<PyObjectRef>,
|
||||
pub wasm_id: Option<String>,
|
||||
}
|
||||
|
||||
impl VirtualMachine {
|
||||
@@ -61,6 +62,7 @@ impl VirtualMachine {
|
||||
stdlib_inits,
|
||||
ctx,
|
||||
current_frame: None,
|
||||
wasm_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -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"
|
||||
]
|
||||
+19
-39
@@ -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<WASMVirtualMachine>,
|
||||
) -> 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<Array>, kwargs: Option<Object>| -> Result<JsValue, JsValue> {
|
||||
@@ -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<dyn Fn(Option<Array>, Option<Object>) -> Result<JsValue, JsValue>>);
|
||||
@@ -80,36 +76,20 @@ pub fn object_entries(obj: &Object) -> impl Iterator<Item = Result<(JsValue, JsV
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pyresult_to_jsresult(
|
||||
vm: &mut VirtualMachine,
|
||||
result: PyResult,
|
||||
wasm_vm: Option<WASMVirtualMachine>,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
pub fn pyresult_to_jsresult(vm: &mut VirtualMachine, result: PyResult) -> Result<JsValue, JsValue> {
|
||||
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<WASMVirtualMachine>,
|
||||
) -> 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::<js_sys::Error>() {
|
||||
|
||||
+4
-3
@@ -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<Object>) -> Result<JsValue, JsValue
|
||||
&JsValue::UNDEFINED,
|
||||
&wasm_builtins::format_print_args(vm, args)?.into(),
|
||||
)
|
||||
.map_err(|err| convert::js_to_py(vm, err, None))?;
|
||||
.map_err(|err| convert::js_to_py(vm, err))?;
|
||||
Ok(vm.get_none())
|
||||
},
|
||||
)
|
||||
@@ -131,7 +132,7 @@ pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue
|
||||
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, None);
|
||||
let py_val = convert::js_to_py(&mut vm, val);
|
||||
vm.ctx.set_item(
|
||||
&injections,
|
||||
&String::from(js_sys::JsString::from(key)),
|
||||
@@ -143,7 +144,7 @@ pub fn eval_py(source: &str, options: Option<Object>) -> Result<JsValue, JsValue
|
||||
vm.ctx.set_attr(&vars, "js_vars", injections);
|
||||
|
||||
let result = eval(&mut vm, source, vars);
|
||||
convert::pyresult_to_jsresult(&mut vm, result, None)
|
||||
convert::pyresult_to_jsresult(&mut vm, result)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
|
||||
+27
-23
@@ -9,7 +9,7 @@ use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::{Rc, Weak};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_builtins;
|
||||
use wasm_builtins::setup_wasm_builtins;
|
||||
|
||||
pub(crate) struct StoredVirtualMachine {
|
||||
pub vm: VirtualMachine,
|
||||
@@ -17,23 +17,18 @@ pub(crate) struct StoredVirtualMachine {
|
||||
}
|
||||
|
||||
impl StoredVirtualMachine {
|
||||
fn new() -> 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<HashMap<String, PyRef<StoredVirtualMachine>>> = 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<bool>) -> 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<RefCell<StoredVirtualMachine>>,
|
||||
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<F, R>(&self, f: F) -> Result<R, JsValue>
|
||||
@@ -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))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<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| 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<Self, PyObjectRef> {
|
||||
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::<web_sys::Response>()
|
||||
.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));
|
||||
}
|
||||
Reference in new issue
Block a user