mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-25 06:02:13 +00:00
Classes are now working!
This commit is contained in:
parent
7c510a8a7e
commit
6593b5ef69
@ -8,3 +8,6 @@ wasm-bindgen-macro = { path = "crates/wasm-bindgen-macro" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-support = { path = "crates/test-support" }
|
test-support = { path = "crates/test-support" }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["crates/wasm-bindgen-cli"]
|
||||||
|
@ -9,6 +9,9 @@ use std::sync::atomic::*;
|
|||||||
use std::sync::{Once, ONCE_INIT};
|
use std::sync::{Once, ONCE_INIT};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
|
thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst));
|
||||||
|
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
files: Vec<(String, String)>,
|
files: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
@ -25,10 +28,12 @@ pub fn project() -> Project {
|
|||||||
files: vec![
|
files: vec![
|
||||||
("Cargo.toml".to_string(), format!(r#"
|
("Cargo.toml".to_string(), format!(r#"
|
||||||
[package]
|
[package]
|
||||||
name = "test"
|
name = "test{}"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
authors = []
|
authors = []
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
@ -37,7 +42,7 @@ pub fn project() -> Project {
|
|||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 2 # TODO: decrease when upstream is not buggy
|
opt-level = 2 # TODO: decrease when upstream is not buggy
|
||||||
"#, dir.display())),
|
"#, IDX.with(|x| *x), dir.display())),
|
||||||
|
|
||||||
("Cargo.lock".to_string(), lockfile),
|
("Cargo.lock".to_string(), lockfile),
|
||||||
|
|
||||||
@ -60,8 +65,6 @@ pub fn project() -> Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn root() -> PathBuf {
|
pub fn root() -> PathBuf {
|
||||||
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
|
|
||||||
thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst));
|
|
||||||
let idx = IDX.with(|x| *x);
|
let idx = IDX.with(|x| *x);
|
||||||
|
|
||||||
let mut me = env::current_exe().unwrap();
|
let mut me = env::current_exe().unwrap();
|
||||||
@ -123,7 +126,8 @@ impl Project {
|
|||||||
.env("CARGO_TARGET_DIR", &target_dir);
|
.env("CARGO_TARGET_DIR", &target_dir);
|
||||||
run(&mut cmd, "cargo");
|
run(&mut cmd, "cargo");
|
||||||
|
|
||||||
let mut out = target_dir.join("wasm32-unknown-unknown/debug/test.wasm");
|
let idx = IDX.with(|x| *x);
|
||||||
|
let mut out = target_dir.join(&format!("wasm32-unknown-unknown/debug/test{}.wasm", idx));
|
||||||
if Command::new("wasm-gc").output().is_ok() {
|
if Command::new("wasm-gc").output().is_ok() {
|
||||||
let tmp = out;
|
let tmp = out;
|
||||||
out = tmp.with_extension("gc.wasm");
|
out = tmp.with_extension("gc.wasm");
|
||||||
|
@ -4,7 +4,11 @@ use shared;
|
|||||||
pub struct Js {
|
pub struct Js {
|
||||||
pub expose_global_memory: bool,
|
pub expose_global_memory: bool,
|
||||||
pub expose_global_exports: bool,
|
pub expose_global_exports: bool,
|
||||||
|
pub expose_get_string_from_wasm: bool,
|
||||||
|
pub expose_pass_string_to_wasm: bool,
|
||||||
|
pub expose_token: bool,
|
||||||
pub exports: Vec<(String, String)>,
|
pub exports: Vec<(String, String)>,
|
||||||
|
pub classes: Vec<String>,
|
||||||
pub nodejs: bool,
|
pub nodejs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,6 +18,9 @@ impl Js {
|
|||||||
for f in program.free_functions.iter() {
|
for f in program.free_functions.iter() {
|
||||||
self.generate_free_function(f);
|
self.generate_free_function(f);
|
||||||
}
|
}
|
||||||
|
for s in program.structs.iter() {
|
||||||
|
self.generate_struct(s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_free_function(&mut self, func: &shared::Function) {
|
pub fn generate_free_function(&mut self, func: &shared::Function) {
|
||||||
@ -28,12 +35,76 @@ impl Js {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dst = format!("function {}(", func.name);
|
let ret = self.generate_function(&format!("function {}", func.name),
|
||||||
|
&func.name,
|
||||||
|
false,
|
||||||
|
&func.arguments,
|
||||||
|
func.ret.as_ref());
|
||||||
|
|
||||||
|
self.exports.push((func.name.clone(), ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_struct(&mut self, s: &shared::Struct) {
|
||||||
|
let mut dst = String::new();
|
||||||
|
self.expose_token = true;
|
||||||
|
self.expose_global_exports = true;
|
||||||
|
dst.push_str(&format!("
|
||||||
|
class {} {{
|
||||||
|
constructor(ptr, sym) {{
|
||||||
|
_checkToken(sym);
|
||||||
|
this.__wasmPtr = ptr;
|
||||||
|
}}
|
||||||
|
|
||||||
|
free() {{
|
||||||
|
const ptr = this.__wasmPtr;
|
||||||
|
this.__wasmPtr = 0;
|
||||||
|
exports.{}(ptr);
|
||||||
|
}}
|
||||||
|
", s.name, s.free_function()));
|
||||||
|
|
||||||
|
for function in s.functions.iter() {
|
||||||
|
let f = self.generate_function(
|
||||||
|
&format!("static {}", function.name),
|
||||||
|
&function.struct_function_export_name(&s.name),
|
||||||
|
false,
|
||||||
|
&function.arguments,
|
||||||
|
function.ret.as_ref(),
|
||||||
|
);
|
||||||
|
dst.push_str(&f);
|
||||||
|
dst.push_str("\n");
|
||||||
|
}
|
||||||
|
for method in s.methods.iter() {
|
||||||
|
let f = self.generate_function(
|
||||||
|
&format!("{}", method.function.name),
|
||||||
|
&method.function.struct_function_export_name(&s.name),
|
||||||
|
true,
|
||||||
|
&method.function.arguments,
|
||||||
|
method.function.ret.as_ref(),
|
||||||
|
);
|
||||||
|
dst.push_str(&f);
|
||||||
|
dst.push_str("\n");
|
||||||
|
}
|
||||||
|
dst.push_str("}\n");
|
||||||
|
self.classes.push(dst);
|
||||||
|
self.exports.push((s.name.clone(), s.name.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_function(&mut self,
|
||||||
|
name: &str,
|
||||||
|
wasm_name: &str,
|
||||||
|
is_method: bool,
|
||||||
|
arguments: &[shared::Type],
|
||||||
|
ret: Option<&shared::Type>) -> String {
|
||||||
|
let mut dst = format!("{}(", name);
|
||||||
let mut passed_args = String::new();
|
let mut passed_args = String::new();
|
||||||
let mut arg_conversions = String::new();
|
let mut arg_conversions = String::new();
|
||||||
let mut destructors = String::new();
|
let mut destructors = String::new();
|
||||||
|
|
||||||
for (i, arg) in func.arguments.iter().enumerate() {
|
if is_method {
|
||||||
|
passed_args.push_str("this.__wasmPtr");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, arg) in arguments.iter().enumerate() {
|
||||||
let name = format!("arg{}", i);
|
let name = format!("arg{}", i);
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
dst.push_str(", ");
|
dst.push_str(", ");
|
||||||
@ -50,23 +121,17 @@ impl Js {
|
|||||||
shared::Type::Number => pass(&name),
|
shared::Type::Number => pass(&name),
|
||||||
shared::Type::BorrowedStr |
|
shared::Type::BorrowedStr |
|
||||||
shared::Type::String => {
|
shared::Type::String => {
|
||||||
if self.nodejs {
|
self.expose_global_exports = true;
|
||||||
arg_conversions.push_str(&format!("\
|
self.expose_pass_string_to_wasm = true;
|
||||||
const buf{i} = Buffer.from({arg});
|
arg_conversions.push_str(&format!("\
|
||||||
const len{i} = buf{i}.length;
|
const [ptr{i}, len{i}] = passStringToWasm({arg});
|
||||||
const ptr{i} = exports.__wbindgen_malloc(len{i});
|
", i = i, arg = name));
|
||||||
let memory{i} = new Uint8Array(memory.buffer);
|
pass(&format!("ptr{}", i));
|
||||||
buf{i}.copy(memory{i}, ptr{i});
|
pass(&format!("len{}", i));
|
||||||
", i = i, arg = name));
|
if let shared::Type::BorrowedStr = *arg {
|
||||||
pass(&format!("ptr{}", i));
|
destructors.push_str(&format!("\n\
|
||||||
pass(&format!("len{}", i));
|
exports.__wbindgen_free(ptr{i}, len{i});\n\
|
||||||
if let shared::Type::BorrowedStr = *arg {
|
", i = i));
|
||||||
destructors.push_str(&format!("\n\
|
|
||||||
exports.__wbindgen_free(ptr{i}, len{i});\n\
|
|
||||||
", i = i));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("strings not implemented for browser");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shared::Type::ByRef(_) |
|
shared::Type::ByRef(_) |
|
||||||
@ -85,59 +150,107 @@ impl Js {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let convert_ret = match func.ret {
|
let convert_ret = match ret {
|
||||||
None |
|
None |
|
||||||
Some(shared::Type::Number) => format!("return ret;"),
|
Some(&shared::Type::Number) => format!("return ret;"),
|
||||||
Some(shared::Type::BorrowedStr) |
|
Some(&shared::Type::BorrowedStr) |
|
||||||
Some(shared::Type::ByMutRef(_)) |
|
Some(&shared::Type::ByMutRef(_)) |
|
||||||
Some(shared::Type::ByRef(_)) => panic!(),
|
Some(&shared::Type::ByRef(_)) => panic!(),
|
||||||
Some(shared::Type::ByValue(ref name)) => {
|
Some(&shared::Type::ByValue(ref name)) => {
|
||||||
format!("\
|
format!("\
|
||||||
return {name}.__wasmWrap(ret);
|
return new {name}(ret, token);
|
||||||
", name = name)
|
", name = name)
|
||||||
}
|
}
|
||||||
Some(shared::Type::String) => {
|
Some(&shared::Type::String) => {
|
||||||
if self.nodejs {
|
self.expose_get_string_from_wasm = true;
|
||||||
format!("\
|
format!("return getStringFromWasm(ret);")
|
||||||
const mem = new Uint8Array(memory.buffer);
|
|
||||||
const ptr = exports.__wbindgen_boxed_str_ptr(ret);
|
|
||||||
const len = exports.__wbindgen_boxed_str_len(ret);
|
|
||||||
const buf = Buffer.from(mem.slice(ptr, ptr + len));
|
|
||||||
const realRet = buf.toString();
|
|
||||||
exports.__wbindgen_boxed_str_free(ret);
|
|
||||||
return realRet;
|
|
||||||
")
|
|
||||||
} else {
|
|
||||||
panic!("strings not implemented for browser");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
dst.push_str(") {\n ");
|
dst.push_str(") {\n ");
|
||||||
dst.push_str(&arg_conversions);
|
dst.push_str(&arg_conversions);
|
||||||
dst.push_str(&format!("\
|
self.expose_global_exports = true;
|
||||||
try {{
|
if destructors.len() == 0 {
|
||||||
|
dst.push_str(&format!("\
|
||||||
const ret = exports.{f}({passed});
|
const ret = exports.{f}({passed});
|
||||||
{convert_ret}
|
{convert_ret}
|
||||||
}} finally {{
|
", f = wasm_name, passed = passed_args, convert_ret = convert_ret));
|
||||||
{destructors}
|
} else {
|
||||||
}}
|
dst.push_str(&format!("\
|
||||||
", f = func.name, passed = passed_args, destructors = destructors,
|
try {{
|
||||||
convert_ret = convert_ret));
|
const ret = exports.{f}({passed});
|
||||||
dst.push_str("};");
|
{convert_ret}
|
||||||
|
}} finally {{
|
||||||
self.exports.push((func.name.clone(), dst));
|
{destructors}
|
||||||
|
}}
|
||||||
|
", f = wasm_name, passed = passed_args, destructors = destructors,
|
||||||
|
convert_ret = convert_ret));
|
||||||
|
}
|
||||||
|
dst.push_str("}");
|
||||||
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
let mut globals = String::new();
|
let mut globals = String::new();
|
||||||
if self.expose_global_memory {
|
if self.expose_global_memory ||
|
||||||
|
self.expose_pass_string_to_wasm ||
|
||||||
|
self.expose_get_string_from_wasm
|
||||||
|
{
|
||||||
globals.push_str("const memory = obj.instance.exports.memory;\n");
|
globals.push_str("const memory = obj.instance.exports.memory;\n");
|
||||||
}
|
}
|
||||||
if self.expose_global_exports {
|
if self.expose_global_exports ||
|
||||||
|
self.expose_pass_string_to_wasm ||
|
||||||
|
self.expose_get_string_from_wasm
|
||||||
|
{
|
||||||
globals.push_str("const exports = obj.instance.exports;\n");
|
globals.push_str("const exports = obj.instance.exports;\n");
|
||||||
}
|
}
|
||||||
|
if self.expose_token {
|
||||||
|
globals.push_str("\
|
||||||
|
const token = Symbol('foo');
|
||||||
|
function _checkToken(sym) {
|
||||||
|
if (token != sym)
|
||||||
|
throw new Error('cannot invoke `new` directly');
|
||||||
|
}
|
||||||
|
");
|
||||||
|
}
|
||||||
|
if self.expose_pass_string_to_wasm {
|
||||||
|
if self.nodejs {
|
||||||
|
globals.push_str("
|
||||||
|
function passStringToWasm(arg) {
|
||||||
|
const buf = Buffer.from(arg);
|
||||||
|
const len = buf.length;
|
||||||
|
const ptr = exports.__wbindgen_malloc(len);
|
||||||
|
let array = new Uint8Array(memory.buffer);
|
||||||
|
buf.copy(array, ptr);
|
||||||
|
return [ptr, len];
|
||||||
|
}
|
||||||
|
");
|
||||||
|
} else {
|
||||||
|
panic!("browser strings not implemented yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.expose_get_string_from_wasm {
|
||||||
|
if self.nodejs {
|
||||||
|
globals.push_str("
|
||||||
|
function getStringFromWasm(ptr) {
|
||||||
|
const mem = new Uint8Array(memory.buffer);
|
||||||
|
const data = exports.__wbindgen_boxed_str_ptr(ptr);
|
||||||
|
const len = exports.__wbindgen_boxed_str_len(ptr);
|
||||||
|
const buf = Buffer.from(mem.slice(data, data + len));
|
||||||
|
const ret = buf.toString();
|
||||||
|
exports.__wbindgen_boxed_str_free(ptr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
");
|
||||||
|
} else {
|
||||||
|
panic!("strings not implemented for browser");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut exports = String::new();
|
let mut exports = String::new();
|
||||||
|
for class in self.classes.iter() {
|
||||||
|
exports.push_str(class);
|
||||||
|
exports.push_str("\n");
|
||||||
|
}
|
||||||
for &(ref name, ref body) in self.exports.iter() {
|
for &(ref name, ref body) in self.exports.iter() {
|
||||||
exports.push_str("obj.");
|
exports.push_str("obj.");
|
||||||
exports.push_str(name);
|
exports.push_str(name);
|
||||||
@ -146,12 +259,12 @@ impl Js {
|
|||||||
exports.push_str(";\n");
|
exports.push_str(";\n");
|
||||||
}
|
}
|
||||||
format!("
|
format!("
|
||||||
const function xform(obj) {{
|
function xform(obj) {{
|
||||||
{}
|
{}
|
||||||
{}
|
{}
|
||||||
return obj;
|
return obj;
|
||||||
}}
|
}}
|
||||||
export const function instantiate(bytes, imports) {{
|
export function instantiate(bytes, imports) {{
|
||||||
return WebAssembly.instantiate(bytes, imports).then(xform);
|
return WebAssembly.instantiate(bytes, imports).then(xform);
|
||||||
}}
|
}}
|
||||||
", globals, exports)
|
", globals, exports)
|
||||||
|
@ -88,6 +88,7 @@ impl Object {
|
|||||||
|
|
||||||
pub fn generate_js(&self) -> String {
|
pub fn generate_js(&self) -> String {
|
||||||
let mut js = js::Js::default();
|
let mut js = js::Js::default();
|
||||||
|
js.nodejs = self.nodejs;
|
||||||
js.generate_program(&self.program);
|
js.generate_program(&self.program);
|
||||||
js.to_string()
|
js.to_string()
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ pub enum Type {
|
|||||||
|
|
||||||
pub struct Struct {
|
pub struct Struct {
|
||||||
pub name: syn::Ident,
|
pub name: syn::Ident,
|
||||||
pub ctor: Option<Function>,
|
|
||||||
pub methods: Vec<Method>,
|
pub methods: Vec<Method>,
|
||||||
pub functions: Vec<Function>,
|
pub functions: Vec<Function>,
|
||||||
}
|
}
|
||||||
@ -118,16 +117,30 @@ impl Function {
|
|||||||
Function { name: input.ident, arguments, ret }
|
Function { name: input.ident, arguments, ret }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export_name(&self) -> syn::Lit {
|
pub fn free_function_export_name(&self) -> syn::Lit {
|
||||||
|
let name = self.shared().free_function_export_name();
|
||||||
syn::Lit {
|
syn::Lit {
|
||||||
value: syn::LitKind::Other(Literal::string(self.name.sym.as_str())),
|
value: syn::LitKind::Other(Literal::string(&name)),
|
||||||
span: Default::default(),
|
span: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rust_symbol(&self) -> syn::Ident {
|
pub fn struct_function_export_name(&self, s: syn::Ident) -> syn::Lit {
|
||||||
let generated_name = format!("__wasm_bindgen_generated_{}",
|
let name = self.shared().struct_function_export_name(s.sym.as_str());
|
||||||
self.name.sym.as_str());
|
syn::Lit {
|
||||||
|
value: syn::LitKind::Other(Literal::string(&name)),
|
||||||
|
span: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rust_symbol(&self, namespace: Option<syn::Ident>) -> syn::Ident {
|
||||||
|
let mut generated_name = format!("__wasm_bindgen_generated");
|
||||||
|
if let Some(ns) = namespace {
|
||||||
|
generated_name.push_str("_");
|
||||||
|
generated_name.push_str(ns.sym.as_str());
|
||||||
|
}
|
||||||
|
generated_name.push_str("_");
|
||||||
|
generated_name.push_str(self.name.sym.as_str());
|
||||||
syn::Ident::from(generated_name)
|
syn::Ident::from(generated_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,12 +233,15 @@ impl Struct {
|
|||||||
pub fn from(s: &syn::ItemStruct) -> Struct {
|
pub fn from(s: &syn::ItemStruct) -> Struct {
|
||||||
Struct {
|
Struct {
|
||||||
name: s.ident,
|
name: s.ident,
|
||||||
ctor: None,
|
|
||||||
methods: Vec::new(),
|
methods: Vec::new(),
|
||||||
functions: Vec::new(),
|
functions: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn free_function(&self) -> syn::Ident {
|
||||||
|
syn::Ident::from(self.shared().free_function())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push_item(&mut self, item: &syn::ImplItem) {
|
pub fn push_item(&mut self, item: &syn::ImplItem) {
|
||||||
let method = match *item {
|
let method = match *item {
|
||||||
syn::ImplItem::Const(_) => panic!("const definitions aren't supported"),
|
syn::ImplItem::Const(_) => panic!("const definitions aren't supported"),
|
||||||
@ -299,7 +315,6 @@ impl Struct {
|
|||||||
pub fn shared(&self) -> shared::Struct {
|
pub fn shared(&self) -> shared::Struct {
|
||||||
shared::Struct {
|
shared::Struct {
|
||||||
name: self.name.to_string(),
|
name: self.name.to_string(),
|
||||||
ctor: self.ctor.as_ref().unwrap().shared(),
|
|
||||||
functions: self.functions.iter().map(|f| f.shared()).collect(),
|
functions: self.functions.iter().map(|f| f.shared()).collect(),
|
||||||
methods: self.methods.iter().map(|f| f.shared()).collect(),
|
methods: self.methods.iter().map(|f| f.shared()).collect(),
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
|||||||
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
|
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
|
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
|
||||||
CNT.fetch_add(1, Ordering::SeqCst));
|
CNT.fetch_add(1, Ordering::SeqCst));
|
||||||
|
let generated_static_name = syn::Ident::from(generated_static_name);
|
||||||
let mut generated_static = String::from("wbg:");
|
let mut generated_static = String::from("wbg:");
|
||||||
generated_static.push_str(&serde_json::to_string(&program.shared()).unwrap());
|
generated_static.push_str(&serde_json::to_string(&program.shared()).unwrap());
|
||||||
let generated_static_value = syn::Lit {
|
let generated_static_value = syn::Lit {
|
||||||
@ -75,22 +76,89 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
|||||||
*#generated_static_value;
|
*#generated_static_value;
|
||||||
}).to_tokens(&mut ret);
|
}).to_tokens(&mut ret);
|
||||||
|
|
||||||
|
// println!("{}", ret);
|
||||||
|
|
||||||
ret.into()
|
ret.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
|
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
|
||||||
let export_name = function.export_name();
|
bindgen(&function.free_function_export_name(),
|
||||||
let generated_name = function.rust_symbol();
|
function.rust_symbol(None),
|
||||||
|
Receiver::FreeFunction(function.name),
|
||||||
|
&function.arguments,
|
||||||
|
function.ret.as_ref(),
|
||||||
|
into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
|
||||||
|
for f in s.functions.iter() {
|
||||||
|
bindgen_struct_fn(s, f, into);
|
||||||
|
}
|
||||||
|
for f in s.methods.iter() {
|
||||||
|
bindgen_struct_method(s, f, into);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = &s.name;
|
||||||
|
let free_fn = s.free_function();
|
||||||
|
(quote! {
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern fn #free_fn(ptr: *mut ::std::cell::RefCell<#name>) {
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
drop(Box::from_raw(ptr));
|
||||||
|
}
|
||||||
|
}).to_tokens(into);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bindgen_struct_fn(s: &ast::Struct, f: &ast::Function, into: &mut Tokens) {
|
||||||
|
bindgen(&f.struct_function_export_name(s.name),
|
||||||
|
f.rust_symbol(Some(s.name)),
|
||||||
|
Receiver::StructFunction(s.name, f.name),
|
||||||
|
&f.arguments,
|
||||||
|
f.ret.as_ref(),
|
||||||
|
into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bindgen_struct_method(s: &ast::Struct, m: &ast::Method, into: &mut Tokens) {
|
||||||
|
bindgen(&m.function.struct_function_export_name(s.name),
|
||||||
|
m.function.rust_symbol(Some(s.name)),
|
||||||
|
Receiver::StructMethod(s.name, m.mutable, m.function.name),
|
||||||
|
&m.function.arguments,
|
||||||
|
m.function.ret.as_ref(),
|
||||||
|
into)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Receiver {
|
||||||
|
FreeFunction(syn::Ident),
|
||||||
|
StructFunction(syn::Ident, syn::Ident),
|
||||||
|
StructMethod(syn::Ident, bool, syn::Ident),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bindgen(export_name: &syn::Lit,
|
||||||
|
generated_name: syn::Ident,
|
||||||
|
receiver: Receiver,
|
||||||
|
arguments: &[ast::Type],
|
||||||
|
ret_type: Option<&ast::Type>,
|
||||||
|
into: &mut Tokens) {
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
let mut arg_conversions = vec![];
|
let mut arg_conversions = vec![];
|
||||||
let real_name = &function.name;
|
|
||||||
let mut converted_arguments = vec![];
|
let mut converted_arguments = vec![];
|
||||||
let ret = syn::Ident::from("_ret");
|
let ret = syn::Ident::from("_ret");
|
||||||
|
|
||||||
let mut malloc = false;
|
let mut malloc = false;
|
||||||
let mut boxed_str = false;
|
let mut boxed_str = false;
|
||||||
|
|
||||||
for (i, ty) in function.arguments.iter().enumerate() {
|
let mut offset = 0;
|
||||||
|
if let Receiver::StructMethod(class, _, _) = receiver {
|
||||||
|
args.push(quote! { me: *mut ::std::cell::RefCell<#class> });
|
||||||
|
arg_conversions.push(quote! {
|
||||||
|
assert!(!me.is_null());
|
||||||
|
let me = unsafe { &*me };
|
||||||
|
});
|
||||||
|
offset = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, ty) in arguments.iter().enumerate() {
|
||||||
|
let i = i + offset;
|
||||||
let ident = syn::Ident::from(format!("arg{}", i));
|
let ident = syn::Ident::from(format!("arg{}", i));
|
||||||
match *ty {
|
match *ty {
|
||||||
ast::Type::Integer(i) => {
|
ast::Type::Integer(i) => {
|
||||||
@ -153,23 +221,23 @@ fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
|
|||||||
}
|
}
|
||||||
let ret_ty;
|
let ret_ty;
|
||||||
let convert_ret;
|
let convert_ret;
|
||||||
match function.ret {
|
match ret_type {
|
||||||
Some(ast::Type::Integer(i)) => {
|
Some(&ast::Type::Integer(i)) => {
|
||||||
ret_ty = quote! { -> #i };
|
ret_ty = quote! { -> #i };
|
||||||
convert_ret = quote! { #ret };
|
convert_ret = quote! { #ret };
|
||||||
}
|
}
|
||||||
Some(ast::Type::BorrowedStr) => panic!("can't return a borrowed string"),
|
Some(&ast::Type::BorrowedStr) => panic!("can't return a borrowed string"),
|
||||||
Some(ast::Type::ByRef(_)) => panic!("can't return a borrowed ref"),
|
Some(&ast::Type::ByRef(_)) => panic!("can't return a borrowed ref"),
|
||||||
Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"),
|
Some(&ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"),
|
||||||
Some(ast::Type::String) => {
|
Some(&ast::Type::String) => {
|
||||||
boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst);
|
boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst);
|
||||||
ret_ty = quote! { -> *mut String };
|
ret_ty = quote! { -> *mut String };
|
||||||
convert_ret = quote! { Box::into_raw(Box::new(#ret)) };
|
convert_ret = quote! { Box::into_raw(Box::new(#ret)) };
|
||||||
}
|
}
|
||||||
Some(ast::Type::ByValue(name)) => {
|
Some(&ast::Type::ByValue(name)) => {
|
||||||
ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> };
|
ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> };
|
||||||
convert_ret = quote! {
|
convert_ret = quote! {
|
||||||
Box::into_raw(Box::new(::std::cell::RefCell<#ret>))
|
Box::into_raw(Box::new(::std::cell::RefCell::new(#ret)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -224,20 +292,38 @@ fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
|
|||||||
#malloc
|
#malloc
|
||||||
#boxed_str
|
#boxed_str
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
#[export_name = #export_name]
|
#[export_name = #export_name]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
pub extern fn #generated_name(#(#args),*) #ret_ty {
|
pub extern fn #generated_name(#(#args),*) #ret_ty {
|
||||||
#(#arg_conversions)*
|
#(#arg_conversions)*
|
||||||
let #ret = #real_name(#(#converted_arguments),*);
|
let #ret = #receiver(#(#converted_arguments),*);
|
||||||
#convert_ret
|
#convert_ret
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// println!("{}", tokens);
|
|
||||||
tokens.to_tokens(into);
|
tokens.to_tokens(into);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
|
impl ToTokens for Receiver {
|
||||||
if s.ctor.is_none() {
|
fn to_tokens(&self, tokens: &mut Tokens) {
|
||||||
panic!("struct `{}` needs a `new` function to construct it", s.name);
|
match *self {
|
||||||
|
Receiver::FreeFunction(name) => name.to_tokens(tokens),
|
||||||
|
Receiver::StructFunction(s, name) => {
|
||||||
|
s.to_tokens(tokens);
|
||||||
|
syn::tokens::Colon2::default().to_tokens(tokens);
|
||||||
|
name.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
Receiver::StructMethod(_, mutable, name) => {
|
||||||
|
syn::Ident::from("me").to_tokens(tokens);
|
||||||
|
syn::tokens::Dot::default().to_tokens(tokens);
|
||||||
|
if mutable {
|
||||||
|
syn::Ident::from("borrow_mut").to_tokens(tokens);
|
||||||
|
} else {
|
||||||
|
syn::Ident::from("borrow").to_tokens(tokens);
|
||||||
|
}
|
||||||
|
tokens.append_delimited("(", Default::default(), |_| ());
|
||||||
|
syn::tokens::Dot::default().to_tokens(tokens);
|
||||||
|
name.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ pub struct Program {
|
|||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Struct {
|
pub struct Struct {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub ctor: Function,
|
|
||||||
pub functions: Vec<Function>,
|
pub functions: Vec<Function>,
|
||||||
pub methods: Vec<Method>,
|
pub methods: Vec<Method>,
|
||||||
}
|
}
|
||||||
@ -28,6 +27,33 @@ pub struct Function {
|
|||||||
pub ret: Option<Type>,
|
pub ret: Option<Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Struct {
|
||||||
|
pub fn free_function(&self) -> String {
|
||||||
|
let mut name = format!("__wbindgen_");
|
||||||
|
name.extend(self.name
|
||||||
|
.chars()
|
||||||
|
.flat_map(|s| s.to_lowercase()));
|
||||||
|
name.push_str("_free");
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
pub fn free_function_export_name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn struct_function_export_name(&self, struct_: &str) -> String {
|
||||||
|
let mut name = struct_
|
||||||
|
.chars()
|
||||||
|
.flat_map(|s| s.to_lowercase())
|
||||||
|
.collect::<String>();
|
||||||
|
name.push_str("_");
|
||||||
|
name.push_str(&self.name);
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
Number,
|
Number,
|
||||||
|
@ -21,7 +21,7 @@ fn simple() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_contents(a: u32) -> Foo {
|
pub fn with_contents(a: u32) -> Foo {
|
||||||
Foo::with_contents(a)
|
Foo { contents: a }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, amt: u32) -> u32 {
|
pub fn add(&mut self, amt: u32) -> u32 {
|
||||||
@ -35,15 +35,72 @@ fn simple() {
|
|||||||
import * as assert from "assert";
|
import * as assert from "assert";
|
||||||
|
|
||||||
export function test(wasm) {
|
export function test(wasm) {
|
||||||
const r = new wasm.Foo();
|
const r = wasm.Foo.new();
|
||||||
assert.strictEqual(r.add(0), 0);
|
assert.strictEqual(r.add(0), 0);
|
||||||
assert.strictEqual(r.add(1), 1);
|
assert.strictEqual(r.add(1), 1);
|
||||||
assert.strictEqual(r.add(1), 2);
|
assert.strictEqual(r.add(1), 2);
|
||||||
|
r.free();
|
||||||
|
|
||||||
const r2 = wasm.Foo.with_contents(10);
|
const r2 = wasm.Foo.with_contents(10);
|
||||||
assert.strictEqual(r.add(1), 11);
|
assert.strictEqual(r2.add(1), 11);
|
||||||
assert.strictEqual(r.add(2), 13);
|
assert.strictEqual(r2.add(2), 13);
|
||||||
assert.strictEqual(r.add(3), 16);
|
assert.strictEqual(r2.add(3), 16);
|
||||||
|
r2.free();
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strings() {
|
||||||
|
test_support::project()
|
||||||
|
.file("src/lib.rs", r#"
|
||||||
|
#![feature(proc_macro)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
wasm_bindgen! {
|
||||||
|
pub struct Foo {
|
||||||
|
name: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Bar {
|
||||||
|
contents: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Foo {
|
||||||
|
pub fn new() -> Foo {
|
||||||
|
Foo { name: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, amt: u32) {
|
||||||
|
self.name = amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bar(&self, mix: &str) -> Bar {
|
||||||
|
Bar { contents: format!("foo-{}-{}", mix, self.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bar {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
self.contents.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#)
|
||||||
|
.file("test.js", r#"
|
||||||
|
import * as assert from "assert";
|
||||||
|
|
||||||
|
export function test(wasm) {
|
||||||
|
const r = wasm.Foo.new();
|
||||||
|
r.set(3);
|
||||||
|
let bar = r.bar('baz');
|
||||||
|
r.free();
|
||||||
|
assert.strictEqual(bar.name(), "foo-baz-3");
|
||||||
|
bar.free();
|
||||||
}
|
}
|
||||||
"#)
|
"#)
|
||||||
.test();
|
.test();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user