extern crate arrayvec;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate proc_macro_hack;
#[macro_use]
extern crate quote;
extern crate syn;
extern crate wasm_wrapper_gen_shared;
use failure::{Error, ResultExt};
use wasm_wrapper_gen_shared::{extract_func_info, get_argument_types, get_ret_type,
transform_macro_input_to_items, SupportedArgumentType,
SupportedRetType, TransformedRustIdent};
#[derive(Debug, Clone)]
struct ConstructedArgIdent {
base: &'static str,
number_suffix: u32,
suffixes: arrayvec::ArrayVec<[&'static str; 4]>,
}
impl ConstructedArgIdent {
fn new(base: &'static str, number_suffix: u32) -> Self {
ConstructedArgIdent {
base,
number_suffix,
suffixes: arrayvec::ArrayVec::new(),
}
}
fn with_suffix(&self, suffix: &'static str) -> Self {
let mut cloned = self.clone(); cloned.suffixes.push(suffix);
cloned
}
}
impl quote::ToTokens for ConstructedArgIdent {
fn to_tokens(&self, tokens: &mut quote::Tokens) {
let mut ident = format!("{}{}", self.base, self.number_suffix);
for suffix in &self.suffixes {
ident.push_str(suffix);
}
tokens.append(ident);
}
}
proc_macro_item_impl! {
pub fn __js_fn_impl(input: &str) -> String {
match process_all_functions(input) {
Ok(v) => v,
Err(e) => {
panic!("js_fn macro failed: {}", e);
}
}
}
}
fn process_all_functions(input: &str) -> Result<String, Error> {
let token_trees = syn::parse_token_trees(input)
.map_err(|e| format_err!("failed to parse macro input as an item: {}", e))?;
let ast = transform_macro_input_to_items(token_trees)?;
let mut full_out = quote::Tokens::new();
for item in &ast {
let output = process_item(item)
.with_context(|e| format!("failed to process function '{:?}': {}", item, e))?;
full_out.append(output);
}
Ok(full_out.to_string())
}
fn process_item(item: &syn::Item) -> Result<quote::Tokens, Error> {
let (item, decl, block) = extract_func_info(item)?;
let out = generate_function_wrapper(item, decl, block)?;
Ok(out)
}
fn generate_function_wrapper(
item: &syn::Item,
decl: &syn::FnDecl,
code: &syn::Block,
) -> Result<quote::Tokens, Error> {
let callable_body = generate_callable_body(item, decl, code)?;
let argument_types = get_argument_types(decl)?;
let ret_ty = get_ret_type(decl)?;
let argument_names = (0..argument_types.len() as u32)
.map(|index| ConstructedArgIdent::new("__arg", index))
.collect::<Vec<_>>();
let mut function_body = quote::Tokens::new();
for (ty, arg_name) in argument_types.iter().zip(&argument_names) {
function_body.append(setup_for_argument(&arg_name, ty)?);
}
let mut arg_names_as_argument_list = quote::Tokens::new();
for arg_name in &argument_names {
arg_names_as_argument_list.append(quote! { #arg_name, });
}
function_body.append(quote! {
let result: #ret_ty = (#callable_body)(#arg_names_as_argument_list);
});
function_body.append(return_handling(&ret_ty)?);
let func_ident = TransformedRustIdent::new(&item.ident);
let mut real_arguments_list = quote::Tokens::new();
for (ty, arg_name) in argument_types.iter().zip(&argument_names) {
expand_argument_into(arg_name, ty, &mut real_arguments_list)?;
}
let ret_def = WrittenReturnType(ret_ty);
let full_definition = quote! {
#[no_mangle]
#[doc(hidden)]
pub extern "C" fn #func_ident (#real_arguments_list) #ret_def {
#function_body
}
};
Ok(full_definition)
}
fn expand_argument_into(
arg_name: &ConstructedArgIdent,
type_type: &SupportedArgumentType,
tokens: &mut quote::Tokens,
) -> Result<(), Error> {
match *type_type {
SupportedArgumentType::IntegerSliceRef(int_ty) => {
let ptr_arg_name = arg_name.with_suffix("_ptr");
let length_arg_name = arg_name.with_suffix("_len");
tokens.append(quote! {
#ptr_arg_name: *const #int_ty,
#length_arg_name: usize,
});
}
SupportedArgumentType::IntegerSliceMutRef(int_ty)
| SupportedArgumentType::IntegerVec(int_ty) => {
let ptr_arg_name = arg_name.with_suffix("_ptr");
let length_arg_name = arg_name.with_suffix("_len");
tokens.append(quote! {
#ptr_arg_name: *mut #int_ty,
#length_arg_name: usize,
});
}
SupportedArgumentType::Integer(int_ty) => tokens.append(quote! {
#arg_name: #int_ty,
}),
}
Ok(())
}
struct WrittenReturnType(SupportedRetType);
impl quote::ToTokens for WrittenReturnType {
fn to_tokens(&self, tokens: &mut quote::Tokens) {
match self.0 {
SupportedRetType::Unit => (),
SupportedRetType::Integer(int_ty) => {
tokens.append(quote! { -> #int_ty });
}
SupportedRetType::IntegerVec(_) => {
tokens.append(quote! { -> *const usize });
}
}
}
}
fn setup_for_argument(
arg_name: &ConstructedArgIdent,
ty: &SupportedArgumentType,
) -> Result<quote::Tokens, Error> {
let tokens = match *ty {
SupportedArgumentType::IntegerSliceRef(int_ty) => {
let ptr_arg_name = arg_name.with_suffix("_ptr");
let length_arg_name = arg_name.with_suffix("_len");
quote! {
let #arg_name: &[#int_ty] = unsafe {
::std::slice::from_raw_parts(#ptr_arg_name, #length_arg_name)
};
}
}
SupportedArgumentType::IntegerSliceMutRef(int_ty) => {
let ptr_arg_name = arg_name.with_suffix("_ptr");
let length_arg_name = arg_name.with_suffix("_len");
quote! {
let #arg_name: &mut [#int_ty] = unsafe {
::std::slice::from_raw_parts_mut(#ptr_arg_name, #length_arg_name)
};
}
}
SupportedArgumentType::IntegerVec(int_ty) => {
let ptr_arg_name = arg_name.with_suffix("_ptr");
let length_arg_name = arg_name.with_suffix("_len");
quote! {
let #arg_name: Vec<#int_ty> = unsafe {
::std::vec::Vec::from_raw_parts(#ptr_arg_name,
#length_arg_name, #length_arg_name)
};
}
}
SupportedArgumentType::Integer(_) => quote::Tokens::new(), };
Ok(tokens)
}
fn return_handling(ty: &SupportedRetType) -> Result<quote::Tokens, Error> {
let tokens = match *ty {
SupportedRetType::Unit | SupportedRetType::Integer(_) => quote! { result },
SupportedRetType::IntegerVec(int_ty) => {
quote! {
{
let result_ptr = result.as_slice().as_ptr() as *mut #int_ty;
let result_len = result.len();
let result_cap = result.capacity();
let to_return = Box::new([result_ptr as usize, result_len, result_cap]);
::std::mem::forget(result);
::std::boxed::Box::into_raw(to_return) as *const usize
}
}
}
};
Ok(tokens)
}
fn generate_callable_body(
_item: &syn::Item,
decl: &syn::FnDecl,
code: &syn::Block,
) -> Result<quote::Tokens, Error> {
if let Some(statement) = code.stmts.first() {
if let syn::Stmt::Expr(ref inner_expr) = *statement {
if let syn::ExprKind::Path(_, _) = inner_expr.node {
return Ok(quote! {
(#inner_expr)
});
}
}
}
let mut arguments = quote::Tokens::new();
for input in &decl.inputs {
arguments.append(quote! {
#input,
});
}
Ok(quote! {
(|#arguments| #code )
})
}