Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions vm/src/pyobject.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::any::Any;
use std::any::TypeId;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::hash_map::{Entry, HashMap};
use std::fmt;
use std::marker::PhantomData;
use std::mem;
Expand Down Expand Up @@ -162,6 +163,7 @@ pub struct PyContext {
pub weakproxy_type: PyClassRef,
pub object: PyClassRef,
pub exceptions: exceptions::ExceptionZoo,
rust_classes: RefCell<HashMap<TypeId, PyClassRef>>,
}

pub fn create_type(name: &str, type_type: &PyClassRef, base: &PyClassRef) -> PyClassRef {
Expand Down Expand Up @@ -367,6 +369,7 @@ impl PyContext {
weakproxy_type,
type_type,
exceptions,
rust_classes: RefCell::default(),
};
objtype::init(&context);
objlist::init(&context);
Expand Down Expand Up @@ -407,6 +410,36 @@ impl PyContext {
context
}

pub fn _add_class(&self, typeid: TypeId, class: PyClassRef) {
let classes = &mut self.rust_classes.borrow_mut();
match classes.entry(typeid) {
Entry::Occupied(o) => panic!(
"Attempted to add rust type twice to the same PyContext, previously held class \
was {:?}",
o.get()
),
Entry::Vacant(v) => {
v.insert(class);
}
}
}

#[inline]
pub fn add_class<T: PyClassImpl + 'static>(&self) -> PyClassRef {
let class = T::make_class(self);
self._add_class(TypeId::of::<T>(), class.clone());
class
}

pub fn _get_class(&self, typeid: &TypeId) -> Option<PyClassRef> {
self.rust_classes.borrow_mut().get(typeid).cloned()
}

#[inline]
pub fn get_class<T: 'static>(&self) -> Option<PyClassRef> {
self._get_class(&TypeId::of::<T>())
}

pub fn bytearray_type(&self) -> PyClassRef {
self.bytearray_type.clone()
}
Expand Down Expand Up @@ -1202,7 +1235,13 @@ impl PyObject<dyn PyObjectPayload> {
pub trait PyValue: fmt::Debug + Sized + 'static {
const HAVE_DICT: bool = false;

fn class(vm: &VirtualMachine) -> PyClassRef;
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.get_class::<Self>().expect(
"Default .class implementation cannot find your class in PyContext.rust_classes. \
Is there some special way to access your class, or did you forget to call \
`ctx.add_class::<T>()`?",
)
}

fn into_ref(self, vm: &VirtualMachine) -> PyRef<Self> {
PyRef {
Expand Down
59 changes: 34 additions & 25 deletions wasm/lib/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use wasm_bindgen::{closure::Closure, prelude::*, JsCast};

use rustpython_vm::function::PyFuncArgs;
use rustpython_vm::obj::{objbytes, objint, objsequence, objtype};
use rustpython_vm::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue};
use rustpython_vm::pyobject::{PyObjectRef, PyResult, PyValue};
use rustpython_vm::VirtualMachine;

use crate::browser_module;
use crate::objjsobject::{PyJsFunction, PyJsObject};
use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine};

pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue {
Expand Down Expand Up @@ -129,6 +130,10 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue {
let view = Uint8Array::view(&bytes);
view.slice(0, bytes.len() as u32).into()
}
} else if let Some(jsval) = py_obj.payload::<PyJsObject>() {
jsval.object().clone().into()
} else if let Some(jsfunc) = py_obj.payload::<PyJsFunction>() {
jsfunc.to_function().into()
} else {
match vm.serialize(&py_obj) {
Ok(json) => js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED),
Expand All @@ -154,6 +159,14 @@ pub fn pyresult_to_jsresult(vm: &VirtualMachine, result: PyResult) -> Result<JsV
}

pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
js_to_py_with_this(vm, js_val, None)
}

pub fn js_to_py_with_this(
vm: &VirtualMachine,
js_val: JsValue,
this: Option<JsValue>,
) -> PyObjectRef {
if js_val.is_object() {
if let Some(promise) = js_val.dyn_ref::<Promise>() {
// the browser module might not be injected
Expand Down Expand Up @@ -184,33 +197,21 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
u8_array.copy_to(&mut vec);
vm.ctx.new_bytes(vec)
} 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)), py_val, vm)
.unwrap();
}
dict.into_object()
PyJsObject::new(js_val.unchecked_into())
.into_ref(vm)
.into_object()
// 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)), py_val, vm)
// .unwrap();
// }
// dict.into_object()
}
} else if js_val.is_function() {
let func = js_sys::Function::from(js_val);
vm.ctx
.new_rustfunc(move |vm: &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("property to be settable");
}
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))
})
PyJsFunction::new(func, this).into_ref(vm).into_object()
} 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,
Expand All @@ -231,3 +232,11 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
vm.deserialize(&json).unwrap_or_else(|_| vm.get_none())
}
}

pub fn iter_to_array(iter: impl Iterator<Item = JsValue>) -> Array {
let arr = Array::new();
for elem in iter {
arr.push(&elem);
}
arr
}
1 change: 1 addition & 0 deletions wasm/lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod browser_module;
pub mod convert;
pub mod objjsobject;
pub mod vm_class;
pub mod wasm_builtins;

