1 use base_x;
2 use serde_json;
3 use syn;
4 use proc_macro::TokenStream;
5 use proc_macro2::{self, Span};
6 
7 #[derive(Clone, Serialize, Deserialize, Debug)]
8 enum TypeMetadata {
9     I32,
10     F64,
11     Custom {
12         name: Option< String >,
13         conversion_fn: String
14     }
15 }
16 
17 #[derive(Clone, Serialize, Deserialize, Debug)]
18 struct ArgMetadata {
19     name: String,
20     ty: TypeMetadata
21 }
22 
23 #[derive(Clone, Serialize, Deserialize, Debug)]
24 struct ExportMetadata {
25     name: String,
26     args: Vec< ArgMetadata >,
27     result: Option< TypeMetadata >
28 }
29 
30 // This is a base62 encoding which consists of only alpha-numeric characters.
31 // Generated with: (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a).join("")
32 const ENCODING_BASE: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
33 
match_shallow_path( path: &syn::Path ) -> Option< String >34 fn match_shallow_path( path: &syn::Path ) -> Option< String > {
35     if path.leading_colon.is_some() || path.segments.len() != 1 {
36         return None;
37     }
38 
39     let segment = &path.segments[ 0 ];
40     match &segment.arguments {
41         &syn::PathArguments::None => {},
42         _ => return None
43     }
44 
45     Some( format!( "{}", segment.ident ) )
46 }
47 
match_type( ty: &syn::Type ) -> ExportType48 fn match_type( ty: &syn::Type ) -> ExportType {
49     match ty {
50         &syn::Type::Reference( ref ty ) => {
51             assert!( ty.mutability.is_none(), "`mut` bindings are not supported" );
52             match *ty.elem {
53                 syn::Type::Path( ref path ) if match_shallow_path( &path.path ).map( |path| path == "str" ).unwrap_or( false ) => {
54                     ExportType::StrRef
55                 },
56                 syn::Type::Slice( ref slice ) => {
57                     ExportType::Slice( (*slice.elem).clone() )
58                 },
59                 ref elem => ExportType::UnknownRef( elem.clone() )
60             }
61         },
62         &syn::Type::Path( ref path ) => {
63             let name = match match_shallow_path( &path.path ) {
64                 Some( name ) => name,
65                 None => return ExportType::Unknown( ty.clone() )
66             };
67 
68             match name.as_str() {
69                 "i32" => ExportType::I32,
70                 "f64" => ExportType::F64,
71                 _ => ExportType::Unknown( ty.clone() )
72             }
73         },
74         &syn::Type::Tuple( ref tuple ) if tuple.elems.is_empty() => ExportType::Unit,
75         _ => ExportType::Unknown( ty.clone() )
76     }
77 }
78 
79 enum ExportType {
80     Unit,
81     I32,
82     F64,
83     StrRef,
84     Slice( syn::Type ),
85     Unknown( syn::Type ),
86     UnknownRef( syn::Type )
87 }
88 
89 struct ExportArg {
90     ident: syn::Ident,
91     ty: ExportType
92 }
93 
94 struct Export {
95     ident: syn::Ident,
96     return_ty: ExportType,
97     args: Vec< ExportArg >
98 }
99 
process( exports: Vec< Export > ) -> proc_macro2::TokenStream100 fn process( exports: Vec< Export > ) -> proc_macro2::TokenStream {
101     let mut output = Vec::new();
102     for export in exports {
103         let export_result;
104         let export_result_conversion;
105         let export_result_metadata;
106         let mut export_args = Vec::new();
107         let mut export_args_metadata = Vec::new();
108         let mut export_args_idents = Vec::new();
109         let mut export_args_conversions = Vec::new();
110 
111         match export.return_ty {
112             ExportType::Unit => {
113                 export_result = quote! { () };
114                 export_result_conversion = quote! {};
115                 export_result_metadata = None;
116             },
117             ExportType::I32 => {
118                 export_result = quote! { i32 };
119                 export_result_conversion = quote! {};
120                 export_result_metadata = Some( TypeMetadata::I32 );
121             },
122             ExportType::F64 => {
123                 export_result = quote! { f64 };
124                 export_result_conversion = quote! {};
125                 export_result_metadata = Some( TypeMetadata::F64 );
126             },
127             ExportType::Unknown( _ ) |
128             ExportType::UnknownRef( _ ) |
129             ExportType::StrRef |
130             ExportType::Slice( _ ) => {
131                 // TODO: For known types generate more efficient serialization.
132                 export_result = quote! { () };
133                 // TODO: Figure out a better way to do this, if possible.
134                 export_result_conversion = quote! {
135                     let __result = ::stdweb::private::IntoNewtype::into_newtype( __result );
136                     let mut __arena_restore_point = ::stdweb::private::ArenaRestorePoint::new();
137                     let mut __result = Some( __result );
138                     let __result = ::stdweb::private::JsSerializeOwned::into_js_owned( &mut __result );
139                     let __result = &__result as *const _;
140                     __js_raw_asm!( "Module.STDWEB_PRIVATE.tmp = Module.STDWEB_PRIVATE.to_js( $0 );", __result );
141                     std::mem::drop( __arena_restore_point );
142                     let __result = ();
143                 };
144                 export_result_metadata = Some( TypeMetadata::Custom {
145                     name: None,
146                     conversion_fn: "Module.STDWEB_PRIVATE.acquire_tmp".to_owned()
147                 });
148             }
149         }
150 
151         for arg in &export.args {
152             let export_arg_ident = arg.ident.clone();
153             let export_arg_ty;
154             let export_arg_ty_metadata;
155             match arg.ty {
156                 ExportType::I32 => {
157                     export_arg_ty = quote! { i32 };
158                     export_arg_ty_metadata = TypeMetadata::I32;
159                 },
160                 ExportType::F64 => {
161                     export_arg_ty = quote! { f64 };
162                     export_arg_ty_metadata = TypeMetadata::F64;
163                 },
164                 ExportType::Unit => {
165                     panic!( "Receiving arguments of type `()` isn't supported" );
166                 },
167                 ExportType::Unknown( _ ) |
168                 ExportType::UnknownRef( _ ) |
169                 ExportType::StrRef |
170                 ExportType::Slice( _ ) => {
171                     // TODO: For known types generate more efficient serialization.
172                     export_arg_ty = quote! { i32 };
173                     export_args_conversions.push( quote! {
174                         let #export_arg_ident = {
175                             let pointer = #export_arg_ident as *mut ::stdweb::private::SerializedValue;
176                             unsafe {
177                                 let value = (&*pointer).deserialize();
178                                 ::stdweb::private::__web_free( pointer as *mut u8, std::mem::size_of::< ::stdweb::private::SerializedValue >() );
179                                 value
180                             }
181                         };
182                     });
183 
184                     export_arg_ty_metadata = TypeMetadata::Custom {
185                         name: None,
186                         conversion_fn: "Module.STDWEB_PRIVATE.prepare_any_arg".to_owned()
187                     };
188                 }
189             }
190 
191             export_args_idents.push( export_arg_ident.clone() );
192             export_args_metadata.push( ArgMetadata {
193                 name: format!( "{}", export_arg_ident ),
194                 ty: export_arg_ty_metadata
195             });
196             export_args.push( quote! {
197                 #export_arg_ident: #export_arg_ty
198             });
199         }
200 
201         for arg in &export.args {
202             let export_arg_ident = arg.ident.clone();
203             match arg.ty {
204                 // TODO: Throw a JS exception if `try_into` fails.
205                 ExportType::Unknown( ref ty ) => {
206                     let ty = ty.clone();
207                     export_args_conversions.push( quote! {
208                         let #export_arg_ident: #ty = #export_arg_ident.try_into().unwrap();
209                     });
210                 },
211                 ExportType::StrRef => {
212                     export_args_conversions.push( quote! {
213                         let #export_arg_ident: String = #export_arg_ident.try_into().unwrap();
214                         let #export_arg_ident: &str = &#export_arg_ident;
215                     });
216                 },
217                 ExportType::Slice( ref ty ) => {
218                     export_args_conversions.push( quote! {
219                         let #export_arg_ident: Vec< #ty > = #export_arg_ident.try_into().unwrap();
220                         let #export_arg_ident: &[#ty] = &#export_arg_ident;
221                     });
222                 },
223                 ExportType::UnknownRef( ref ty ) => {
224                     let ty = ty.clone();
225                     export_args_conversions.push( quote! {
226                         let #export_arg_ident: #ty = #export_arg_ident.try_into().unwrap();
227                         let #export_arg_ident = &#export_arg_ident;
228                     });
229                 },
230                 _ => {}
231             }
232         }
233 
234         let metadata = ExportMetadata {
235             name: format!( "{}", export.ident ),
236             args: export_args_metadata,
237             result: export_result_metadata
238         };
239 
240         let json_metadata = serde_json::to_string( &metadata ).unwrap();
241         let encoded_metadata = base_x::encode( ENCODING_BASE, json_metadata.as_bytes() );
242         let export_ident = syn::Ident::new( &format!( "__JS_EXPORT_{}", &encoded_metadata ), Span::call_site() );
243         let original_ident = export.ident.clone();
244 
245         output.push(
246             quote! {
247                 #[doc(hidden)]
248                 #[no_mangle]
249                 #[deny(private_no_mangle_fns)]
250                 #[allow(unused_imports)]
251                 pub extern fn #export_ident( #(#export_args),* ) -> #export_result {
252                     use ::stdweb::unstable::TryInto;
253                     #(#export_args_conversions)*
254                     let __result = #original_ident( #(#export_args_idents),* );
255                     #export_result_conversion
256                     return __result;
257                 }
258             }
259         );
260     }
261 
262     quote! { #(#output)* }
263 }
264 
into_export( decl: &syn::Signature ) -> Export265 fn into_export( decl: &syn::Signature ) -> Export {
266     let ident = decl.ident.clone();
267     assert!( decl.generics.lifetimes().next().is_none(), "Lifetimes are not yet not supported" );
268     assert!( decl.generics.type_params().next().is_none(), "Generics are not supported" );
269     assert!( decl.generics.where_clause.is_none(), "`where` clauses are not supported" );
270     assert!( decl.variadic.is_none(), "Variadic functions are not supported" );
271 
272     let return_ty = match &decl.output {
273         &syn::ReturnType::Default => ExportType::Unit,
274         &syn::ReturnType::Type( _, ref ty ) => match_type( ty )
275     };
276 
277     let mut args = Vec::new();
278     for (index, arg) in decl.inputs.iter().cloned().enumerate() {
279         match arg {
280             syn::FnArg::Receiver( .. ) => panic!( "`self` is not supported" ),
281             syn::FnArg::Typed( syn::PatType { pat, ty, .. } ) => {
282                 match *pat {
283                     syn::Pat::Wild( _ ) => {
284                         let ident = syn::Ident::new( &format!( "__arg_{}", index ), Span::call_site() );
285                         args.push( ExportArg {
286                             ident,
287                             ty: match_type( &ty )
288                         });
289                     },
290                     syn::Pat::Ident( pat ) => {
291                         assert!( pat.by_ref.is_none(), "`ref` bindings are not supported" );
292                         assert!( pat.mutability.is_none(), "`mut` bindings are not supported" );
293                         assert!( pat.subpat.is_none(), "Subpatterns are not supported" );
294 
295                         args.push( ExportArg {
296                             ident: pat.ident,
297                             ty: match_type( &ty )
298                         });
299                     },
300                     _ => panic!( "Argument patterns are not supported" )
301                 }
302             }
303         }
304     }
305 
306     Export {
307         ident,
308         return_ty,
309         args
310     }
311 }
312 
js_export( attrs: TokenStream, input: TokenStream ) -> TokenStream313 pub fn js_export( attrs: TokenStream, input: TokenStream ) -> TokenStream {
314     let input: proc_macro2::TokenStream = input.into();
315     let item: syn::Item = syn::parse2( input ).unwrap();
316     let mut exports = Vec::new();
317 
318     if !attrs.is_empty() {
319         panic!( "Extra attributes are not supported in `#[js_export]`!" );
320     }
321 
322     match item {
323         syn::Item::Fn( ref function ) => {
324             exports.push( into_export( &function.sig ) );
325         },
326         _ => panic!( "`#[js_export]` attached to an unsupported element!" )
327     }
328 
329     let generated = process( exports );
330     let output = quote! {
331         #item
332         #generated
333     };
334 
335     output.into()
336 }
337