Skip to content

Commit edbb328

Browse files
committed
Add PyJsValue
1 parent 19be5c9 commit edbb328

File tree

5 files changed

+188
-11
lines changed

5 files changed

+188
-11
lines changed

vm/src/pyobject.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::any::Any;
2+
use std::any::TypeId;
23
use std::cell::Cell;
34
use std::cell::RefCell;
4-
use std::collections::HashMap;
5+
use std::collections::hash_map::{Entry, HashMap};
56
use std::fmt;
67
use std::marker::PhantomData;
78
use std::mem;
@@ -162,6 +163,7 @@ pub struct PyContext {
162163
pub weakproxy_type: PyClassRef,
163164
pub object: PyClassRef,
164165
pub exceptions: exceptions::ExceptionZoo,
166+
rust_classes: RefCell<HashMap<TypeId, PyClassRef>>,
165167
}
166168

167169
pub fn create_type(name: &str, type_type: &PyClassRef, base: &PyClassRef) -> PyClassRef {
@@ -367,6 +369,7 @@ impl PyContext {
367369
weakproxy_type,
368370
type_type,
369371
exceptions,
372+
rust_classes: RefCell::default(),
370373
};
371374
objtype::init(&context);
372375
objlist::init(&context);
@@ -407,6 +410,36 @@ impl PyContext {
407410
context
408411
}
409412

413+
pub fn _add_class(&self, typeid: TypeId, class: PyClassRef) {
414+
let classes = &mut self.rust_classes.borrow_mut();
415+
match classes.entry(typeid) {
416+
Entry::Occupied(o) => panic!(
417+
"Attempted to add rust type twice to the same PyContext, previously held class \
418+
was {:?}",
419+
o.get()
420+
),
421+
Entry::Vacant(v) => {
422+
v.insert(class);
423+
}
424+
}
425+
}
426+
427+
#[inline]
428+
pub fn add_class<T: PyClassImpl + 'static>(&self) -> PyClassRef {
429+
let class = T::make_class(self);
430+
self._add_class(TypeId::of::<T>(), class.clone());
431+
class
432+
}
433+
434+
pub fn _get_class(&self, typeid: &TypeId) -> Option<PyClassRef> {
435+
self.rust_classes.borrow_mut().get(typeid).cloned()
436+
}
437+
438+
#[inline]
439+
pub fn get_class<T: 'static>(&self) -> Option<PyClassRef> {
440+
self._get_class(&TypeId::of::<T>())
441+
}
442+
410443
pub fn bytearray_type(&self) -> PyClassRef {
411444
self.bytearray_type.clone()
412445
}
@@ -1202,7 +1235,13 @@ impl PyObject<dyn PyObjectPayload> {
12021235
pub trait PyValue: fmt::Debug + Sized + 'static {
12031236
const HAVE_DICT: bool = false;
12041237

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

12071246
fn into_ref(self, vm: &VirtualMachine) -> PyRef<Self> {
12081247
PyRef {

wasm/lib/src/convert.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use wasm_bindgen::{closure::Closure, prelude::*, JsCast};
44

55
use rustpython_vm::function::PyFuncArgs;
66
use rustpython_vm::obj::{objbytes, objint, objsequence, objtype};
7-
use rustpython_vm::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue};
7+
use rustpython_vm::pyobject::{PyObjectRef, PyResult, PyValue};
88
use rustpython_vm::VirtualMachine;
99

1010
use crate::browser_module;
11+
use crate::objjsvalue::PyJsValue;
1112
use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine};
1213

1314
pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue {
@@ -184,14 +185,15 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
184185
u8_array.copy_to(&mut vec);
185186
vm.ctx.new_bytes(vec)
186187
} else {
187-
let dict = vm.ctx.new_dict();
188-
for pair in object_entries(&Object::from(js_val)) {
189-
let (key, val) = pair.expect("iteration over object to not fail");
190-
let py_val = js_to_py(vm, val);
191-
dict.set_item(&String::from(js_sys::JsString::from(key)), py_val, vm)
192-
.unwrap();
193-
}
194-
dict.into_object()
188+
PyJsValue::new(js_val).into_ref(vm).into_object()
189+
// let dict = vm.ctx.new_dict();
190+
// for pair in object_entries(&Object::from(js_val)) {
191+
// let (key, val) = pair.expect("iteration over object to not fail");
192+
// let py_val = js_to_py(vm, val);
193+
// dict.set_item(&String::from(js_sys::JsString::from(key)), py_val, vm)
194+
// .unwrap();
195+
// }
196+
// dict.into_object()
195197
}
196198
} else if js_val.is_function() {
197199
let func = js_sys::Function::from(js_val);
@@ -231,3 +233,11 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
231233
vm.deserialize(&json).unwrap_or_else(|_| vm.get_none())
232234
}
233235
}
236+
237+
pub fn iter_to_array(iter: impl Iterator<Item = JsValue>) -> Array {
238+
let arr = Array::new();
239+
for elem in iter {
240+
arr.push(&elem);
241+
}
242+
arr
243+
}

wasm/lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod browser_module;
22
pub mod convert;
3+
pub mod objjsvalue;
34
pub mod vm_class;
45
pub mod wasm_builtins;
56