Expand Down
154 changes: 154 additions & 0 deletions wasm/lib/src/objjsobject.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use js_sys::{Function, JsString, Object, Reflect};
use wasm_bindgen::JsValue;

use rustpython_vm::function::{Args, OptionalArg};
use rustpython_vm::obj::objstr::PyStringRef;
use rustpython_vm::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use rustpython_vm::VirtualMachine;

use crate::convert;

fn get_prop(object: Object, name: &str, vm: &VirtualMachine) -> Option<PyObjectRef> {
let name: &JsString = &name.into();
if Reflect::has(&object, name).expect("Reflect.has failed") {
Some(convert::js_to_py_with_this(
vm,
Reflect::get(&object, name).expect("Reflect.get failed"),
Some(object.into()),
))
} else {
None
}
}

fn set_prop(value: &Object, name: &str, val: PyObjectRef, vm: &VirtualMachine) {
Reflect::set(value, &name.into(), &convert::py_to_js(vm, val)).expect("Reflect failed");
}

#[pyclass(name = "JSObject")]
#[derive(Debug)]
pub struct PyJsObject {
object: Object,
}
pub type PyJsObjectRef = PyRef<PyJsObject>;

impl PyValue for PyJsObject {}

#[pyimpl]
impl PyJsObject {
pub fn new(object: Object) -> PyJsObject {
PyJsObject { object }
}

pub fn object(&self) -> &Object {
&self.object
}

#[pyproperty(name = "_props")]
fn props(&self, _vm: &VirtualMachine) -> PyJsProps {
PyJsProps {
object: self.object().clone(),
}
}

#[pymethod(name = "__getattr__")]
fn getattr(&self, attr_name: PyStringRef, vm: &VirtualMachine) -> PyResult {
get_prop(self.object().clone(), attr_name.as_str(), vm).ok_or_else(|| {
vm.new_attribute_error(format!("JS value has no property {:?}", attr_name.as_str()))
})
}

#[pymethod(name = "__setattr__")]
fn setattr(&self, attr_name: PyStringRef, val: PyObjectRef, vm: &VirtualMachine) {
set_prop(self.object(), attr_name.as_str(), val, vm);
}

#[pymethod(name = "__repr__")]
fn repr(&self, _vm: &VirtualMachine) -> String {
format!("{:?}", self.object())
}
}

#[pyclass(name = "JSFunction")]
#[derive(Debug)]
pub struct PyJsFunction {
func: Function,
this: Option<JsValue>,
}

impl PyValue for PyJsFunction {}

#[pyimpl]
impl PyJsFunction {
pub fn new(func: Function, this: Option<JsValue>) -> PyJsFunction {
PyJsFunction { func, this }
}
pub fn to_function(&self) -> Function {
if let Some(this) = &self.this {
self.func.bind(&this)
} else {
self.func.clone()
}
}

#[pymethod(name = "__call__")]
fn call(&self, args: Args, vm: &VirtualMachine) -> PyResult {
let undef = JsValue::UNDEFINED;
let this = match self.this {
Some(ref this) => this,
None => &undef,
};
let args = convert::iter_to_array(args.into_iter().map(|elem| convert::py_to_js(vm, elem)));
let result = self.func.apply(this, &args);
result
.map(|val| convert::js_to_py(vm, val))
.map_err(|err| convert::js_to_py(vm, err))
}

#[pymethod(name = "__repr__")]
fn repr(&self, _vm: &VirtualMachine) -> String {
// for better formatting
let value: &JsValue = &self.func;
format!("{:?}", value)
}
}

#[pyclass(name = "JSProps")]
#[derive(Debug)]
struct PyJsProps {
object: Object,
}

impl PyValue for PyJsProps {}

#[pyimpl]
impl PyJsProps {
#[pymethod]
fn get(
&self,
item_name: PyStringRef,
default: OptionalArg,
vm: &VirtualMachine,
) -> PyObjectRef {
get_prop(self.object.clone(), item_name.as_str(), vm)
.or(default.into_option())
.unwrap_or_else(|| vm.get_none())
}

#[pymethod(name = "__getitem__")]
fn getitem(&self, item_name: PyStringRef, vm: &VirtualMachine) -> PyResult {
get_prop(self.object.clone(), item_name.as_str(), vm)
.ok_or_else(|| vm.new_key_error(format!("{:?}", item_name.as_str())))
}

#[pymethod(name = "__setitem__")]
fn setitem(&self, item_name: PyStringRef, val: PyObjectRef, vm: &VirtualMachine) {
set_prop(&self.object, item_name.as_str(), val, vm);
}
}

pub fn init(ctx: &PyContext) {
ctx.add_class::<PyJsObject>();
ctx.add_class::<PyJsFunction>();
ctx.add_class::<PyJsProps>();
}
2 changes: 2 additions & 0 deletions wasm/lib/src/vm_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rustpython_vm::VirtualMachine;

use crate::browser_module::setup_browser_module;
use crate::convert;
use crate::objjsobject;
use crate::wasm_builtins;

pub(crate) struct StoredVirtualMachine {
Expand All @@ -31,6 +32,7 @@ impl StoredVirtualMachine {
setup_browser_module(&vm);
}
vm.wasm_id = Some(id);
objjsobject::init(&vm.ctx);
StoredVirtualMachine {
vm,
scope: RefCell::new(scope),
Expand Down