This commit is contained in:
Alex Crichton 2018-02-05 14:24:25 -08:00
parent ec1c263480
commit 8f8da49dab
5 changed files with 251 additions and 62 deletions

View File

@ -109,6 +109,7 @@ fn extract_program(module: &mut Module) -> shared::Program {
structs: Vec::new(), structs: Vec::new(),
free_functions: Vec::new(), free_functions: Vec::new(),
imports: Vec::new(), imports: Vec::new(),
imported_structs: Vec::new(),
}; };
let data = match data { let data = match data {
Some(data) => data, Some(data) => data,
@ -126,10 +127,11 @@ fn extract_program(module: &mut Module) -> shared::Program {
Ok(f) => f, Ok(f) => f,
Err(_) => continue, Err(_) => continue,
}; };
let shared::Program { structs, free_functions, imports } = p; let shared::Program { structs, free_functions, imports, imported_structs } = p;
ret.structs.extend(structs); ret.structs.extend(structs);
ret.free_functions.extend(free_functions); ret.free_functions.extend(free_functions);
ret.imports.extend(imports); ret.imports.extend(imports);
ret.imported_structs.extend(imported_structs);
} }
data.entries_mut().remove(i); data.entries_mut().remove(i);
} }

View File

@ -6,6 +6,7 @@ pub struct Program {
pub structs: Vec<Struct>, pub structs: Vec<Struct>,
pub free_functions: Vec<Function>, pub free_functions: Vec<Function>,
pub imports: Vec<Import>, pub imports: Vec<Import>,
pub imported_structs: Vec<ImportStruct>,
} }
pub struct Function { pub struct Function {
@ -16,11 +17,21 @@ pub struct Function {
pub struct Import { pub struct Import {
pub module: String, pub module: String,
pub function: Function, pub function: ImportFunction,
pub decl: Box<syn::FnDecl>, }
pub struct ImportFunction {
pub ident: syn::Ident, pub ident: syn::Ident,
pub vis: syn::Visibility, pub wasm_function: Function,
pub attrs: Vec<syn::Attribute>, pub rust_decl: Box<syn::FnDecl>,
pub rust_vis: syn::Visibility,
pub rust_attrs: Vec<syn::Attribute>,
}
pub struct ImportStruct {
pub module: Option<String>,
pub name: syn::Ident,
pub functions: Vec<(bool, ImportFunction)>,
} }
pub enum Type { pub enum Type {
@ -103,23 +114,51 @@ impl Program {
}) })
.expect("must specify `#[wasm_module = ...]` for module to import from"); .expect("must specify `#[wasm_module = ...]` for module to import from");
for item in f.items.iter() { for item in f.items.iter() {
self.push_foreign_item(&module, item); let import = self.gen_foreign_item(item, false).0;
self.imports.push(Import {
module: module.clone(),
function: import,
});
} }
} }
pub fn push_foreign_item(&mut self, module: &str, f: &syn::ForeignItem) { pub fn gen_foreign_item(&mut self,
f: &syn::ForeignItem,
allow_self: bool) -> (ImportFunction, bool) {
let f = match *f { let f = match *f {
syn::ForeignItem::Fn(ref f) => f, syn::ForeignItem::Fn(ref f) => f,
_ => panic!("only foreign functions allowed for now, not statics"), _ => panic!("only foreign functions allowed for now, not statics"),
}; };
self.imports.push(Import { let (wasm, mutable) = Function::from_decl(f.ident, &f.decl, allow_self);
module: module.to_string(), let is_method = match mutable {
attrs: f.attrs.clone(), Some(false) => true,
vis: f.vis.clone(), None => false,
decl: f.decl.clone(), Some(true) => {
panic!("mutable self methods not allowed in extern structs");
}
};
(ImportFunction {
rust_attrs: f.attrs.clone(),
rust_vis: f.vis.clone(),
rust_decl: f.decl.clone(),
ident: f.ident.clone(), ident: f.ident.clone(),
function: Function::from_decl(f.ident, &f.decl), wasm_function: wasm,
}, is_method)
}
pub fn push_extern_class(&mut self, class: &ExternClass) {
let functions = class.functions.iter()
.map(|f| {
let (f, method) = self.gen_foreign_item(f, true);
(method, f)
})
.collect();
self.imported_structs.push(ImportStruct {
module: class.module.as_ref().map(|s| s.value()),
name: class.name,
functions,
}); });
} }
@ -128,7 +167,10 @@ impl Program {
structs: self.structs.iter().map(|s| s.shared()).collect(), structs: self.structs.iter().map(|s| s.shared()).collect(),
free_functions: self.free_functions.iter().map(|s| s.shared()).collect(), free_functions: self.free_functions.iter().map(|s| s.shared()).collect(),
imports: self.imports.iter() imports: self.imports.iter()
.map(|i| (i.module.clone(), i.function.shared())) .map(|i| (i.module.clone(), i.function.wasm_function.shared()))
.collect(),
imported_structs: self.imported_structs.iter()
.map(|i| i.shared())
.collect(), .collect(),
} }
} }
@ -153,10 +195,12 @@ impl Function {
panic!("can only bindgen Rust ABI functions") panic!("can only bindgen Rust ABI functions")
} }
Function::from_decl(input.ident, &input.decl) Function::from_decl(input.ident, &input.decl, false).0
} }
pub fn from_decl(name: syn::Ident, decl: &syn::FnDecl) -> Function { pub fn from_decl(name: syn::Ident,
decl: &syn::FnDecl,
allow_self: bool) -> (Function, Option<bool>) {
if decl.variadic.is_some() { if decl.variadic.is_some() {
panic!("can't bindgen variadic functions") panic!("can't bindgen variadic functions")
} }
@ -164,10 +208,19 @@ impl Function {
panic!("can't bindgen functions with lifetime or type parameters") panic!("can't bindgen functions with lifetime or type parameters")
} }
let mut mutable = None;
let arguments = decl.inputs.iter() let arguments = decl.inputs.iter()
.map(|arg| { .filter_map(|arg| {
match *arg { match *arg {
syn::FnArg::Captured(ref c) => c, syn::FnArg::Captured(ref c) => Some(c),
syn::FnArg::SelfValue(_) => {
panic!("by-value `self` not yet supported");
}
syn::FnArg::SelfRef(ref a) if allow_self => {
assert!(mutable.is_none());
mutable = Some(a.mutability.is_some());
None
}
_ => panic!("arguments cannot be `self` or ignored"), _ => panic!("arguments cannot be `self` or ignored"),
} }
}) })
@ -179,7 +232,7 @@ impl Function {
syn::ReturnType::Type(_, ref t) => Some(Type::from(t)), syn::ReturnType::Type(_, ref t) => Some(Type::from(t)),
}; };
Function { name, arguments, ret } (Function { name, arguments, ret }, mutable)
} }
pub fn free_function_export_name(&self) -> syn::LitStr { pub fn free_function_export_name(&self) -> syn::LitStr {
@ -346,38 +399,9 @@ impl Struct {
panic!("can only bindgen safe functions"); panic!("can only bindgen safe functions");
} }
if method.sig.decl.variadic.is_some() { let (function, mutable) = Function::from_decl(method.sig.ident,
panic!("can't bindgen variadic functions") &method.sig.decl,
} true);
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()
.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(a.mutability.is_some());
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 { match mutable {
Some(mutable) => { Some(mutable) => {
self.methods.push(Method { mutable, function }); self.methods.push(Method { mutable, function });
@ -405,3 +429,77 @@ impl Method {
} }
} }
} }
impl ImportStruct {
fn shared(&self) -> shared::ImportStruct {
shared::ImportStruct {
module: self.module.clone(),
name: self.name.to_string(),
functions: self.functions.iter()
.map(|&(b, ref f)| (b, f.wasm_function.shared()))
.collect(),
}
}
}
pub struct File {
pub items: Vec<MyItem>,
}
impl syn::synom::Synom for File {
named!(parse -> Self, map!(many0!(syn!(MyItem)), |items| File { items }));
}
pub enum MyItem {
Normal(syn::Item),
ExternClass(ExternClass),
}
impl syn::synom::Synom for MyItem {
named!(parse -> Self, alt!(
syn!(syn::Item) => { MyItem::Normal }
|
syn!(ExternClass) => { MyItem::ExternClass }
));
}
pub struct ExternClass {
name: syn::Ident,
module: Option<syn::LitStr>,
functions: Vec<syn::ForeignItem>,
}
impl syn::synom::Synom for ExternClass {
named!(parse -> Self, do_parse!(
module: option!(do_parse!(
punct!(#) >>
name: brackets!(do_parse!(
call!(term, "wasm_module") >>
punct!(=) >>
val: syn!(syn::LitStr) >>
(val)
)) >>
(name.1)
)) >>
keyword!(extern) >>
keyword!(struct) >>
name: syn!(syn::Ident) >>
items: braces!(many0!(syn!(syn::ForeignItem))) >>
(ExternClass {
name,
module,
functions: items.1,
})
));
}
fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str)
-> syn::synom::PResult<'a, ()>
{
if let Some((_span, term, next)) = cursor.term() {
if term.as_str() == name {
return Ok(((), next))
}
}
syn::parse_error()
}

