#![allow(clippy::empty_docs)]
use crate::js_module;
use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine};
use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, SyntaxError, Uint8Array};
use rustpython_parser::ParseErrorType;
use rustpython_vm::{
builtins::PyBaseExceptionRef,
compiler::{CompileError, CompileErrorType},
exceptions,
function::{ArgBytesLike, FuncArgs},
py_serde, AsObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine,
};
use wasm_bindgen::{closure::Closure, prelude::*, JsCast};
#[wasm_bindgen(inline_js = r"
export class PyError extends Error {
constructor(info) {
const msg = info.args[0];
if (typeof msg === 'string') super(msg);
else super();
this.info = info;
}
get name() { return this.info.exc_type; }
get traceback() { return this.info.traceback; }
toString() { return this.info.rendered; }
}
")]
extern "C" {
pub type PyError;
#[wasm_bindgen(constructor)]
fn new(info: JsValue) -> PyError;
}
pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyBaseExceptionRef) -> JsValue {
let jserr = vm.try_class("_js", "JSError").ok();
let js_arg = if jserr.map_or(false, |jserr| py_err.fast_isinstance(&jserr)) {
py_err.get_arg(0)
} else {
None
};
let js_arg = js_arg
.as_ref()
.and_then(|x| x.payload::<js_module::PyJsValue>());
match js_arg {
Some(val) => val.value.clone(),
None => {
let res =
serde_wasm_bindgen::to_value(&exceptions::SerializeException::new(vm, py_err));
match res {
Ok(err_info) => PyError::new(err_info).into(),
Err(e) => e.into(),
}
}
}
}
pub fn js_py_typeerror(vm: &VirtualMachine, js_err: JsValue) -> PyBaseExceptionRef {
let msg = js_err.unchecked_into::<js_sys::Error>().to_string();
vm.new_type_error(msg.into())
}
pub fn js_err_to_py_err(vm: &VirtualMachine, js_err: &JsValue) -> PyBaseExceptionRef {
match js_err.dyn_ref::<js_sys::Error>() {
Some(err) => {
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,
}
.to_owned();
vm.new_exception_msg(exc_type, err.message().into())
}
None => vm.new_exception_msg(
vm.ctx.exceptions.exception_type.to_owned(),
format!("{js_err:?}"),
),
}
}
pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue {
if let Some(ref wasm_id) = vm.wasm_id {
if py_obj.fast_isinstance(vm.ctx.types.function_type) {
let wasm_vm = WASMVirtualMachine {
id: wasm_id.clone(),
};
let weak_py_obj = wasm_vm.push_held_rc(py_obj).unwrap().unwrap();
let closure = move |args: Option<Box<[JsValue]>>,
kwargs: Option<Object>|
-> Result<JsValue, JsValue> {
let py_obj = match wasm_vm.assert_valid() {
Ok(_) => weak_py_obj
.upgrade()
.expect("weak_py_obj to be valid if VM is valid"),
Err(err) => {
return Err(err);
}
};
stored_vm_from_wasm(&wasm_vm).interp.enter(move |vm| {
let args = match args {
Some(args) => Vec::from(args)
.into_iter()
.map(|arg| js_to_py(vm, arg))
.collect::<Vec<_>>(),
None => Vec::new(),
};
let mut py_func_args = FuncArgs::from(args);
if let Some(ref kwargs) = kwargs {
for pair in object_entries(kwargs) {
let (key, val) = pair?;
py_func_args
.kwargs
.insert(js_sys::JsString::from(key).into(), js_to_py(vm, val));
}
}
let result = py_obj.call(py_func_args, vm);
pyresult_to_jsresult(vm, result)
})
};
let closure = Closure::wrap(Box::new(closure)
as Box<
dyn FnMut(Option<Box<[JsValue]>>, Option<Object>) -> Result<JsValue, JsValue>,
>);
let func = closure.as_ref().clone();
closure.forget();
return func;
}
}
if vm.try_class("_js", "Promise").is_ok() {
if let Some(py_prom) = py_obj.payload::<js_module::PyPromise>() {
return py_prom.as_js(vm).into();
}
}
if let Ok(bytes) = ArgBytesLike::try_from_borrowed_object(vm, &py_obj) {
bytes.with_ref(|bytes| unsafe {
let view = Uint8Array::view(bytes);
view.slice(0, bytes.len() as u32).into()
})
} else {
py_serde::serialize(vm, &py_obj, &serde_wasm_bindgen::Serializer::new())
.unwrap_or(JsValue::UNDEFINED)
}
}
pub fn object_entries(obj: &Object) -> impl Iterator<Item = Result<(JsValue, JsValue), JsValue>> {
Object::entries(obj).values().into_iter().map(|pair| {
pair.map(|pair| {
let key = Reflect::get(&pair, &"0".into()).unwrap();
let val = Reflect::get(&pair, &"1".into()).unwrap();
(key, val)
})
})
}
pub fn pyresult_to_jsresult(vm: &VirtualMachine, result: PyResult) -> Result<JsValue, JsValue> {
result
.map(|value| py_to_js(vm, value))
.map_err(|err| py_err_to_js_err(vm, &err))
}
pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
if js_val.is_object() {
if let Some(promise) = js_val.dyn_ref::<Promise>() {
if vm.try_class("browser", "Promise").is_ok() {
return js_module::PyPromise::new(promise.clone())
.into_ref(&vm.ctx)
.into();
}
}
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).into()
} else if ArrayBuffer::is_view(&js_val) || js_val.is_instance_of::<ArrayBuffer>() {
let u8_array = js_sys::Uint8Array::new(
&js_val
.dyn_ref::<ArrayBuffer>()
.cloned()
.unwrap_or_else(|| js_val.unchecked_ref::<Uint8Array>().buffer()),
);
let mut vec = vec![0; u8_array.length() as usize];
u8_array.copy_to(&mut vec);
vm.ctx.new_bytes(vec).into()
} else {
let dict = vm.ctx.new_dict();
for pair in object_entries(&Object::from(js_val)) {
let (key, val) = pair.expect("iteration over object to not fail");
let py_val = js_to_py(vm, val);
dict.set_item(
String::from(js_sys::JsString::from(key)).as_str(),
py_val,
vm,
)
.unwrap();
}
dict.into()
}
} else if js_val.is_function() {
let func = js_sys::Function::from(js_val);
vm.new_function(
vm.ctx.intern_str(String::from(func.name())).as_str(),
move |args: FuncArgs, vm: &VirtualMachine| -> PyResult {
let this = Object::new();
for (k, v) in args.kwargs {
Reflect::set(&this, &k.into(), &py_to_js(vm, v))
.expect("property to be settable");
}
let js_args = args
.args
.into_iter()
.map(|v| py_to_js(vm, v))
.collect::<Array>();
func.apply(&this, &js_args)
.map(|val| js_to_py(vm, val))
.map_err(|err| js_err_to_py_err(vm, &err))
},
)
.into()
} else if let Some(err) = js_val.dyn_ref::<js_sys::Error>() {
js_err_to_py_err(vm, err).into()
} else if js_val.is_undefined() {
vm.ctx.none()
} else {
py_serde::deserialize(vm, serde_wasm_bindgen::Deserializer::from(js_val))
.unwrap_or_else(|_| vm.ctx.none())
}
}
pub fn syntax_err(err: CompileError) -> SyntaxError {
let js_err = SyntaxError::new(&format!("Error parsing Python code: {err}"));
let _ = Reflect::set(
&js_err,
&"row".into(),
&(err.location.unwrap().row.get()).into(),
);
let _ = Reflect::set(
&js_err,
&"col".into(),
&(err.location.unwrap().column.get()).into(),
);
let can_continue = matches!(&err.error, CompileErrorType::Parse(ParseErrorType::Eof));
let _ = Reflect::set(&js_err, &"canContinue".into(), &can_continue.into());
js_err
}
pub trait PyResultExt<T> {
fn into_js(self, vm: &VirtualMachine) -> Result<T, JsValue>;
}
impl<T> PyResultExt<T> for PyResult<T> {
fn into_js(self, vm: &VirtualMachine) -> Result<T, JsValue> {
self.map_err(|err| py_err_to_js_err(vm, &err))
}
}