Skip to content

Commit 56bcb02

Browse files
committed
Add _js module and fix imports
1 parent 61b2687 commit 56bcb02

File tree

4 files changed

+248
-5
lines changed

4 files changed

+248
-5
lines changed

wasm/lib/src/js_module.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use crate::convert;
2+
use js_sys::{Array, Object, Reflect};
3+
use rustpython_vm::function::Args;
4+
use rustpython_vm::obj::{objfloat::PyFloatRef, objstr::PyStringRef, objtype::PyClassRef};
5+
use rustpython_vm::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject};
6+
use rustpython_vm::VirtualMachine;
7+
use wasm_bindgen::{prelude::*, JsCast};
8+
9+
// I don't know why there is no other option for this
10+
#[wasm_bindgen(inline_js = "export function type_of(a) { return typeof a; }")]
11+
extern "C" {
12+
#[wasm_bindgen]
13+
fn type_of(a: JsValue) -> String;
14+
}
15+
16+
#[pyclass(name = "JsValue")]
17+
#[derive(Debug)]
18+
pub struct PyJsValue {
19+
value: JsValue,
20+
}
21+
type PyJsValueRef = PyRef<PyJsValue>;
22+
23+
impl PyValue for PyJsValue {
24+
fn class(vm: &VirtualMachine) -> PyClassRef {
25+
vm.class("_js", "JsValue")
26+
}
27+
}
28+
29+
enum JsProperty {
30+
Str(PyStringRef),
31+
Js(PyJsValueRef),
32+
}
33+
34+
impl TryFromObject for JsProperty {
35+
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
36+
PyStringRef::try_from_object(vm, obj.clone())
37+
.map(JsProperty::Str)
38+
.or_else(|_| PyJsValueRef::try_from_object(vm, obj).map(JsProperty::Js))
39+
}
40+
}
41+
42+
impl JsProperty {
43+
fn to_jsvalue(self) -> JsValue {
44+
match self {
45+
JsProperty::Str(s) => s.as_str().into(),
46+
JsProperty::Js(value) => value.value.clone(),
47+
}
48+
}
49+
}
50+
51+
#[pyimpl]
52+
impl PyJsValue {
53+
#[inline]
54+
pub fn new(value: impl Into<JsValue>) -> PyJsValue {
55+
PyJsValue {
56+
value: value.into(),
57+
}
58+
}
59+
60+
#[pyclassmethod]
61+
fn null(cls: PyClassRef, vm: &VirtualMachine) -> PyResult<PyJsValueRef> {
62+
PyJsValue::new(JsValue::NULL).into_ref_with_type(vm, cls)
63+
}
64+
65+
#[pyclassmethod]
66+
fn undefined(cls: PyClassRef, vm: &VirtualMachine) -> PyResult<PyJsValueRef> {
67+
PyJsValue::new(JsValue::UNDEFINED).into_ref_with_type(vm, cls)
68+
}
69+
70+
#[pyclassmethod]
71+
fn fromstr(cls: PyClassRef, s: PyStringRef, vm: &VirtualMachine) -> PyResult<PyJsValueRef> {
72+
PyJsValue::new(s.as_str()).into_ref_with_type(vm, cls)
73+
}
74+
75+
#[pyclassmethod]
76+
fn fromfloat(cls: PyClassRef, n: PyFloatRef, vm: &VirtualMachine) -> PyResult<PyJsValueRef> {
77+
PyJsValue::new(n.to_f64()).into_ref_with_type(vm, cls)
78+
}
79+
80+
#[pyclassmethod]
81+
fn new_object(
82+
cls: PyClassRef,
83+
opts: NewObjectOptions,
84+
vm: &VirtualMachine,
85+
) -> PyResult<PyJsValueRef> {
86+
let value = if let Some(proto) = opts.prototype {
87+
if let Some(proto) = proto.value.dyn_ref::<Object>() {
88+
Object::create(proto)
89+
} else if proto.value.is_null() {
90+
Object::create(proto.value.unchecked_ref())
91+
} else {
92+
return Err(vm.new_value_error(format!("prototype must be an Object or null")));
93+
}
94+
} else {
95+
Object::new()
96+
};
97+
PyJsValue::new(value).into_ref_with_type(vm, cls)
98+
}
99+
100+
#[pymethod]
101+
fn get_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult<PyJsValue> {
102+
let name = &name.to_jsvalue();
103+
if Reflect::has(&self.value, name).map_err(|err| convert::js_to_py(vm, err))? {
104+
Reflect::get(&self.value, name)
105+
.map(PyJsValue::new)
106+
.map_err(|err| convert::js_to_py(vm, err))
107+
} else {
108+
Err(vm.new_attribute_error(format!("No attribute {:?} on JS value", name)))
109+
}
110+
}
111+
112+
#[pymethod]
113+
fn set_prop(
114+
&self,
115+
name: JsProperty,
116+
value: PyJsValueRef,
117+
vm: &VirtualMachine,
118+
) -> PyResult<PyJsValue> {
119+
Reflect::set(&self.value, &name.to_jsvalue(), &value.value)
120+
.map(PyJsValue::new)
121+
.map_err(|err| convert::js_to_py(vm, err))
122+
}
123+
124+
#[pymethod]
125+
fn as_str(&self, _vm: &VirtualMachine) -> Option<String> {
126+
self.value.as_string()
127+
}
128+
129+
#[pymethod]
130+
fn as_float(&self, _vm: &VirtualMachine) -> Option<f64> {
131+
self.value.as_f64()
132+
}
133+
134+
#[pymethod]
135+
/// Checks that `typeof self == "object" && self !== null`. Use instead
136+
/// of `value.typeof() == "object"`
137+
fn is_object(&self, _vm: &VirtualMachine) -> bool {
138+
self.value.is_object()
139+
}
140+
141+
#[pymethod]
142+
fn call(
143+
&self,
144+
args: Args<PyJsValueRef>,
145+
opts: CallOptions,
146+
vm: &VirtualMachine,
147+
) -> PyResult<PyJsValue> {
148+
let func = self
149+
.value
150+
.dyn_ref::<js_sys::Function>()
151+
.ok_or_else(|| vm.new_type_error("JS value is not callable".to_string()))?;
152+
let this = opts
153+
.this
154+
.map(|this| this.value.clone())
155+
.unwrap_or(JsValue::UNDEFINED);
156+
let js_args = Array::new();
157+
for arg in args {
158+
js_args.push(&arg.value);
159+
}
160+
Reflect::apply(func, &this, &js_args)
161+
.map(PyJsValue::new)
162+
.map_err(|err| convert::js_to_py(vm, err))
163+
}
164+
165+
#[pymethod(name = "typeof")]
166+
fn type_of(&self, _vm: &VirtualMachine) -> String {
167+
type_of(self.value.clone())
168+
}
169+
170+
#[pymethod(name = "__repr__")]
171+
fn repr(&self, _vm: &VirtualMachine) -> String {
172+
format!("{:?}", self.value)
173+
}
174+
}
175+
176+
#[derive(FromArgs)]
177+
struct CallOptions {
178+
#[pyarg(keyword_only, default = "None")]
179+
this: Option<PyJsValueRef>,
180+
}
181+
182+
#[derive(FromArgs)]
183+
struct NewObjectOptions {
184+
#[pyarg(keyword_only, default = "None")]
185+
prototype: Option<PyJsValueRef>,
186+
}
187+
188+
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
189+
py_module!(vm, "_js", {
190+
"JsValue" => PyJsValue::make_class(&vm.ctx),
191+
})
192+
}
193+
194+
pub fn setup_js_module(vm: &VirtualMachine) {
195+
vm.stdlib_inits
196+
.borrow_mut()
197+
.insert("_js".to_string(), Box::new(make_module));
198+
}

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 js_module;
34
pub mod vm_class;
45
pub mod wasm_builtins;
56

