Expose primitive information about JsObject

Adds bindings for wbindgen to fill in via JS bindings to read the various
primitive properties of a JS value.
This commit is contained in:
Alex Crichton 2017-12-31 15:45:47 -08:00
parent 996c296de8
commit 0b81185c99
3 changed files with 391 additions and 48 deletions

View File

@ -525,58 +525,151 @@ impl Js {
extra_imports_interface.push_str("}\n");
}
if self.wasm_import_needed("__wbindgen_object_clone_ref", m) {
self.expose_add_heap_object();
self.expose_get_object();
let bump_cnt = if self.debug {
String::from("
if (typeof(val) === 'number')
throw new Error('corrupt slab');
val.cnt += 1;
")
} else {
String::from("(val as {cnt:number}).cnt += 1;")
{
let mut bind = |name: &str, f: &Fn(&mut Self) -> String| {
if !self.wasm_import_needed(name, m) {
return
}
imports_object.push_str(&format!("
{}: {},
", m.import_name(name), f(self)));
};
imports_object.push_str(&format!("
{}: function(idx: number): number {{
// If this object is on the stack promote it to the heap.
if ((idx & 1) === 1)
return addHeapObject(getObject(idx));
// Otherwise if the object is on the heap just bump the
// refcount and move on
const val = slab[idx >> 1];
{}
return idx;
}},
", m.import_name("__wbindgen_object_clone_ref"), bump_cnt));
}
bind("__wbindgen_object_clone_ref", &|me| {
me.expose_add_heap_object();
me.expose_get_object();
let bump_cnt = if me.debug {
String::from("
if (typeof(val) === 'number')
throw new Error('corrupt slab');
val.cnt += 1;
")
} else {
String::from("(val as {cnt:number}).cnt += 1;")
};
format!("
function(idx: number): number {{
// If this object is on the stack promote it to the heap.
if ((idx & 1) === 1)
return addHeapObject(getObject(idx));
if self.wasm_import_needed("__wbindgen_object_drop_ref", m) {
self.expose_drop_ref();
let name = m.import_name("__wbindgen_object_drop_ref");
imports_object.push_str(&format!("{}: dropRef,\n", name));
}
// Otherwise if the object is on the heap just bump the
// refcount and move on
const val = slab[idx >> 1];
{}
return idx;
}}
", bump_cnt)
});
if self.wasm_import_needed("__wbindgen_string_new", m) {
self.expose_add_heap_object();
self.expose_get_string_from_wasm();
imports_object.push_str(&format!("
{}: function(ptr: number, len: number): number {{
return addHeapObject(getStringFromWasm(ptr, len));
}},
", m.import_name("__wbindgen_string_new")));
}
bind("__wbindgen_object_drop_ref", &|me| {
me.expose_drop_ref();
"dropRef".to_string()
});
if self.wasm_import_needed("__wbindgen_throw", m) {
self.expose_get_string_from_wasm();
imports_object.push_str(&format!("\
{}: function(ptr: number, len: number) {{
throw new Error(getStringFromWasm(ptr, len));
}},
",
m.import_name("__wbindgen_throw"),
));
bind("__wbindgen_string_new", &|me| {
me.expose_add_heap_object();
me.expose_get_string_from_wasm();
String::from("(p, l) => addHeapObject(getStringFromWasm(p, l))")
});
bind("__wbindgen_number_new", &|me| {
me.expose_add_heap_object();
String::from("addHeapObject")
});
bind("__wbindgen_number_get", &|me| {
me.expose_global_memory();
String::from("
function(n: number, invalid: number): number {
let obj = getObject(n);
if (typeof(obj) === 'number')
return obj;
(new Uint8Array(memory.buffer))[invalid] = 1;
return 0;
}
")
});
bind("__wbindgen_undefined_new", &|me| {
me.expose_add_heap_object();
String::from("() => addHeapObject(undefined)")
});
bind("__wbindgen_null_new", &|me| {
me.expose_add_heap_object();
String::from("() => addHeapObject(null)")
});
bind("__wbindgen_is_null", &|me| {
me.expose_get_object();
String::from("(idx) => getObject(idx) === null ? 1 : 0")
});
bind("__wbindgen_is_undefined", &|me| {
me.expose_get_object();
String::from("(idx) => getObject(idx) === undefined ? 1 : 0")
});
bind("__wbindgen_boolean_new", &|me| {
me.expose_add_heap_object();
String::from("(v) => addHeapObject(v == 1)")
});
bind("__wbindgen_boolean_get", &|me| {
me.expose_get_object();
String::from("(i) => {
let v = getObject(i);
if (typeof(v) == 'boolean') {
return v ? 1 : 0;
} else {
return 2;
}
}")
});
bind("__wbindgen_symbol_new", &|me| {
me.expose_get_string_from_wasm();
me.expose_add_heap_object();
String::from("(ptr, len) => {
let a: Symbol;
console.log(ptr, len);
if (ptr === 0) {
a = Symbol();
} else {
a = Symbol(getStringFromWasm(ptr, len));
}
return addHeapObject(a);
}")
});
bind("__wbindgen_is_symbol", &|me| {
me.expose_get_object();
String::from("(i) => typeof(getObject(i)) == 'symbol' ? 1 : 0")
});
bind("__wbindgen_throw", &|me| {
me.expose_get_string_from_wasm();
String::from("
function(ptr: number, len: number) {
throw new Error(getStringFromWasm(ptr, len));
}
")
});
bind("__wbindgen_string_get", &|me| {
me.expose_pass_string_to_wasm(m);
me.expose_get_object();
me.expose_global_memory();
String::from("(i, len_ptr) => {
let obj = getObject(i);
if (typeof(obj) !== 'string')
return 0;
const [ptr, len] = passStringToWasm(obj);
(new Uint32Array(memory.buffer))[len_ptr / 4] = len;
return ptr;
}")
});
}
let mut writes = String::new();

View File

@ -9,6 +9,7 @@
extern crate wasm_bindgen_macro;
use std::mem;
use std::ptr;
/// A module which is typically glob imported from:
///
@ -31,7 +32,7 @@ pub struct JsObject {
}
impl JsObject {
/// Creates a new JS object which is a string.
/// Creates a new JS value which is a string.
///
/// The utf-8 string provided is copied to the JS heap and the string will
/// be owned by the JS garbage collector.
@ -41,6 +42,52 @@ impl JsObject {
}
}
/// Creates a new JS value which is a number.
///
/// This function creates a JS value representing a number (a heap
/// allocated number) and returns a handle to the JS version of it.
pub fn from_f64(n: f64) -> JsObject {
unsafe {
JsObject::__from_idx(__wbindgen_number_new(n))
}
}
/// Creates a new JS value which is a boolean.
///
/// This function creates a JS object representing a boolean (a heap
/// allocated boolean) and returns a handle to the JS version of it.
pub fn from_bool(b: bool) -> JsObject {
unsafe {
JsObject::__from_idx(__wbindgen_boolean_new(b as u32))
}
}
/// Creates a new JS value representing `undefined`.
pub fn undefined() -> JsObject {
unsafe {
JsObject::__from_idx(__wbindgen_undefined_new())
}
}
/// Creates a new JS value representing `null`.
pub fn null() -> JsObject {
unsafe {
JsObject::__from_idx(__wbindgen_null_new())
}
}
/// Creates a new JS symbol with the optional description specified.
///
/// This function will invoke the `Symbol` constructor in JS and return the
/// JS object corresponding to the symbol created.
pub fn symbol(description: Option<&str>) -> JsObject {
unsafe {
let ptr = description.map(|s| s.as_ptr()).unwrap_or(ptr::null());
let len = description.map(|s| s.len()).unwrap_or(0);
JsObject::__from_idx(__wbindgen_symbol_new(ptr, len))
}
}
#[doc(hidden)]
pub fn __from_idx(idx: u32) -> JsObject {
JsObject { idx }
@ -57,6 +104,77 @@ impl JsObject {
mem::forget(self);
return ret
}
/// Returns the `f64` value of this JS value if it's an instance of a
/// number.
///
/// If this JS value is not an instance of a number then this returns
/// `None`.
pub fn as_f64(&self) -> Option<f64> {
let mut invalid = 0;
unsafe {
let ret = __wbindgen_number_get(self.idx, &mut invalid);
if invalid == 1 {
None
} else {
Some(ret)
}
}
}
/// Returns the `String` of this JS value if it's an instance of a
/// string and it's valid utf-8.
///
/// If this JS value is not an instance of a string or if it's not valid
/// utf-8 then this returns `None`.
pub fn as_string(&self) -> Option<String> {
unsafe {
let mut len = 0;
let ptr = __wbindgen_string_get(self.idx, &mut len);
if ptr.is_null() {
None
} else {
let data = Vec::from_raw_parts(ptr, len, len);
Some(String::from_utf8_unchecked(data))
}
}
}
/// Returns the `bool` value of this JS value if it's an instance of a
/// boolean.
///
/// If this JS value is not an instance of a boolean then this returns
/// `None`.
pub fn as_bool(&self) -> Option<bool> {
unsafe {
match __wbindgen_boolean_get(self.idx) {
0 => Some(false),
1 => Some(true),
_ => None,
}
}
}
/// Tests whether this JS value is `null`
pub fn is_null(&self) -> bool {
unsafe {
__wbindgen_is_null(self.idx) == 1
}
}
/// Tests whether this JS value is `undefined`
pub fn is_undefined(&self) -> bool {
unsafe {
__wbindgen_is_undefined(self.idx) == 1
}
}
/// Tests whether the type of this JS value is `symbol`
pub fn is_symbol(&self) -> bool {
unsafe {
__wbindgen_is_symbol(self.idx) == 1
}
}
}
impl<'a> From<&'a str> for JsObject {
@ -65,10 +183,45 @@ impl<'a> From<&'a str> for JsObject {
}
}
impl<'a> From<&'a String> for JsObject {
fn from(s: &'a String) -> JsObject {
JsObject::from_str(s)
}
}
impl From<bool> for JsObject {
fn from(s: bool) -> JsObject {
JsObject::from_bool(s)
}
}
macro_rules! numbers {
($($n:ident)*) => ($(
impl From<$n> for JsObject {
fn from(n: $n) -> JsObject {
JsObject::from_f64(n.into())
}
}
)*)
}
numbers! { i8 u8 i16 u16 i32 u32 f32 f64 }
extern {
fn __wbindgen_object_clone_ref(idx: u32) -> u32;
fn __wbindgen_object_drop_ref(idx: u32);
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_number_new(f: f64) -> u32;
fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64;
fn __wbindgen_null_new() -> u32;
fn __wbindgen_undefined_new() -> u32;
fn __wbindgen_is_null(idx: u32) -> u32;
fn __wbindgen_is_undefined(idx: u32) -> u32;
fn __wbindgen_boolean_new(val: u32) -> u32;
fn __wbindgen_boolean_get(idx: u32) -> u32;
fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_is_symbol(idx: u32) -> u32;
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
}
impl Clone for JsObject {

View File

@ -18,6 +18,82 @@ fn works() {
pub fn bar(s: &str) -> JsObject {
JsObject::from(s)
}
pub fn baz() -> JsObject {
JsObject::from(1.0)
}
pub fn baz2(a: &JsObject, b: &JsObject) {
assert_eq!(a.as_f64(), Some(2.0));
assert_eq!(b.as_f64(), None);
}
pub fn js_null() -> JsObject {
JsObject::null()
}
pub fn js_undefined() -> JsObject {
JsObject::undefined()
}
pub fn test_is_null_undefined(
a: &JsObject,
b: &JsObject,
c: &JsObject,
) {
assert!(a.is_null());
assert!(!a.is_undefined());
assert!(!b.is_null());
assert!(b.is_undefined());
assert!(!c.is_null());
assert!(!c.is_undefined());
}
pub fn get_true() -> JsObject {
JsObject::from(true)
}
pub fn get_false() -> JsObject {
JsObject::from(false)
}
pub fn test_bool(
a: &JsObject,
b: &JsObject,
c: &JsObject,
) {
assert_eq!(a.as_bool(), Some(true));
assert_eq!(b.as_bool(), Some(false));
assert_eq!(c.as_bool(), None);
}
pub fn mk_symbol() -> JsObject {
let a = JsObject::symbol(None);
assert!(a.is_symbol());
return a
}
pub fn mk_symbol2(s: &str) -> JsObject {
let a = JsObject::symbol(Some(s));
assert!(a.is_symbol());
return a
}
pub fn assert_symbols(a: &JsObject, b: &JsObject) {
assert!(a.is_symbol());
assert!(!b.is_symbol());
}
pub fn acquire_string(a: &JsObject, b: &JsObject) {
assert_eq!(a.as_string().unwrap(), "foo");
assert_eq!(b.as_string(), None);
}
pub fn acquire_string2(a: &JsObject) -> String {
a.as_string().unwrap_or("wrong".to_string())
}
}
"#)
.file("test.ts", r#"
@ -29,6 +105,27 @@ fn works() {
export function test(wasm: Exports) {
assert.strictEqual(wasm.foo(), 'foo');
assert.strictEqual(wasm.bar('a'), 'a');
assert.strictEqual(wasm.baz(), 1);
wasm.baz2(2, 'a');
assert.strictEqual(wasm.js_null(), null);
assert.strictEqual(wasm.js_undefined(), undefined);
wasm.test_is_null_undefined(null, undefined, 1.0);
assert.strictEqual(wasm.get_true(), true);
assert.strictEqual(wasm.get_false(), false);
wasm.test_bool(true, false, 1.0);
assert.strictEqual(typeof(wasm.mk_symbol()), 'symbol');
assert.strictEqual(typeof(wasm.mk_symbol2('a')), 'symbol');
assert.strictEqual(Symbol.keyFor(wasm.mk_symbol()), undefined);
assert.strictEqual(Symbol.keyFor(wasm.mk_symbol2('b')), undefined);
wasm.assert_symbols(Symbol(), 'a');
wasm.acquire_string('foo', null)
assert.strictEqual(wasm.acquire_string2(''), '');
assert.strictEqual(wasm.acquire_string2('a'), 'a');
}
"#)
.test();