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(),
free_functions: Vec::new(),
imports: Vec::new(),
imported_structs: Vec::new(),
};
let data = match data {
Some(data) => data,
@ -126,10 +127,11 @@ fn extract_program(module: &mut Module) -> shared::Program {
Ok(f) => f,
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.free_functions.extend(free_functions);
ret.imports.extend(imports);
ret.imported_structs.extend(imported_structs);
}
data.entries_mut().remove(i);
}

View File

@ -6,6 +6,7 @@ pub struct Program {
pub structs: Vec<Struct>,
pub free_functions: Vec<Function>,
pub imports: Vec<Import>,
pub imported_structs: Vec<ImportStruct>,
}
pub struct Function {
@ -16,11 +17,21 @@ pub struct Function {
pub struct Import {
pub module: String,
pub function: Function,
pub decl: Box<syn::FnDecl>,
pub function: ImportFunction,
}
pub struct ImportFunction {
pub ident: syn::Ident,
pub vis: syn::Visibility,
pub attrs: Vec<syn::Attribute>,
pub wasm_function: Function,
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 {
@ -103,23 +114,51 @@ impl Program {
})
.expect("must specify `#[wasm_module = ...]` for module to import from");
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 {
syn::ForeignItem::Fn(ref f) => f,
_ => panic!("only foreign functions allowed for now, not statics"),
};
self.imports.push(Import {
module: module.to_string(),
attrs: f.attrs.clone(),
vis: f.vis.clone(),
decl: f.decl.clone(),
let (wasm, mutable) = Function::from_decl(f.ident, &f.decl, allow_self);
let is_method = match mutable {
Some(false) => true,
None => false,
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(),
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(),
free_functions: self.free_functions.iter().map(|s| s.shared()).collect(),
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(),
}
}
@ -153,10 +195,12 @@ impl Function {
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() {
panic!("can't bindgen variadic functions")
}
@ -164,10 +208,19 @@ impl Function {
panic!("can't bindgen functions with lifetime or type parameters")
}
let mut mutable = None;
let arguments = decl.inputs.iter()
.map(|arg| {
.filter_map(|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"),
}
})
@ -179,7 +232,7 @@ impl Function {
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 {
@ -346,38 +399,9 @@ impl Struct {
panic!("can only bindgen safe functions");
}
if method.sig.decl.variadic.is_some() {
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()
.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 };
let (function, mutable) = Function::from_decl(method.sig.ident,
&method.sig.decl,
true);
match mutable {
Some(mutable) => {
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)]
#[macro_use]
extern crate syn;
#[macro_use]
extern crate quote;
@ -26,7 +27,7 @@ macro_rules! my_quote {
#[proc_macro]
pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
// 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");
let mut ret = Tokens::new();
@ -35,12 +36,21 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
structs: Vec::new(),
free_functions: Vec::new(),
imports: Vec::new(),
imported_structs: Vec::new(),
};
// Translate all input items into our own internal representation (the `ast`
// module). We'll be panicking here on anything that we can't process
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 {
syn::Item::Fn(ref f) => {
item.to_tokens(&mut ret);
@ -76,6 +86,9 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
for i in program.imports.iter() {
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
// 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) {
let vis = &import.vis;
let ret = &import.decl.output;
let name = &import.ident;
let fn_token = &import.decl.fn_token;
let arguments = &import.decl.inputs;
bindgen_import_function(&import.function,
&import.function.ident.to_string(),
Some(&import.module),
tokens);
}
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_arguments = Vec::new();
let mut arg_conversions = Vec::new();
let ret_ident = syn::Ident::from("_ret");
let names = import.decl.inputs
let names = import.rust_decl.inputs
.iter()
.map(|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 {
ast::Type::Integer(i) => {
abi_argument_names.push(name);
@ -507,7 +555,7 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
}
let abi_ret;
let convert_ret;
match import.function.ret {
match import.wasm_function.ret {
Some(ast::Type::Integer(i)) => {
abi_ret = my_quote! { #i };
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! {
#vis #fn_token #name(#arguments) #ret {
extern {
fn #name(#(#abi_arguments),*) -> #abi_ret;
fn #import_name(#(#abi_arguments),*) -> #abi_ret;
}
unsafe {
#(#arg_conversions)*
let #ret_ident = #name(#(#abi_argument_names),*);
let #ret_ident = #import_name(#(#abi_argument_names),*);
#convert_ret
}
}

View File

@ -6,6 +6,7 @@ pub struct Program {
pub structs: Vec<Struct>,
pub free_functions: Vec<Function>,
pub imports: Vec<(String, Function)>,
pub imported_structs: Vec<ImportStruct>,
}
#[derive(Serialize, Deserialize)]
@ -15,6 +16,13 @@ pub struct Struct {
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)]
pub struct Method {
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();
}