wasm/lib/src/vm_class.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ use wasm_bindgen::prelude::*;
88
use rustpython_vm::compile;
99
use rustpython_vm::frame::{NameProtocol, Scope};
1010
use rustpython_vm::function::PyFuncArgs;
11-
use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult};
11+
use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, PyValue};
1212
use rustpython_vm::VirtualMachine;
1313

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

1819
pub(crate) struct StoredVirtualMachine {
@@ -26,11 +27,24 @@ pub(crate) struct StoredVirtualMachine {
2627
impl StoredVirtualMachine {
2728
fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine {
2829
let mut vm = VirtualMachine::new();
30+
vm.wasm_id = Some(id);
2931
let scope = vm.new_scope_with_builtins();
32+
33+
js_module::setup_js_module(&vm);
3034
if inject_browser_module {
35+
vm.stdlib_inits.borrow_mut().insert(
36+
"_window".to_string(),
37+
Box::new(|vm| {
38+
py_module!(vm, "_window", {
39+
"window" => js_module::PyJsValue::new(wasm_builtins::window()).into_ref(vm),
40+
})
41+
}),
42+
);
3143
setup_browser_module(&vm);
3244
}
33-
vm.wasm_id = Some(id);
45+
46+
*vm.import_func.borrow_mut() = vm.ctx.new_rustfunc(wasm_builtins::builtin_import);
47+
3448
StoredVirtualMachine {
3549
vm,
3650
scope: RefCell::new(scope),

wasm/lib/src/wasm_builtins.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
use js_sys::{self, Array};
88
use web_sys::{self, console};
99

10-
use rustpython_vm::function::PyFuncArgs;
11-
use rustpython_vm::obj::{objstr, objtype};
12-
use rustpython_vm::pyobject::{IdProtocol, PyObjectRef, PyResult, TypeProtocol};
10+
use rustpython_vm::function::{Args, KwArgs, PyFuncArgs};
11+
use rustpython_vm::import;
12+
use rustpython_vm::obj::{
13+
objstr::{self, PyStringRef},
14+
objtype,
15+
};
16+
use rustpython_vm::pyobject::{IdProtocol, ItemProtocol, PyObjectRef, PyResult, TypeProtocol};
1317
use rustpython_vm::VirtualMachine;
1418

1519
pub(crate) fn window() -> web_sys::Window {
@@ -76,3 +80,29 @@ pub fn builtin_print_console(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult
7680
console::log(&arr);
7781
Ok(vm.get_none())
7882
}
83+
84+
pub fn builtin_import(
85+
module_name: PyStringRef,
86+
_args: Args,
87+
_kwargs: KwArgs,
88+
vm: &VirtualMachine,
89+
) -> PyResult<PyObjectRef> {
90+
let module_name = module_name.as_str();
91+
92+
let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap();
93+
94+
// First, see if we already loaded the module:
95+
if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) {
96+
Ok(module)
97+
} else if vm.frozen.borrow().contains_key(module_name) {
98+
import::import_frozen(vm, module_name)
99+
} else if vm.stdlib_inits.borrow().contains_key(module_name) {
100+
import::import_builtin(vm, module_name)
101+
} else {
102+
let notfound_error = vm.context().exceptions.module_not_found_error.clone();
103+
Err(vm.new_exception(
104+
notfound_error,
105+
format!("Module {:?} not found", module_name),
106+
))
107+
}
108+
}

0 commit comments

Comments
 (0)