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