View File

@ -1,5 +1,6 @@
#![feature(proc_macro)] #![feature(proc_macro)]
#[macro_use]
extern crate syn; extern crate syn;
#[macro_use] #[macro_use]
extern crate quote; extern crate quote;
@ -26,7 +27,7 @@ macro_rules! my_quote {
#[proc_macro] #[proc_macro]
pub fn wasm_bindgen(input: TokenStream) -> TokenStream { pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
// Parse the input as a list of Rust items, reusing the `syn::File` parser. // Parse the input as a list of Rust items, reusing the `syn::File` parser.
let file = syn::parse::<syn::File>(input) let file = syn::parse::<ast::File>(input)
.expect("expected a set of valid Rust items"); .expect("expected a set of valid Rust items");
let mut ret = Tokens::new(); let mut ret = Tokens::new();
@ -35,12 +36,21 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
structs: Vec::new(), structs: Vec::new(),
free_functions: Vec::new(), free_functions: Vec::new(),
imports: Vec::new(), imports: Vec::new(),
imported_structs: Vec::new(),
}; };
// Translate all input items into our own internal representation (the `ast` // Translate all input items into our own internal representation (the `ast`
// module). We'll be panicking here on anything that we can't process // module). We'll be panicking here on anything that we can't process
for item in file.items.iter() { for item in file.items.iter() {
let item = match *item {
ast::MyItem::ExternClass(ref c) => {
program.push_extern_class(c);
continue
}
ast::MyItem::Normal(ref item) => item,
};
match *item { match *item {
syn::Item::Fn(ref f) => { syn::Item::Fn(ref f) => {
item.to_tokens(&mut ret); item.to_tokens(&mut ret);
@ -76,6 +86,9 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
for i in program.imports.iter() { for i in program.imports.iter() {
bindgen_import(i, &mut ret); bindgen_import(i, &mut ret);
} }
for i in program.imported_structs.iter() {
bindgen_imported_struct(i, &mut ret);
}
// Finally generate a static which will eventually be what lives in a custom // Finally generate a static which will eventually be what lives in a custom
// section of the wasm executable. For now it's just a plain old static, but // section of the wasm executable. For now it's just a plain old static, but
@ -416,18 +429,53 @@ impl ToTokens for Receiver {
} }
fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
let vis = &import.vis; bindgen_import_function(&import.function,
let ret = &import.decl.output; &import.function.ident.to_string(),
let name = &import.ident; Some(&import.module),
let fn_token = &import.decl.fn_token; tokens);
let arguments = &import.decl.inputs; }
fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) {
let name = import.name;
(my_quote! {
pub struct #name {
obj: ::wasm_bindgen::JsObject,
}
}).to_tokens(tokens);
let mut methods = Tokens::new();
for &(_is_method, ref f) in import.functions.iter() {
let import_name = format!("__wbg_{}_{}", name, f.ident);
bindgen_import_function(f,
&import_name,
import.module.as_ref().map(|s| &**s),
&mut methods);
}
(my_quote! {
impl #name {
#methods
}
}).to_tokens(tokens);
}
fn bindgen_import_function(import: &ast::ImportFunction,
import_name: &str,
_import_module: Option<&str>,
tokens: &mut Tokens) {
let vis = &import.rust_vis;
let ret = &import.rust_decl.output;
let fn_token = &import.rust_decl.fn_token;
let arguments = &import.rust_decl.inputs;
let mut abi_argument_names = Vec::new(); let mut abi_argument_names = Vec::new();
let mut abi_arguments = Vec::new(); let mut abi_arguments = Vec::new();
let mut arg_conversions = Vec::new(); let mut arg_conversions = Vec::new();
let ret_ident = syn::Ident::from("_ret"); let ret_ident = syn::Ident::from("_ret");
let names = import.decl.inputs let names = import.rust_decl.inputs
.iter() .iter()
.map(|arg| { .map(|arg| {
match *arg { match *arg {
@ -449,7 +497,7 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
} }
}); });
for (ty, name) in import.function.arguments.iter().zip(names) { for (ty, name) in import.wasm_function.arguments.iter().zip(names) {
match *ty { match *ty {
ast::Type::Integer(i) => { ast::Type::Integer(i) => {
abi_argument_names.push(name); abi_argument_names.push(name);
@ -507,7 +555,7 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
} }
let abi_ret; let abi_ret;
let convert_ret; let convert_ret;
match import.function.ret { match import.wasm_function.ret {
Some(ast::Type::Integer(i)) => { Some(ast::Type::Integer(i)) => {
abi_ret = my_quote! { #i }; abi_ret = my_quote! { #i };
convert_ret = my_quote! { #ret_ident }; convert_ret = my_quote! { #ret_ident };
@ -542,14 +590,16 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
} }
} }
let name = import.ident;
let import_name = syn::Ident::from(import_name);
(quote! { (quote! {
#vis #fn_token #name(#arguments) #ret { #vis #fn_token #name(#arguments) #ret {
extern { extern {
fn #name(#(#abi_arguments),*) -> #abi_ret; fn #import_name(#(#abi_arguments),*) -> #abi_ret;
} }
unsafe { unsafe {
#(#arg_conversions)* #(#arg_conversions)*
let #ret_ident = #name(#(#abi_argument_names),*); let #ret_ident = #import_name(#(#abi_argument_names),*);
#convert_ret #convert_ret
} }
} }

View File

@ -6,6 +6,7 @@ pub struct Program {
pub structs: Vec<Struct>, pub structs: Vec<Struct>,
pub free_functions: Vec<Function>, pub free_functions: Vec<Function>,
pub imports: Vec<(String, Function)>, pub imports: Vec<(String, Function)>,
pub imported_structs: Vec<ImportStruct>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -15,6 +16,13 @@ pub struct Struct {
pub methods: Vec<Method>, pub methods: Vec<Method>,
} }
#[derive(Serialize, Deserialize)]
pub struct ImportStruct {
pub module: Option<String>,
pub name: String,
pub functions: Vec<(bool, Function)>,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Method { pub struct Method {
pub mutable: bool, pub mutable: bool,

31
tests/import-class.rs Normal file
View File

@ -0,0 +1,31 @@
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! {
extern struct Math {
fn random() -> f64;
}
pub fn get_random() -> f64 {
Math::random()
}
}
"#)
.file("test.ts", r#"
import * as wasm from "./out";
export function test() {
wasm.get_random();
}
"#)
.test();
}