wasm/lib/src/objjsvalue.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use js_sys::{Function, JsString, Reflect};
2+
use wasm_bindgen::JsValue;
3+
4+
use rustpython_vm::function::{Args, OptionalArg};
5+
use rustpython_vm::obj::objstr::PyStringRef;
6+
use rustpython_vm::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue};
7+
use rustpython_vm::VirtualMachine;
8+
9+
use crate::convert;
10+
11+
fn get_prop(value: &JsValue, name: &str, vm: &VirtualMachine) -> Option<PyObjectRef> {
12+
let name: &JsString = &name.into();
13+
if Reflect::has(value, name).expect("Reflect.has failed") {
14+
Some(convert::js_to_py(
15+
vm,
16+
Reflect::get(value, name).expect("Reflect.get failed"),
17+
))
18+
} else {
19+
None
20+
}
21+
}
22+
23+
#[pyclass(name = "JsValue")]
24+
#[derive(Debug)]
25+
pub struct PyJsValue {
26+
value: JsValue,
27+
}
28+
pub type PyJsValueRef = PyRef<PyJsValue>;
29+
30+
impl PyValue for PyJsValue {}
31+
32+
#[pyimpl]
33+
impl PyJsValue {
34+
pub fn new(value: JsValue) -> PyJsValue {
35+
PyJsValue { value }
36+
}
37+
38+
pub fn value(&self) -> &JsValue {
39+
&self.value
40+
}
41+
42+
#[pyproperty(name = "_props")]
43+
fn props(&self, _vm: &VirtualMachine) -> PyJsProps {
44+
PyJsProps {
45+
value: self.value().clone(),
46+
}
47+
}
48+
49+
#[pymethod(name = "__getattr__")]
50+
fn getattr(&self, attr_name: PyStringRef, vm: &VirtualMachine) -> PyResult {
51+
get_prop(self.value(), attr_name.as_str(), vm).ok_or_else(|| {
52+
vm.new_attribute_error(format!("JS value has no property {:?}", attr_name.as_str()))
53+
})
54+
}
55+
56+
#[pymethod(name = "__repr__")]
57+
fn repr(&self, _vm: &VirtualMachine) -> String {
58+
format!("{:?}", self.value())
59+
}
60+
}
61+
62+
#[pyclass(name = "JsFunction")]
63+
#[derive(Debug)]
64+
pub struct PyJsFunction {
65+
func: Function,
66+
this: Option<JsValue>,
67+
}
68+
69+
impl PyValue for PyJsFunction {}
70+
71+
#[pyimpl]
72+
impl PyJsFunction {
73+
pub fn new(func: Function, this: Option<JsValue>) -> PyJsFunction {
74+
PyJsFunction { func, this }
75+
}
76+
77+
#[pymethod(name = "__call__")]
78+
fn call(&self, args: Args, vm: &VirtualMachine) -> PyResult {
79+
let undef = JsValue::UNDEFINED;
80+
let this = match self.this {
81+
Some(ref this) => this,
82+
None => &undef,
83+
};
84+
let args = convert::iter_to_array(args.into_iter().map(|elem| convert::py_to_js(vm, elem)));
85+
let result = self.func.apply(this, &args);
86+
result
87+
.map(|val| convert::js_to_py(vm, val))
88+
.map_err(|err| convert::js_to_py(vm, err))
89+
}
90+
}
91+
92+
#[pyclass(name = "JsProps")]
93+
#[derive(Debug)]
94+
struct PyJsProps {
95+
value: JsValue,
96+
}
97+
98+
impl PyValue for PyJsProps {}
99+
100+
#[pyimpl]
101+
impl PyJsProps {
102+
#[pymethod]
103+
fn get(
104+
&self,
105+
item_name: PyStringRef,
106+
default: OptionalArg,
107+
vm: &VirtualMachine,
108+
) -> PyObjectRef {
109+
get_prop(&self.value, item_name.as_str(), vm)
110+
.or(default.into_option())
111+
.unwrap_or_else(|| vm.get_none())
112+
}
113+
114+
#[pymethod(name = "__getitem__")]
115+
fn getitem(&self, item_name: PyStringRef, vm: &VirtualMachine) -> PyResult {
116+
get_prop(&self.value, item_name.as_str(), vm)
117+
.ok_or_else(|| vm.new_key_error(format!("{:?}", item_name.as_str())))
118+
}
119+
}
120+
121+
pub fn init(ctx: &PyContext) {
122+
ctx.add_class::<PyJsValue>();
123+
ctx.add_class::<PyJsFunction>();
124+
ctx.add_class::<PyJsProps>();
125+
}

wasm/lib/src/vm_class.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use rustpython_vm::VirtualMachine;
1313

1414
use crate::browser_module::setup_browser_module;
1515
use crate::convert;
16+
use crate::objjsvalue;
1617
use crate::wasm_builtins;
1718

1819
pub(crate) struct StoredVirtualMachine {
@@ -31,6 +32,7 @@ impl StoredVirtualMachine {
3132
setup_browser_module(&vm);
3233
}
3334
vm.wasm_id = Some(id);
35+
objjsvalue::init(&vm.ctx);
3436
StoredVirtualMachine {
3537
vm,
3638
scope: RefCell::new(scope),

0 commit comments

Comments
 (0)