mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-14 13:31:22 +00:00
Start adding support for classes
This commit is contained in:
159
crates/wasm-bindgen-cli-support/src/js.rs
Normal file
159
crates/wasm-bindgen-cli-support/src/js.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use shared;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Js {
|
||||
pub expose_global_memory: bool,
|
||||
pub expose_global_exports: bool,
|
||||
pub exports: Vec<(String, String)>,
|
||||
pub nodejs: bool,
|
||||
}
|
||||
|
||||
impl Js {
|
||||
|
||||
pub fn generate_program(&mut self, program: &shared::Program) {
|
||||
for f in program.free_functions.iter() {
|
||||
self.generate_free_function(f);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_free_function(&mut self, func: &shared::Function) {
|
||||
let simple = func.arguments.iter().all(|t| t.is_number()) &&
|
||||
func.ret.as_ref().map(|t| t.is_number()).unwrap_or(true);
|
||||
|
||||
if simple {
|
||||
self.exports.push((
|
||||
func.name.clone(),
|
||||
format!("obj.instance.exports.{}", func.name),
|
||||
));
|
||||
return
|
||||
}
|
||||
|
||||
let mut dst = format!("function {}(", func.name);
|
||||
let mut passed_args = String::new();
|
||||
let mut arg_conversions = String::new();
|
||||
let mut destructors = String::new();
|
||||
|
||||
for (i, arg) in func.arguments.iter().enumerate() {
|
||||
let name = format!("arg{}", i);
|
||||
if i > 0 {
|
||||
dst.push_str(", ");
|
||||
}
|
||||
dst.push_str(&name);
|
||||
|
||||
let mut pass = |arg: &str| {
|
||||
if passed_args.len() > 0 {
|
||||
passed_args.push_str(", ");
|
||||
}
|
||||
passed_args.push_str(arg);
|
||||
};
|
||||
match *arg {
|
||||
shared::Type::Number => pass(&name),
|
||||
shared::Type::BorrowedStr |
|
||||
shared::Type::String => {
|
||||
if self.nodejs {
|
||||
arg_conversions.push_str(&format!("\
|
||||
const buf{i} = Buffer.from({arg});
|
||||
const len{i} = buf{i}.length;
|
||||
const ptr{i} = exports.__wbindgen_malloc(len{i});
|
||||
let memory{i} = new Uint8Array(memory.buffer);
|
||||
buf{i}.copy(memory{i}, ptr{i});
|
||||
", i = i, arg = name));
|
||||
pass(&format!("ptr{}", i));
|
||||
pass(&format!("len{}", i));
|
||||
if let shared::Type::BorrowedStr = *arg {
|
||||
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::ByMutRef(_) => {
|
||||
arg_conversions.push_str(&format!("\
|
||||
const ptr{i} = {arg}.__wasmPtr;
|
||||
", i = i, arg = name));
|
||||
pass(&format!("ptr{}", i));
|
||||
}
|
||||
shared::Type::ByValue(_) => {
|
||||
arg_conversions.push_str(&format!("\
|
||||
const ptr{i} = {arg}.__wasmPtr;
|
||||
{arg}.__wasmPtr = 0;
|
||||
", i = i, arg = name));
|
||||
pass(&format!("ptr{}", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
let convert_ret = match func.ret {
|
||||
None |
|
||||
Some(shared::Type::Number) => format!("return ret;"),
|
||||
Some(shared::Type::BorrowedStr) |
|
||||
Some(shared::Type::ByMutRef(_)) |
|
||||
Some(shared::Type::ByRef(_)) => panic!(),
|
||||
Some(shared::Type::ByValue(ref name)) => {
|
||||
format!("\
|
||||
return {name}.__wasmWrap(ret);
|
||||
", name = name)
|
||||
}
|
||||
Some(shared::Type::String) => {
|
||||
if self.nodejs {
|
||||
format!("\
|
||||
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(&arg_conversions);
|
||||
dst.push_str(&format!("\
|
||||
try {{
|
||||
const ret = exports.{f}({passed});
|
||||
{convert_ret}
|
||||
}} finally {{
|
||||
{destructors}
|
||||
}}
|
||||
", f = func.name, passed = passed_args, destructors = destructors,
|
||||
convert_ret = convert_ret));
|
||||
dst.push_str("};");
|
||||
|
||||
self.exports.push((func.name.clone(), dst));
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut globals = String::new();
|
||||
if self.expose_global_memory {
|
||||
globals.push_str("const memory = obj.instance.exports.memory;\n");
|
||||
}
|
||||
if self.expose_global_exports {
|
||||
globals.push_str("const exports = obj.instance.exports;\n");
|
||||
}
|
||||
|
||||
let mut exports = String::new();
|
||||
for &(ref name, ref body) in self.exports.iter() {
|
||||
exports.push_str("obj.");
|
||||
exports.push_str(name);
|
||||
exports.push_str(" = ");
|
||||
exports.push_str(body);
|
||||
exports.push_str(";\n");
|
||||
}
|
||||
format!("
|
||||
const function xform(obj) {{
|
||||
{}
|
||||
{}
|
||||
return obj;
|
||||
}}
|
||||
export const function instantiate(bytes, imports) {{
|
||||
return WebAssembly.instantiate(bytes, imports).then(xform);
|
||||
}}
|
||||
", globals, exports)
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ use std::io::Write;
|
||||
use failure::{Error, ResultExt};
|
||||
use parity_wasm::elements::*;
|
||||
|
||||
mod js;
|
||||
|
||||
pub struct Bindgen {
|
||||
path: Option<PathBuf>,
|
||||
nodejs: bool,
|
||||
@ -18,7 +20,7 @@ pub struct Bindgen {
|
||||
|
||||
pub struct Object {
|
||||
module: Module,
|
||||
items: Vec<shared::Function>,
|
||||
program: shared::Program,
|
||||
nodejs: bool,
|
||||
}
|
||||
|
||||
@ -48,10 +50,10 @@ impl Bindgen {
|
||||
let mut module = parity_wasm::deserialize_file(input).map_err(|e| {
|
||||
format_err!("{:?}", e)
|
||||
})?;
|
||||
let items = extract_items(&mut module);
|
||||
let program = extract_program(&mut module);
|
||||
Ok(Object {
|
||||
module,
|
||||
items,
|
||||
program,
|
||||
nodejs: self.nodejs,
|
||||
})
|
||||
}
|
||||
@ -85,117 +87,13 @@ impl Object {
|
||||
}
|
||||
|
||||
pub fn generate_js(&self) -> String {
|
||||
let mut set_exports = String::new();
|
||||
for func in self.items.iter() {
|
||||
self.add_export(func, &mut set_exports);
|
||||
}
|
||||
|
||||
format!("\
|
||||
const xform = (obj) => {{
|
||||
const exports = obj.instance.exports;
|
||||
const memory = obj.instance.exports.memory;
|
||||
{set_exports}
|
||||
return obj;
|
||||
}};
|
||||
export const instantiate = (bytes, imports) => {{
|
||||
return WebAssembly.instantiate(bytes, imports).then(xform);
|
||||
}};
|
||||
",
|
||||
set_exports = set_exports,
|
||||
)
|
||||
}
|
||||
|
||||
fn add_export(&self, func: &shared::Function, dst: &mut String) {
|
||||
let simple = func.arguments.iter().all(|t| t.is_number()) &&
|
||||
func.ret.as_ref().map(|t| t.is_number()).unwrap_or(true);
|
||||
|
||||
if simple {
|
||||
dst.push_str(&format!("\n\
|
||||
obj.{f} = obj.instance.exports.{f};\
|
||||
", f = func.name));
|
||||
return
|
||||
}
|
||||
|
||||
dst.push_str(&format!("\n obj.{f} = function {f}(",
|
||||
f = func.name));
|
||||
let mut passed_args = String::new();
|
||||
let mut arg_conversions = String::new();
|
||||
let mut destructors = String::new();
|
||||
|
||||
for (i, arg) in func.arguments.iter().enumerate() {
|
||||
let name = format!("arg{}", i);
|
||||
if i > 0 {
|
||||
dst.push_str(", ");
|
||||
}
|
||||
dst.push_str(&name);
|
||||
|
||||
let mut pass = |arg: &str| {
|
||||
if passed_args.len() > 0 {
|
||||
passed_args.push_str(", ");
|
||||
}
|
||||
passed_args.push_str(arg);
|
||||
};
|
||||
match *arg {
|
||||
shared::Type::Number => pass(&name),
|
||||
shared::Type::BorrowedStr |
|
||||
shared::Type::String => {
|
||||
if self.nodejs {
|
||||
arg_conversions.push_str(&format!("\
|
||||
const buf{i} = Buffer.from({arg});
|
||||
const len{i} = buf{i}.length;
|
||||
const ptr{i} = exports.__wbindgen_malloc(len{i});
|
||||
let memory{i} = new Uint8Array(memory.buffer);
|
||||
buf{i}.copy(memory{i}, ptr{i});
|
||||
", i = i, arg = name));
|
||||
pass(&format!("ptr{}", i));
|
||||
pass(&format!("len{}", i));
|
||||
if let shared::Type::BorrowedStr = *arg {
|
||||
destructors.push_str(&format!("\n\
|
||||
exports.__wbindgen_free(ptr{i}, len{i});\n\
|
||||
", i = i));
|
||||
}
|
||||
} else {
|
||||
panic!("strings not implemented for browser");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let convert_ret = match func.ret {
|
||||
None |
|
||||
Some(shared::Type::Number) => format!("return ret;"),
|
||||
Some(shared::Type::BorrowedStr) => panic!(),
|
||||
Some(shared::Type::String) => {
|
||||
if self.nodejs {
|
||||
format!("\
|
||||
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(&arg_conversions);
|
||||
dst.push_str(&format!("\
|
||||
try {{
|
||||
const ret = exports.{f}({passed});
|
||||
{convert_ret}
|
||||
}} finally {{
|
||||
{destructors}
|
||||
}}
|
||||
", f = func.name, passed = passed_args, destructors = destructors,
|
||||
convert_ret = convert_ret));
|
||||
dst.push_str("};");
|
||||
let mut js = js::Js::default();
|
||||
js.generate_program(&self.program);
|
||||
js.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_items(module: &mut Module) -> Vec<shared::Function> {
|
||||
fn extract_program(module: &mut Module) -> shared::Program {
|
||||
let data = module.sections_mut()
|
||||
.iter_mut()
|
||||
.filter_map(|s| {
|
||||
@ -205,12 +103,16 @@ fn extract_items(module: &mut Module) -> Vec<shared::Function> {
|
||||
}
|
||||
})
|
||||
.next();
|
||||
|
||||
let mut ret = shared::Program {
|
||||
structs: Vec::new(),
|
||||
free_functions: Vec::new(),
|
||||
};
|
||||
let data = match data {
|
||||
Some(data) => data,
|
||||
None => return Vec::new(),
|
||||
None => return ret,
|
||||
};
|
||||
|
||||
let mut ret = Vec::new();
|
||||
for i in (0..data.entries().len()).rev() {
|
||||
{
|
||||
let value = data.entries()[i].value();
|
||||
@ -218,11 +120,13 @@ fn extract_items(module: &mut Module) -> Vec<shared::Function> {
|
||||
continue
|
||||
}
|
||||
let json = &value[4..];
|
||||
let func: shared::Function = match serde_json::from_slice(json) {
|
||||
let p = match serde_json::from_slice(json) {
|
||||
Ok(f) => f,
|
||||
Err(_) => continue,
|
||||
};
|
||||
ret.push(func);
|
||||
let shared::Program { structs, free_functions } = p;
|
||||
ret.structs.extend(structs);
|
||||
ret.free_functions.extend(free_functions);
|
||||
}
|
||||
data.entries_mut().remove(i);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
extern crate wasm_bindgen;
|
||||
extern crate wasm_bindgen_cli_support;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate docopt;
|
||||
@ -6,7 +6,7 @@ extern crate docopt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use docopt::Docopt;
|
||||
use wasm_bindgen::Bindgen;
|
||||
use wasm_bindgen_cli_support::Bindgen;
|
||||
|
||||
const USAGE: &'static str = "
|
||||
Generating JS bindings for a wasm file
|
||||
|
@ -1,9 +1,12 @@
|
||||
use proc_macro2::Literal;
|
||||
use quote::{ToTokens, Tokens};
|
||||
use serde_json;
|
||||
use syn;
|
||||
use wasm_bindgen_shared as shared;
|
||||
|
||||
pub struct Program {
|
||||
pub structs: Vec<Struct>,
|
||||
pub free_functions: Vec<Function>,
|
||||
}
|
||||
|
||||
pub struct Function {
|
||||
pub name: syn::Ident,
|
||||
pub arguments: Vec<Type>,
|
||||
@ -14,6 +17,58 @@ pub enum Type {
|
||||
Integer(syn::Ident),
|
||||
BorrowedStr,
|
||||
String,
|
||||
ByValue(syn::Ident),
|
||||
ByRef(syn::Ident),
|
||||
ByMutRef(syn::Ident),
|
||||
}
|
||||
|
||||
pub struct Struct {
|
||||
pub name: syn::Ident,
|
||||
pub ctor: Option<Function>,
|
||||
pub methods: Vec<Method>,
|
||||
pub functions: Vec<Function>,
|
||||
}
|
||||
|
||||
pub struct Method {
|
||||
pub mutable: bool,
|
||||
pub function: Function,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn push_impl(&mut self, item: &syn::ItemImpl) {
|
||||
match item.defaultness {
|
||||
syn::Defaultness::Final => {}
|
||||
_ => panic!("default impls are not supported"),
|
||||
}
|
||||
match item.unsafety {
|
||||
syn::Unsafety::Normal => {}
|
||||
_ => panic!("unsafe impls are not supported"),
|
||||
}
|
||||
if item.trait_.is_some() {
|
||||
panic!("trait impls are not supported");
|
||||
}
|
||||
if item.generics.params.len() > 0 {
|
||||
panic!("generic impls aren't supported");
|
||||
}
|
||||
let name = match Type::from(&item.self_ty) {
|
||||
Type::ByValue(ident) => ident,
|
||||
_ => panic!("unsupported self type in impl"),
|
||||
};
|
||||
let dst = self.structs
|
||||
.iter_mut()
|
||||
.find(|s| s.name == name)
|
||||
.expect(&format!("failed to definition of struct for impl of `{}`", name));
|
||||
for item in item.items.iter() {
|
||||
dst.push_item(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared(&self) -> shared::Program {
|
||||
shared::Program {
|
||||
structs: self.structs.iter().map(|s| s.shared()).collect(),
|
||||
free_functions: self.free_functions.iter().map(|s| s.shared()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Function {
|
||||
@ -36,6 +91,7 @@ impl Function {
|
||||
if !input.abi.is_none() {
|
||||
panic!("can only bindgen Rust ABI functions")
|
||||
}
|
||||
|
||||
if input.decl.variadic {
|
||||
panic!("can't bindgen variadic functions")
|
||||
}
|
||||
@ -59,11 +115,7 @@ impl Function {
|
||||
syn::ReturnType::Type(ref t, _) => Some(Type::from(t)),
|
||||
};
|
||||
|
||||
Function {
|
||||
name: input.ident,
|
||||
arguments,
|
||||
ret,
|
||||
}
|
||||
Function { name: input.ident, arguments, ret }
|
||||
}
|
||||
|
||||
pub fn export_name(&self) -> syn::Lit {
|
||||
@ -79,18 +131,6 @@ impl Function {
|
||||
syn::Ident::from(generated_name)
|
||||
}
|
||||
|
||||
pub fn generated_static_name(&self) -> syn::Ident {
|
||||
let generated_name = format!("__WASM_BINDGEN_GENERATED_{}",
|
||||
self.name.sym.as_str());
|
||||
syn::Ident::from(generated_name)
|
||||
}
|
||||
|
||||
pub fn generate_static(&self) -> Vec<u8> {
|
||||
let mut prefix = String::from("wbg:");
|
||||
prefix.push_str(&serde_json::to_string(&self.shared()).unwrap());
|
||||
prefix.into_bytes()
|
||||
}
|
||||
|
||||
fn shared(&self) -> shared::Function {
|
||||
shared::Function {
|
||||
name: self.name.sym.as_str().to_string(),
|
||||
@ -120,16 +160,22 @@ impl Type {
|
||||
if r.lifetime.is_some() {
|
||||
panic!("can't have lifetimes on references yet");
|
||||
}
|
||||
match r.ty.mutability {
|
||||
syn::Mutability::Immutable => {}
|
||||
_ => panic!("can't have mutable references yet"),
|
||||
}
|
||||
let mutable = match r.ty.mutability {
|
||||
syn::Mutability::Immutable => false,
|
||||
syn::Mutability::Mutable(_) => true,
|
||||
};
|
||||
match r.ty.ty {
|
||||
syn::Type::Path(syn::TypePath { qself: None, ref path }) => {
|
||||
let ident = extract_path_ident(path);
|
||||
match ident.sym.as_str() {
|
||||
"str" => Type::BorrowedStr,
|
||||
_ => panic!("unsupported reference type"),
|
||||
"str" => {
|
||||
if mutable {
|
||||
panic!("mutable strings not allowed");
|
||||
}
|
||||
Type::BorrowedStr
|
||||
}
|
||||
_ if mutable => Type::ByMutRef(ident),
|
||||
_ => Type::ByRef(ident),
|
||||
}
|
||||
}
|
||||
_ => panic!("unsupported reference type"),
|
||||
@ -151,7 +197,7 @@ impl Type {
|
||||
Type::Integer(ident)
|
||||
}
|
||||
"String" => Type::String,
|
||||
s => panic!("unsupported type: {}", s),
|
||||
_ => Type::ByValue(ident),
|
||||
}
|
||||
}
|
||||
_ => panic!("unsupported type"),
|
||||
@ -163,21 +209,108 @@ impl Type {
|
||||
Type::Integer(_) => shared::Type::Number,
|
||||
Type::BorrowedStr => shared::Type::BorrowedStr,
|
||||
Type::String => shared::Type::String,
|
||||
Type::ByValue(n) => shared::Type::ByValue(n.to_string()),
|
||||
Type::ByRef(n) => shared::Type::ByRef(n.to_string()),
|
||||
Type::ByMutRef(n) => shared::Type::ByMutRef(n.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Type {
|
||||
fn to_tokens(&self, tokens: &mut Tokens) {
|
||||
match *self {
|
||||
Type::Integer(i) => i.to_tokens(tokens),
|
||||
Type::String => {
|
||||
syn::Ident::from("String").to_tokens(tokens);
|
||||
impl Struct {
|
||||
pub fn from(s: &syn::ItemStruct) -> Struct {
|
||||
Struct {
|
||||
name: s.ident,
|
||||
ctor: None,
|
||||
methods: Vec::new(),
|
||||
functions: Vec::new(),
|
||||
}
|
||||
Type::BorrowedStr => {
|
||||
<Token![&]>::default().to_tokens(tokens);
|
||||
syn::Ident::from("str").to_tokens(tokens);
|
||||
}
|
||||
|
||||
pub fn push_item(&mut self, item: &syn::ImplItem) {
|
||||
let method = match *item {
|
||||
syn::ImplItem::Const(_) => panic!("const definitions aren't supported"),
|
||||
syn::ImplItem::Type(_) => panic!("type definitions in impls aren't supported"),
|
||||
syn::ImplItem::Method(ref m) => m,
|
||||
syn::ImplItem::Macro(_) => panic!("macros in impls aren't supported"),
|
||||
};
|
||||
match method.vis {
|
||||
syn::Visibility::Public(_) => {}
|
||||
_ => return,
|
||||
}
|
||||
match method.defaultness {
|
||||
syn::Defaultness::Final => {}
|
||||
_ => panic!("default methods are not supported"),
|
||||
}
|
||||
match method.sig.constness {
|
||||
syn::Constness::NotConst => {}
|
||||
_ => panic!("can only bindgen non-const functions"),
|
||||
}
|
||||
match method.sig.unsafety {
|
||||
syn::Unsafety::Normal => {}
|
||||
_ => panic!("can only bindgen safe functions"),
|
||||
}
|
||||
|
||||
if method.sig.decl.variadic {
|
||||
panic!("can't bindgen variadic functions")
|
||||
}
|
||||
if method.sig.decl.generics.params.len() > 0 {
|
||||
panic!("can't bindgen functions with lifetime or type parameters")
|
||||
}
|
||||
|
||||
let mut mutable = None;
|
||||
let arguments = method.sig.decl.inputs.iter()
|
||||
.map(|i| i.into_item())
|
||||
.filter_map(|arg| {
|
||||
match *arg {
|
||||
syn::FnArg::Captured(ref c) => Some(c),
|
||||
syn::FnArg::SelfValue(_) => {
|
||||
panic!("by-value `self` not yet supported");
|
||||
}
|
||||
syn::FnArg::SelfRef(ref a) => {
|
||||
assert!(mutable.is_none());
|
||||
mutable = Some(match a.mutbl {
|
||||
syn::Mutability::Mutable(_) => true,
|
||||
syn::Mutability::Immutable => false,
|
||||
});
|
||||
None
|
||||
}
|
||||
_ => panic!("arguments cannot be `self` or ignored"),
|
||||
}
|
||||
})
|
||||
.map(|arg| Type::from(&arg.ty))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ret = match method.sig.decl.output {
|
||||
syn::ReturnType::Default => None,
|
||||
syn::ReturnType::Type(ref t, _) => Some(Type::from(t)),
|
||||
};
|
||||
|
||||
let function = Function { name: method.sig.ident, arguments, ret };
|
||||
match mutable {
|
||||
Some(mutable) => {
|
||||
self.methods.push(Method { mutable, function });
|
||||
}
|
||||
None => {
|
||||
self.functions.push(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared(&self) -> shared::Struct {
|
||||
shared::Struct {
|
||||
name: self.name.to_string(),
|
||||
ctor: self.ctor.as_ref().unwrap().shared(),
|
||||
functions: self.functions.iter().map(|f| f.shared()).collect(),
|
||||
methods: self.methods.iter().map(|f| f.shared()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn shared(&self) -> shared::Method {
|
||||
shared::Method {
|
||||
mutable: self.mutable,
|
||||
function: self.function.shared(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate syn;
|
||||
#[macro_use]
|
||||
extern crate synom;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
@ -28,25 +27,63 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
||||
|
||||
let mut ret = Tokens::new();
|
||||
|
||||
let mut program = ast::Program {
|
||||
structs: Vec::new(),
|
||||
free_functions: Vec::new(),
|
||||
};
|
||||
|
||||
for item in file.items.iter() {
|
||||
item.to_tokens(&mut ret);
|
||||
match *item {
|
||||
syn::Item::Fn(ref f) => bindgen_fn(f, &mut ret),
|
||||
syn::Item::Fn(ref f) => {
|
||||
program.free_functions.push(ast::Function::from(f));
|
||||
}
|
||||
syn::Item::Struct(ref s) => {
|
||||
let s = ast::Struct::from(s);
|
||||
if program.structs.iter().any(|a| a.name == s.name) {
|
||||
panic!("redefinition of struct: {}", s.name);
|
||||
}
|
||||
program.structs.push(s);
|
||||
}
|
||||
syn::Item::Impl(ref s) => program.push_impl(s),
|
||||
_ => panic!("unexpected item in bindgen macro"),
|
||||
}
|
||||
}
|
||||
|
||||
for function in program.free_functions.iter() {
|
||||
bindgen_fn(function, &mut ret);
|
||||
}
|
||||
for s in program.structs.iter() {
|
||||
bindgen_struct(s, &mut ret);
|
||||
}
|
||||
|
||||
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
|
||||
CNT.fetch_add(1, Ordering::SeqCst));
|
||||
let mut generated_static = String::from("wbg:");
|
||||
generated_static.push_str(&serde_json::to_string(&program.shared()).unwrap());
|
||||
let generated_static_value = syn::Lit {
|
||||
value: syn::LitKind::Other(Literal::byte_string(generated_static.as_bytes())),
|
||||
span: Default::default(),
|
||||
};
|
||||
let generated_static_length = generated_static.len();
|
||||
|
||||
(quote! {
|
||||
#[no_mangle]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static #generated_static_name: [u8; #generated_static_length] =
|
||||
*#generated_static_value;
|
||||
}).to_tokens(&mut ret);
|
||||
|
||||
ret.into()
|
||||
}
|
||||
|
||||
fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) {
|
||||
let function = ast::Function::from(input);
|
||||
|
||||
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
|
||||
let export_name = function.export_name();
|
||||
let generated_name = function.rust_symbol();
|
||||
let mut args = vec![];
|
||||
let mut arg_conversions = vec![];
|
||||
let real_name = &input.ident;
|
||||
let real_name = &function.name;
|
||||
let mut converted_arguments = vec![];
|
||||
let ret = syn::Ident::from("_ret");
|
||||
|
||||
@ -85,6 +122,32 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) {
|
||||
};
|
||||
});
|
||||
}
|
||||
ast::Type::ByValue(name) => {
|
||||
args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> });
|
||||
arg_conversions.push(quote! {
|
||||
assert!(!#ident.is_null());
|
||||
let #ident = unsafe {
|
||||
(*#ident).borrow_mut();
|
||||
Box::from_raw(#ident).into_inner()
|
||||
};
|
||||
});
|
||||
}
|
||||
ast::Type::ByRef(name) => {
|
||||
args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> });
|
||||
arg_conversions.push(quote! {
|
||||
assert!(!#ident.is_null());
|
||||
let #ident = unsafe { (*#ident).borrow() };
|
||||
let #ident = &*#ident;
|
||||
});
|
||||
}
|
||||
ast::Type::ByMutRef(name) => {
|
||||
args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> });
|
||||
arg_conversions.push(quote! {
|
||||
assert!(!#ident.is_null());
|
||||
let mut #ident = unsafe { (*#ident).borrow_mut() };
|
||||
let #ident = &mut *#ident;
|
||||
});
|
||||
}
|
||||
}
|
||||
converted_arguments.push(quote! { #ident });
|
||||
}
|
||||
@ -96,25 +159,25 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) {
|
||||
convert_ret = quote! { #ret };
|
||||
}
|
||||
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::ByMutRef(_)) => panic!("can't return a borrowed ref"),
|
||||
Some(ast::Type::String) => {
|
||||
boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst);
|
||||
ret_ty = quote! { -> *mut String };
|
||||
convert_ret = quote! { Box::into_raw(Box::new(#ret)) };
|
||||
}
|
||||
Some(ast::Type::ByValue(name)) => {
|
||||
ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> };
|
||||
convert_ret = quote! {
|
||||
Box::into_raw(Box::new(::std::cell::RefCell<#ret>))
|
||||
};
|
||||
}
|
||||
None => {
|
||||
ret_ty = quote! {};
|
||||
convert_ret = quote! {};
|
||||
}
|
||||
}
|
||||
|
||||
let generated_static_name = function.generated_static_name();
|
||||
let generated_static = function.generate_static();
|
||||
let generated_static_value = syn::Lit {
|
||||
value: syn::LitKind::Other(Literal::byte_string(&generated_static)),
|
||||
span: Default::default(),
|
||||
};
|
||||
let generated_static_length = generated_static.len();
|
||||
|
||||
let malloc = if malloc {
|
||||
quote! {
|
||||
#[no_mangle]
|
||||
@ -161,11 +224,6 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) {
|
||||
#malloc
|
||||
#boxed_str
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static #generated_static_name: [u8; #generated_static_length] =
|
||||
*#generated_static_value;
|
||||
|
||||
#[no_mangle]
|
||||
#[export_name = #export_name]
|
||||
pub extern fn #generated_name(#(#args),*) #ret_ty {
|
||||
@ -177,3 +235,9 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) {
|
||||
// println!("{}", tokens);
|
||||
tokens.to_tokens(into);
|
||||
}
|
||||
|
||||
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
|
||||
if s.ctor.is_none() {
|
||||
panic!("struct `{}` needs a `new` function to construct it", s.name);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,26 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Program {
|
||||
pub structs: Vec<Struct>,
|
||||
pub free_functions: Vec<Function>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Struct {
|
||||
pub name: String,
|
||||
pub ctor: Function,
|
||||
pub functions: Vec<Function>,
|
||||
pub methods: Vec<Method>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Method {
|
||||
pub mutable: bool,
|
||||
pub function: Function,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Function {
|
||||
pub name: String,
|
||||
@ -13,6 +33,9 @@ pub enum Type {
|
||||
Number,
|
||||
BorrowedStr,
|
||||
String,
|
||||
ByValue(String),
|
||||
ByRef(String),
|
||||
ByMutRef(String),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
|
50
tests/classes.rs
Normal file
50
tests/classes.rs
Normal file
@ -0,0 +1,50 @@
|
||||
extern crate test_support;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
test_support::project()
|
||||
.file("src/lib.rs", r#"
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate wasm_bindgen;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
wasm_bindgen! {
|
||||
pub struct Foo {
|
||||
contents: u32,
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
pub fn new() -> Foo {
|
||||
Foo::with_contents(0)
|
||||
}
|
||||
|
||||
pub fn with_contents(a: u32) -> Foo {
|
||||
Foo::with_contents(a)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, amt: u32) -> u32 {
|
||||
self.contents += amt;
|
||||
self.contents
|
||||
}
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
import * as assert from "assert";
|
||||
|
||||
export function test(wasm) {
|
||||
const r = new wasm.Foo();
|
||||
assert.strictEqual(r.add(0), 0);
|
||||
assert.strictEqual(r.add(1), 1);
|
||||
assert.strictEqual(r.add(1), 2);
|
||||
|
||||
const r2 = wasm.Foo.with_contents(10);
|
||||
assert.strictEqual(r.add(1), 11);
|
||||
assert.strictEqual(r.add(2), 13);
|
||||
assert.strictEqual(r.add(3), 16);
|
||||
}
|
||||
"#)
|
||||
.test();
|
||||
}
|
Reference in New Issue
Block a user