1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
extern crate proc_macro;

extern crate cpp_syn;
extern crate embed_js_common;
#[macro_use] extern crate quote;
use cpp_syn::{ TokenTree, Ident };

use proc_macro::TokenStream;
use embed_js_common::{ WasmPrimitiveType, JsMacArg };
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

fn unwrap_delimited(t: &TokenTree) -> &[TokenTree] {
    match *t {
        TokenTree::Delimited(ref delimited, _) => &delimited.tts,
        _ => panic!("tried to delimit-unwrap a token that isn't a Delimited")
    }
}

fn prim_to_ty(ty: WasmPrimitiveType) -> Ident {
    match ty {
        WasmPrimitiveType::I32 => Ident::from("i32"),
        WasmPrimitiveType::I64 => Ident::from("i64"),
        WasmPrimitiveType::F32 => Ident::from("f32"),
        WasmPrimitiveType::F64 => Ident::from("f64"),
    }
}

#[proc_macro_derive(EmbedJsDetail)]
pub fn embed_js(input: TokenStream) -> TokenStream {
    let s: String = input.to_string();
    let tokens = cpp_syn::parse_token_trees(&s).unwrap();
    let trimmed = unwrap_delimited(&unwrap_delimited(&unwrap_delimited(&tokens[4])[2])[2]);
    let js_mac = embed_js_common::parse_js_mac_string_source(trimmed, &s).expect("syntax error in js macro");
    let mut hasher = DefaultHasher::new();
    js_mac.hash(&mut hasher);
    let mac_hash = hasher.finish();
    let mut type_params = Vec::new();
    let mut arg_names = Vec::new();
    let mut arg_types = Vec::new();
    let mut extern_arg_types = Vec::new();
    let mut cast_fragments = Vec::new();
    let mut next_type_param = 0;
    for arg in js_mac.args {
        match arg {
            JsMacArg::Primitive(_, name, ty) => {
                arg_names.push(Ident::from(name));
                let ty = prim_to_ty(ty);
                let ty_ = ty.clone();
                extern_arg_types.push(quote!(#ty_));
                cast_fragments.push(quote!());
                arg_types.push(quote!(#ty));
            }
            JsMacArg::Ref(refs, _, name) => {
                let mutable = refs[0];
                let mutability = if mutable {
                    quote!(mut)
                } else {
                    quote!()
                };
                arg_names.push(Ident::from(name));
                let type_param = Ident::from(format!("T{}", next_type_param));
                {
                    let type_param_ = &type_param;
                    next_type_param += 1;
                    extern_arg_types.push(quote!(*mut u8));
                    arg_types.push(quote!(& #mutability #type_param_));
                    if mutable {
                        cast_fragments.push(quote!(as *mut #type_param_ as *mut u8));
                    } else {
                        cast_fragments.push(quote!(as *const #type_param_ as *const u8 as *mut u8));
                    }
                }
                type_params.push(type_param);
            }
        }
    }
    let type_params = if type_params.len() == 0 {
        quote!()
    } else {
        quote!(<#(#type_params),*>)
    };
    let arg_names = &arg_names;
    let arg_types = &arg_types;
    let extern_name = Ident::from(format!("__embed_js__{:x}", mac_hash));
    let result = match js_mac.ret {
        Some(ty) => {
            let ty = prim_to_ty(ty);
            quote! {
                impl EmbedJsStruct {
                    fn call #type_params(#(#arg_names: #arg_types),*) -> #ty {
                        extern {
                            fn #extern_name(#(#arg_names: #extern_arg_types),*) -> #ty;
                        }
                        unsafe { #extern_name(#(#arg_names #cast_fragments),*) }
                    }
                }
            }
        },
        None => {
            quote! {
                impl EmbedJsStruct {
                    fn call #type_params(#(#arg_names: #arg_types),*) {
                        extern {
                            fn #extern_name(#(#arg_names: #extern_arg_types),*);
                        }
                        unsafe { #extern_name(#(#arg_names #cast_fragments),*) }
                    }
                }
            }
        }
    };
    result.parse().unwrap()
}