stdweb-internal-macros-0.2.9/Cargo.toml.orig010064400017500001750000000013261354666775400172470ustar0000000000000000[package] name = "stdweb-internal-macros" version = "0.2.9" authors = ["Jan Bujak "] repository = "https://github.com/koute/stdweb" homepage = "https://github.com/koute/stdweb" documentation = "https://docs.rs/stdweb/*/stdweb/" license = "MIT/Apache-2.0" readme = "README.md" keywords = ["web", "asmjs", "webasm", "javascript"] categories = ["api-bindings", "gui", "web-programming"] description = "Internal procedural macros for the `stdweb` crate" [lib] proc-macro = true [dependencies] quote = "1" base-x = "0.2" serde = "1" serde_derive = "1" serde_json = "1" proc-macro2 = "1" sha1 = "0.6" [dependencies.syn] version = "1" default-features = false features = ["full", "parsing", "printing", "clone-impls"] stdweb-internal-macros-0.2.9/Cargo.toml0000644000000025710000000000000134720ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "stdweb-internal-macros" version = "0.2.9" authors = ["Jan Bujak "] description = "Internal procedural macros for the `stdweb` crate" homepage = "https://github.com/koute/stdweb" documentation = "https://docs.rs/stdweb/*/stdweb/" readme = "README.md" keywords = ["web", "asmjs", "webasm", "javascript"] categories = ["api-bindings", "gui", "web-programming"] license = "MIT/Apache-2.0" repository = "https://github.com/koute/stdweb" [lib] proc-macro = true [dependencies.base-x] version = "0.2" [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.serde] version = "1" [dependencies.serde_derive] version = "1" [dependencies.serde_json] version = "1" [dependencies.sha1] version = "0.6" [dependencies.syn] version = "1" features = ["full", "parsing", "printing", "clone-impls"] default-features = false stdweb-internal-macros-0.2.9/README.md010064400017500001750000000002431331624470200156070ustar0000000000000000# Procedural macros for the `stdweb` crate These aren't the droids you are looking for; please go to [stdweb] instead. [stdweb]: https://github.com/koute/stdweb stdweb-internal-macros-0.2.9/src/attr_hack.rs010064400017500001750000000027671354666412200174510ustar0000000000000000use syn::parse::{ParseStream, Parse, Result}; // Since currently producing an expression from a procedural // macro is not stable we use something like this to work around // this problem. pub struct AttrHack< T: Parse > { pub fn_name: syn::Ident, pub return_ty: Option< syn::Type >, pub inner: T } impl< T > Parse for AttrHack< T > where T: Parse { fn parse( input: ParseStream ) -> Result< Self > { input.parse::< Token![fn] >()?; let fn_name = input.parse::< syn::Ident >()?; #[allow(unused_variables)] let fn_args_input; parenthesized!( fn_args_input in input ); let return_ty = if input.peek( Token![->] ) { let _: Token![->] = input.parse()?; Some( input.parse()? ) } else { None }; let fn_body_input; braced!( fn_body_input in input ); let ident = fn_body_input.parse::< syn::Ident >()?; if ident == "call" { fn_body_input.parse::< Token![!] >()?; let inner; parenthesized!( inner in fn_body_input ); let inner = inner.parse::< T >()?; fn_body_input.parse::< Token![;] >()?; Ok( AttrHack { fn_name, return_ty, inner }) } else { let ident_str = ident.to_string(); Err( syn::Error::new_spanned( ident, format!( "unexpected ident '{}'", ident_str ) ) ) } } } stdweb-internal-macros-0.2.9/src/js_shim.rs010064400017500001750000000110021354666471100171270ustar0000000000000000use std::env; use std::path::PathBuf; use std::fs; use std::fmt::Write; use syn; use proc_macro2::{TokenStream, Span}; use sha1::Sha1; use utils::{Target, dummy_idents}; #[derive(Clone, Serialize, Deserialize, Debug)] struct Snippet { name: String, code: String, arg_count: usize } fn hash( string: &str ) -> String { let mut hasher = Sha1::new(); hasher.update( string.as_bytes() ); format!( "{}", hasher.digest() ) } fn database_path() -> PathBuf { let target_path = env::var_os( "CARGO_WEB_TARGET_DIR" ) .map( PathBuf::from ) .expect( "you need to use `cargo-web` to compile your project for the `wasm32-unknown-unknown` target" ); assert!( target_path.exists() ); target_path.join( ".cargo-web" ).join( "snippets" ) } fn output_snippet( snippet: &Snippet ) { let hash = hash( &snippet.name ); let directory = database_path().join( &hash[ 0..2 ] ); fs::create_dir_all( &directory ).expect( "failed to create a directory for the JS snippet database" ); let path = directory.join( format!( "{}.json", hash ) ); let blob: Vec< u8 > = serde_json::to_string( &snippet ).expect( "failed to convert the JS snipped to JSON" ).into_bytes(); if path.exists() { if let Ok( size ) = path.metadata().map( |metadata| metadata.len() ) { if size == blob.len() as u64 { return; } } } fs::write( path, blob ).expect( "failed to write a JS snippet" ); } pub fn js_shim_extern_code( target: Target, code: &str, arg_count: usize, return_ty: Option< syn::Type > ) -> (syn::Ident, TokenStream) { let snippet = Snippet { name: format!( "__cargo_web_snippet_{}", hash( code ) ), code: code.to_owned(), arg_count }; let has_return_value = return_ty.is_some(); let return_signature = if let Some( ty ) = return_ty { quote! { -> #ty } } else { quote! {} }; let shim_name = syn::Ident::new( &snippet.name, Span::call_site() ); let shim_args: Vec< _ > = dummy_idents( arg_count ).map( |name| quote! { #name: *const u8 } ).collect(); let shim_args_passthrough: Vec< _ > = dummy_idents( arg_count ).map( |name| quote! { #name } ).collect(); let output = match target { Target::Emscripten => { let code_bytes = syn::LitByteStr::new( format!( "{}\0", code ).as_str().as_bytes(), Span::call_site() ); let return_semicolon = if has_return_value { quote! {} } else { quote! { ; } }; quote! { const SNIPPET: &'static [u8] = #code_bytes; fn #shim_name( #(#shim_args),* ) #return_signature { extern "C" { pub fn emscripten_asm_const_int( code: *const u8, ... ) -> i32; } unsafe { emscripten_asm_const_int( SNIPPET as *const _ as *const u8, #(#shim_args_passthrough),* ) #return_semicolon } } } }, Target::NativeWebAssembly => { output_snippet( &snippet ); quote! { extern "C" { pub fn #shim_name( #(#shim_args),* ) #return_signature; } } }, Target::WasmBindgen => { let mut code_string = String::new(); write!( &mut code_string, "export function {}(", &snippet.name ).unwrap(); write!( &mut code_string, "Module" ).unwrap(); if arg_count != 0 { write!( &mut code_string, ", " ).unwrap(); } for nth in 0..arg_count { write!( &mut code_string, "${}", nth ).unwrap(); if nth + 1 != arg_count { write!( &mut code_string, ", " ).unwrap(); } } write!( &mut code_string, ") {{ {} }}", code ).unwrap(); let shim_name = &shim_name; let shim_args = &shim_args; quote! { use ::stdweb::private::wasm_bindgen::prelude::*; unsafe fn #shim_name( #(#shim_args),* ) #return_signature { #[wasm_bindgen(inline_js = #code_string)] extern "C" { pub fn #shim_name( module: JsValue, #(#shim_args),* ) #return_signature; } #shim_name( ::stdweb::private::get_module(), #(#shim_args_passthrough),* ) } } } }; (shim_name, output) } stdweb-internal-macros-0.2.9/src/js_stringify.rs010064400017500001750000000124231347032716600202110ustar0000000000000000use std::fmt::Write; use proc_macro2::{TokenTree, Delimiter}; use syn; use syn::buffer::Cursor; use syn::parse::{Result, ParseStream}; use quote::ToTokens; enum Chunk { Text( String ), Block( syn::Block ) } pub struct StringifiedCode { chunks: Vec< Chunk > } fn can_trim_whitespace_next_to( ch: char ) -> bool { ch.is_whitespace() || "=+-*/%<>&^|~?:{}()[];.,".contains( ch ) } impl StringifiedCode { fn push( &mut self, string: &str ) { match self.chunks.last_mut() { None | Some( Chunk::Block( .. ) ) => {}, Some( Chunk::Text( ref mut buffer ) ) => { let l = buffer.chars().last().unwrap_or( ' ' ); let r = string.chars().next().unwrap_or( ' ' ); if !can_trim_whitespace_next_to( l ) && !can_trim_whitespace_next_to( r ) { buffer.push( ' ' ); } buffer.push_str( string ); return; } } self.chunks.push( Chunk::Text( string.into() ) ); } fn push_block( &mut self, block: syn::Block ) { self.chunks.push( Chunk::Block( block ) ); } pub fn arg_count( &self ) -> usize { self.chunks.iter().filter( |chunk| match chunk { Chunk::Block( .. ) => true, _ => false } ).count() } pub fn code( &self, initial_placeholder_index: usize ) -> String { let capacity = self.chunks.iter().map( |chunk| match chunk { Chunk::Text( text ) => text.len(), Chunk::Block( .. ) => 4 } ).fold( 0, |sum, len| sum + len ); let mut counter = initial_placeholder_index; let mut output = String::with_capacity( capacity ); for chunk in &self.chunks { match chunk { Chunk::Text( text ) => output.push_str( text ), Chunk::Block( _ ) => { write!( output, "(${})", counter ).unwrap(); counter += 1; } } } output } } fn stringify< 'a >( mut cursor: Cursor< 'a >, output: &mut StringifiedCode ) -> Result< Cursor< 'a > > { while let Some( (tt, next) ) = cursor.token_tree() { cursor = match tt { TokenTree::Punct( ref punct ) if punct.as_char() == '@' && next.group( Delimiter::Brace ).is_some() => { let (tt, next_next) = next.token_tree().unwrap(); output.push_block( syn::parse2( tt.into_token_stream() )? ); next_next }, TokenTree::Group( ref group ) => { let (start, end) = match group.delimiter() { Delimiter::Brace => ("{", "}"), Delimiter::Bracket => ("[", "]"), Delimiter::Parenthesis => ("(", ")"), Delimiter::None => ("", "") }; output.push( start ); let inner = cursor.group( group.delimiter() ).unwrap().0; stringify( inner, output )?; output.push( end ); next }, _ => { let token = tt.to_string(); output.push( &token ); next } }; } Ok( cursor ) } impl syn::parse::Parse for StringifiedCode { fn parse( input: ParseStream ) -> Result< Self > { input.step( |cursor| { let mut output = StringifiedCode { chunks: Vec::new() }; let cursor = stringify( *cursor, &mut output )?; Ok( (output, cursor) ) }) } } #[cfg(test)] mod tests { use super::StringifiedCode; use proc_macro2::TokenStream; fn assert_stringify( input: TokenStream, initial_placeholder: usize, expected: &str ) { let snippet: StringifiedCode = syn::parse2( input ).unwrap(); assert_eq!( snippet.code( initial_placeholder ), expected ); } #[test] fn test_stringify() { assert_stringify( quote! { return thing; }, 0, "return thing;" ); assert_stringify( quote! { console.log }, 0, "console.log" ); assert_stringify( quote! { 1.0 }, 0, "1.0" ); assert_stringify( quote! { [ 1.0 ] }, 0, "[1.0]" ); assert_stringify( quote! { { 1.0 } }, 0, "{1.0}" ); assert_stringify( quote! { ( 1.0 ) }, 0, "(1.0)" ); assert_stringify( quote! { a b }, 0, "a b" ); assert_stringify( quote! { === }, 0, "===" ); assert_stringify( quote! { ++i }, 0, "++i" ); assert_stringify( quote! { i++ }, 0, "i++" ); assert_stringify( quote! { --i }, 0, "--i" ); assert_stringify( quote! { i-- }, 0, "i--" ); assert_stringify( quote! { return _.sum([1, 2]); }, 0, "return _.sum([1,2]);" ); assert_stringify( quote! { return $; }, 0, "return $;" ); assert_stringify( quote! { ( @{1} ); }, 0, "(($0));" ); assert_stringify( quote! { console.log( "Hello!", @{1234i32} ); }, 0, "console.log(\"Hello!\",($0));" ); assert_stringify( quote! { @{a}.fn( @{b} ); }, 0, "($0).fn(($1));" ); assert_stringify( quote! { @{a}.fn( @{b} ); }, 1, "($1).fn(($2));" ); } } stdweb-internal-macros-0.2.9/src/lib.rs010064400017500001750000000066521354665527400162640ustar0000000000000000#![recursion_limit="128"] extern crate proc_macro; extern crate proc_macro2; #[macro_use] extern crate syn; #[macro_use] extern crate quote; extern crate base_x; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; extern crate sha1; #[cfg(test)] mod testutils; mod utils; mod macro_js_export; mod macro_async_test; mod macro_js_raw; mod macro_js; mod attr_hack; mod js_stringify; mod js_shim; use utils::Target; fn emit( result: syn::parse::Result< proc_macro2::TokenStream > ) -> proc_macro::TokenStream { match result { Ok( stream ) => stream.into(), Err( error ) => proc_macro::TokenStream::from( error.to_compile_error() ) } } #[proc_macro_attribute] pub fn js_export( attrs: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { macro_js_export::js_export( attrs, input ) } #[proc_macro_attribute] pub fn async_test( attrs: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { macro_async_test::async_test( attrs, input ) } #[proc_macro] pub fn wasm32_unknown_unknown_js_raw( input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js_raw::js_raw( Target::NativeWebAssembly, input.into() ) ) } #[proc_macro_attribute] pub fn wasm32_unknown_unknown_js_raw_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js_raw::js_raw_attr( Target::NativeWebAssembly, input.into() ) ) } #[proc_macro_attribute] pub fn wasm32_unknown_unknown_js_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js::js_attr( Target::NativeWebAssembly, input.into(), false ) ) } #[proc_macro_attribute] pub fn wasm32_unknown_unknown_js_no_return_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js::js_attr( Target::NativeWebAssembly, input.into(), true ) ) } #[proc_macro] pub fn emscripten_js_raw( input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js_raw::js_raw( Target::Emscripten, input.into() ) ) } #[proc_macro_attribute] pub fn emscripten_js_raw_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js_raw::js_raw_attr( Target::Emscripten, input.into() ) ) } #[proc_macro_attribute] pub fn emscripten_js_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js::js_attr( Target::Emscripten, input.into(), false ) ) } #[proc_macro_attribute] pub fn emscripten_js_no_return_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js::js_attr( Target::Emscripten, input.into(), true ) ) } #[proc_macro_attribute] pub fn wasm_bindgen_js_raw_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js_raw::js_raw_attr( Target::WasmBindgen, input.into() ) ) } #[proc_macro_attribute] pub fn wasm_bindgen_js_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js::js_attr( Target::WasmBindgen, input.into(), false ) ) } #[proc_macro_attribute] pub fn wasm_bindgen_js_no_return_attr( _: proc_macro::TokenStream, input: proc_macro::TokenStream ) -> proc_macro::TokenStream { emit( macro_js::js_attr( Target::WasmBindgen, input.into(), true ) ) } stdweb-internal-macros-0.2.9/src/macro_async_test.rs010064400017500001750000000274331354025000100210200ustar0000000000000000use syn; use proc_macro::TokenStream; use proc_macro2::{self, Span}; #[cfg(test)] use testutils::assert_code_eq; fn check_return_type( return_type: &syn::ReturnType ) -> bool { match return_type { &syn::ReturnType::Default => true, &syn::ReturnType::Type( _, ref ty ) => { match **ty { syn::Type::Tuple( ref tuple ) if tuple.elems.is_empty() => true, _ => false } } } } enum TestKind { Simple { callback_ident: syn::Ident }, Fallible { callback_ident: syn::Ident, result_ty: syn::Type }, Synchronous } // TODO: There must be a cleaner way to do this. fn check_decl( decl: &syn::Signature ) -> TestKind { assert!( decl.generics.lifetimes().next().is_none(), "Lifetimes are yet not supported" ); assert!( decl.generics.where_clause.is_none(), "`where` clauses are not supported" ); assert!( decl.variadic.is_none(), "Variadic functions are not supported" ); if !check_return_type( &decl.output ) { panic!( "The function should not return anything!" ); } let mut type_params: Vec< _ > = decl.generics.type_params().collect(); if type_params.is_empty() && decl.inputs.is_empty() { // Exactly like a normal #[test]. // fn test_foo() {} return TestKind::Synchronous; } assert_eq!( type_params.len(), 1, "The function should have a single type parameter" ); let type_param = type_params.pop().unwrap(); assert!( type_param.attrs.is_empty(), "Type param attributes are not supported" ); assert!( type_param.default.is_none(), "Type param defaults are not supported" ); assert!( type_param.eq_token.is_none() ); assert_eq!( type_param.bounds.len(), 1, "The type param should have only one bound" ); let bound = match type_param.bounds[ 0 ].clone() { syn::TypeParamBound::Lifetime( .. ) => panic!( "Lifetime type param bounds are not supported" ), syn::TypeParamBound::Trait( bound ) => bound }; match bound.modifier { syn::TraitBoundModifier::None => {}, syn::TraitBoundModifier::Maybe( _ ) => panic!( "'?Trait' type bounds are not supported" ) } assert!( bound.lifetimes.is_none(), "Lifetimes in type param bounds are not supported" ); if !bound.path.leading_colon.is_none() || bound.path.segments.len() != 1 || bound.path.segments[ 0 ].ident != "FnOnce" { panic!( "Unsupported type bound" ); } enum Kind { Simple, Fallible { result_ty: syn::Type } } let kind: Kind = match bound.path.segments[ 0 ].arguments { syn::PathArguments::Parenthesized( syn::ParenthesizedGenericArguments { ref inputs, ref output, .. } ) => { match output { syn::ReturnType::Default => {}, _ => panic!( "Unsupported type bound" ) } let inputs: Vec< _ > = inputs.iter().collect(); match *inputs { [] => { // A test which can only succeed, or timeout. // fn test_foo< F: FnOnce() >( cb: F ) {} Kind::Simple }, [ syn::Type::Path( syn::TypePath { qself: None, path: syn::Path { leading_colon: None, segments } } ) ] if segments.len() == 1 => { let segment = &segments[ 0 ]; if segment.ident != "Result" { panic!( "Unsupported type bound" ); } match segment.arguments { syn::PathArguments::AngleBracketed( ref args ) => { if args.args.len() != 2 { panic!( "Unsupported type bound" ); } match args.args[ 0 ] { syn::GenericArgument::Type( syn::Type::Tuple( syn::TypeTuple { ref elems, .. } ) ) if elems.is_empty() => { // A test which can suceed, fail or timeout. // fn test_foo< F: FnOnce( Result< (), E > ) >( cb: F ) {} Kind::Fallible { result_ty: inputs[ 0 ].clone() } }, _ => panic!( "Unsupported type bound" ) } }, _ => panic!( "Unsupported type bound" ) } }, _ => panic!( "Unsupported type bound" ) } }, _ => panic!( "Unsupported type bound" ) }; if decl.inputs.len() != 1 { panic!( "Expected a function with a single argument!" ); } let arg = decl.inputs.last().unwrap(); match *arg { syn::FnArg::Receiver( .. ) => panic!( "`self` is not supported" ), syn::FnArg::Typed( syn::PatType { ref pat, ref ty, .. } ) => { match **pat { syn::Pat::Ident( ref pat ) => { assert!( pat.by_ref.is_none(), "`ref` bindings are not supported" ); assert!( pat.mutability.is_none(), "`mut` bindings are not supported" ); assert!( pat.subpat.is_none(), "Subpatterns are not supported" ); match **ty { syn::Type::Path( syn::TypePath { qself: None, path: syn::Path { leading_colon: None, ref segments } } ) if segments.len() == 1 => { if type_param.ident != segments[ 0 ].ident { panic!( "Unsupported argument type" ); } }, _ => panic!( "Unsupported argument type" ) } let callback_ident = pat.ident.clone(); match kind { Kind::Simple => TestKind::Simple { callback_ident }, Kind::Fallible { result_ty } => TestKind::Fallible { callback_ident, result_ty } } }, _ => panic!( "Argument patterns are not supported" ) } } } } fn async_test_impl( item: syn::Item ) -> proc_macro2::TokenStream { let (ident, block, test_kind) = match item { syn::Item::Fn( function ) => { let test_kind = check_decl( &function.sig ); (function.sig.ident.clone(), function.block, test_kind) }, _ => panic!( "`#[async_test]` attached to an unsupported element!" ) }; let inner; match test_kind { TestKind::Simple { callback_ident } => { let prelude = quote! { let #callback_ident = { let resolve = js!( return ASYNC_TEST_PRIVATE.resolve; ); move || { js!( @{resolve}(); ); } }; }; inner = quote! { #prelude #block }; }, TestKind::Fallible { callback_ident, result_ty } => { let prelude = quote! { let #callback_ident = { let resolve = js!( return ASYNC_TEST_PRIVATE.resolve; ); let reject = js!( return ASYNC_TEST_PRIVATE.reject; ); move |result: #result_ty| { match result { Ok(()) => js! { @{resolve}(); }, Err( error ) => js! { @{reject}(@{format!( "{:?}", error )}); } }; } }; }; inner = quote! { #prelude #block }; }, TestKind::Synchronous => { inner = quote! { (move || { #block })(); js! { ASYNC_TEST_PRIVATE.resolve(); }; } } } let symbol = syn::Ident::new( &format!( "__async_test__{}", ident ), Span::call_site() ); let output = quote! { #[cfg(test)] #[linkage = "external"] #[no_mangle] #[allow(dead_code)] #[allow(non_snake_case)] fn #symbol() { #inner } }; output } pub fn async_test( attrs: TokenStream, input: TokenStream ) -> TokenStream { if !attrs.is_empty() { panic!( "Extra attributes are not supported in `#[async_test]`!" ); } let input: proc_macro2::TokenStream = input.into(); let item: syn::Item = syn::parse2( input ).unwrap(); async_test_impl( item ).into() } #[test] fn test_async_test_simple() { let input = quote! { fn foobar< F: FnOnce() >( done: F ) { if true { done(); } } }; let expected = quote! { #[cfg(test)] #[linkage = "external"] #[no_mangle] #[allow(dead_code)] #[allow(non_snake_case)] fn __async_test__foobar() { let done = { let resolve = js ! ( return ASYNC_TEST_PRIVATE . resolve ; ); move || { js ! ( @ { resolve } ( ) ; ); } }; { if true { done(); } } } }; let output = async_test_impl( syn::parse2( input ).unwrap() ); assert_code_eq( output, expected ); } #[test] fn test_async_test_fallible() { let input = quote! { #[async_test] fn foobar< F: FnOnce( Result< (), i32 > ) >( done: F ) { done( Ok(()) ); } }; let expected = quote! { #[cfg(test)] #[linkage = "external"] #[no_mangle] #[allow(dead_code)] #[allow(non_snake_case)] fn __async_test__foobar() { let done = { let resolve = js ! ( return ASYNC_TEST_PRIVATE . resolve ; ); let reject = js ! ( return ASYNC_TEST_PRIVATE . reject ; ); move |result: Result<(), i32>| { match result { Ok(()) => js ! { @ { resolve } ( ) ; }, Err(error) => js ! { @ { reject } ( @ { format ! ( "{:?}" , error ) } ) ; } };} }; { done(Ok(())); } } }; let output = async_test_impl( syn::parse2( input ).unwrap() ); assert_code_eq( output, expected ); } #[test] fn test_async_test_synchronous() { let input = quote! { fn foobar() { body(); } }; let expected = quote! { #[cfg(test)] #[linkage = "external"] #[no_mangle] #[allow(dead_code)] #[allow(non_snake_case)] fn __async_test__foobar() { (move || {{ body(); }})(); js ! { ASYNC_TEST_PRIVATE . resolve ( ) ; }; } }; let output = async_test_impl( syn::parse2( input ).unwrap() ); assert_code_eq( output, expected ); } stdweb-internal-macros-0.2.9/src/macro_js.rs010064400017500001750000000037701354666356400173120ustar0000000000000000use std::fmt::Write; use syn; use syn::parse::Result; use proc_macro2::TokenStream; use attr_hack::AttrHack; use js_shim::js_shim_extern_code; use js_stringify::StringifiedCode; use utils::{Target, dummy_idents}; // TODO: Delete this once expression procedural macros are stable. pub fn js_attr( target: Target, input: TokenStream, outer_no_return: bool ) -> Result< TokenStream > { let wrapper: AttrHack< StringifiedCode > = syn::parse2( input )?; let wrapper_name = wrapper.fn_name; let snippet = wrapper.inner; let inner_no_return = outer_no_return || snippet.code( 0 ).contains( "return" ) == false; let inner_arg_count = snippet.arg_count() + if inner_no_return { 0 } else { 1 }; let outer_arg_count = snippet.arg_count() + if outer_no_return { 0 } else { 1 }; let initial_arg_index = if inner_no_return { 0 } else { 1 }; let mut code = snippet.code( initial_arg_index ); if !inner_no_return { code = format!( "Module.STDWEB_PRIVATE.from_js($0, (function(){{{}}})());", code ); } let mut prelude = String::new(); for nth in initial_arg_index..inner_arg_count { write!( prelude, "${} = Module.STDWEB_PRIVATE.to_js(${});", nth, nth ).unwrap(); } code = format!( "{}{}", prelude, code ); let (shim_name, shim) = js_shim_extern_code( target, &code, inner_arg_count, wrapper.return_ty ); let arg_names: Vec< _ > = dummy_idents( outer_arg_count ).collect(); let prototype_args = arg_names.clone().into_iter().map( |name| quote! { #name: *const u8 } ); let call_args: Vec< _ >; if inner_no_return && !outer_no_return { call_args = arg_names.into_iter().skip( 1 ).collect(); } else { call_args = arg_names.into_iter().collect(); } let call_args = call_args.into_iter().map( |name| quote! { #name } ); let output = quote! { fn #wrapper_name( #(#prototype_args),* ) { #shim unsafe { #shim_name( #(#call_args),* ) } } }; Ok( output ) } stdweb-internal-macros-0.2.9/src/macro_js_export.rs010064400017500001750000000302431354025000100206520ustar0000000000000000use base_x; use serde_json; use syn; use proc_macro::TokenStream; use proc_macro2::{self, Span}; #[derive(Clone, Serialize, Deserialize, Debug)] enum TypeMetadata { I32, F64, Custom { name: Option< String >, conversion_fn: String } } #[derive(Clone, Serialize, Deserialize, Debug)] struct ArgMetadata { name: String, ty: TypeMetadata } #[derive(Clone, Serialize, Deserialize, Debug)] struct ExportMetadata { name: String, args: Vec< ArgMetadata >, result: Option< TypeMetadata > } // This is a base62 encoding which consists of only alpha-numeric characters. // Generated with: (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a).join("") const ENCODING_BASE: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; fn match_shallow_path( path: &syn::Path ) -> Option< String > { if path.leading_colon.is_some() || path.segments.len() != 1 { return None; } let segment = &path.segments[ 0 ]; match &segment.arguments { &syn::PathArguments::None => {}, _ => return None } Some( format!( "{}", segment.ident ) ) } fn match_type( ty: &syn::Type ) -> ExportType { match ty { &syn::Type::Reference( ref ty ) => { assert!( ty.mutability.is_none(), "`mut` bindings are not supported" ); match *ty.elem { syn::Type::Path( ref path ) if match_shallow_path( &path.path ).map( |path| path == "str" ).unwrap_or( false ) => { ExportType::StrRef }, syn::Type::Slice( ref slice ) => { ExportType::Slice( (*slice.elem).clone() ) }, ref elem => ExportType::UnknownRef( elem.clone() ) } }, &syn::Type::Path( ref path ) => { let name = match match_shallow_path( &path.path ) { Some( name ) => name, None => return ExportType::Unknown( ty.clone() ) }; match name.as_str() { "i32" => ExportType::I32, "f64" => ExportType::F64, _ => ExportType::Unknown( ty.clone() ) } }, &syn::Type::Tuple( ref tuple ) if tuple.elems.is_empty() => ExportType::Unit, _ => ExportType::Unknown( ty.clone() ) } } enum ExportType { Unit, I32, F64, StrRef, Slice( syn::Type ), Unknown( syn::Type ), UnknownRef( syn::Type ) } struct ExportArg { ident: syn::Ident, ty: ExportType } struct Export { ident: syn::Ident, return_ty: ExportType, args: Vec< ExportArg > } fn process( exports: Vec< Export > ) -> proc_macro2::TokenStream { let mut output = Vec::new(); for export in exports { let export_result; let export_result_conversion; let export_result_metadata; let mut export_args = Vec::new(); let mut export_args_metadata = Vec::new(); let mut export_args_idents = Vec::new(); let mut export_args_conversions = Vec::new(); match export.return_ty { ExportType::Unit => { export_result = quote! { () }; export_result_conversion = quote! {}; export_result_metadata = None; }, ExportType::I32 => { export_result = quote! { i32 }; export_result_conversion = quote! {}; export_result_metadata = Some( TypeMetadata::I32 ); }, ExportType::F64 => { export_result = quote! { f64 }; export_result_conversion = quote! {}; export_result_metadata = Some( TypeMetadata::F64 ); }, ExportType::Unknown( _ ) | ExportType::UnknownRef( _ ) | ExportType::StrRef | ExportType::Slice( _ ) => { // TODO: For known types generate more efficient serialization. export_result = quote! { () }; // TODO: Figure out a better way to do this, if possible. export_result_conversion = quote! { let __result = ::stdweb::private::IntoNewtype::into_newtype( __result ); let mut __arena_restore_point = ::stdweb::private::ArenaRestorePoint::new(); let mut __result = Some( __result ); let __result = ::stdweb::private::JsSerializeOwned::into_js_owned( &mut __result ); let __result = &__result as *const _; __js_raw_asm!( "Module.STDWEB_PRIVATE.tmp = Module.STDWEB_PRIVATE.to_js( $0 );", __result ); std::mem::drop( __arena_restore_point ); let __result = (); }; export_result_metadata = Some( TypeMetadata::Custom { name: None, conversion_fn: "Module.STDWEB_PRIVATE.acquire_tmp".to_owned() }); } } for arg in &export.args { let export_arg_ident = arg.ident.clone(); let export_arg_ty; let export_arg_ty_metadata; match arg.ty { ExportType::I32 => { export_arg_ty = quote! { i32 }; export_arg_ty_metadata = TypeMetadata::I32; }, ExportType::F64 => { export_arg_ty = quote! { f64 }; export_arg_ty_metadata = TypeMetadata::F64; }, ExportType::Unit => { panic!( "Receiving arguments of type `()` isn't supported" ); }, ExportType::Unknown( _ ) | ExportType::UnknownRef( _ ) | ExportType::StrRef | ExportType::Slice( _ ) => { // TODO: For known types generate more efficient serialization. export_arg_ty = quote! { i32 }; export_args_conversions.push( quote! { let #export_arg_ident = { let pointer = #export_arg_ident as *mut ::stdweb::private::SerializedValue; unsafe { let value = (&*pointer).deserialize(); ::stdweb::private::__web_free( pointer as *mut u8, std::mem::size_of::< ::stdweb::private::SerializedValue >() ); value } }; }); export_arg_ty_metadata = TypeMetadata::Custom { name: None, conversion_fn: "Module.STDWEB_PRIVATE.prepare_any_arg".to_owned() }; } } export_args_idents.push( export_arg_ident.clone() ); export_args_metadata.push( ArgMetadata { name: format!( "{}", export_arg_ident ), ty: export_arg_ty_metadata }); export_args.push( quote! { #export_arg_ident: #export_arg_ty }); } for arg in &export.args { let export_arg_ident = arg.ident.clone(); match arg.ty { // TODO: Throw a JS exception if `try_into` fails. ExportType::Unknown( ref ty ) => { let ty = ty.clone(); export_args_conversions.push( quote! { let #export_arg_ident: #ty = #export_arg_ident.try_into().unwrap(); }); }, ExportType::StrRef => { export_args_conversions.push( quote! { let #export_arg_ident: String = #export_arg_ident.try_into().unwrap(); let #export_arg_ident: &str = &#export_arg_ident; }); }, ExportType::Slice( ref ty ) => { export_args_conversions.push( quote! { let #export_arg_ident: Vec< #ty > = #export_arg_ident.try_into().unwrap(); let #export_arg_ident: &[#ty] = &#export_arg_ident; }); }, ExportType::UnknownRef( ref ty ) => { let ty = ty.clone(); export_args_conversions.push( quote! { let #export_arg_ident: #ty = #export_arg_ident.try_into().unwrap(); let #export_arg_ident = &#export_arg_ident; }); }, _ => {} } } let metadata = ExportMetadata { name: format!( "{}", export.ident ), args: export_args_metadata, result: export_result_metadata }; let json_metadata = serde_json::to_string( &metadata ).unwrap(); let encoded_metadata = base_x::encode( ENCODING_BASE, json_metadata.as_bytes() ); let export_ident = syn::Ident::new( &format!( "__JS_EXPORT_{}", &encoded_metadata ), Span::call_site() ); let original_ident = export.ident.clone(); output.push( quote! { #[doc(hidden)] #[no_mangle] #[deny(private_no_mangle_fns)] #[allow(unused_imports)] pub extern fn #export_ident( #(#export_args),* ) -> #export_result { use ::stdweb::unstable::TryInto; #(#export_args_conversions)* let __result = #original_ident( #(#export_args_idents),* ); #export_result_conversion return __result; } } ); } quote! { #(#output)* } } fn into_export( decl: &syn::Signature ) -> Export { let ident = decl.ident.clone(); assert!( decl.generics.lifetimes().next().is_none(), "Lifetimes are not yet not supported" ); assert!( decl.generics.type_params().next().is_none(), "Generics are not supported" ); assert!( decl.generics.where_clause.is_none(), "`where` clauses are not supported" ); assert!( decl.variadic.is_none(), "Variadic functions are not supported" ); let return_ty = match &decl.output { &syn::ReturnType::Default => ExportType::Unit, &syn::ReturnType::Type( _, ref ty ) => match_type( ty ) }; let mut args = Vec::new(); for (index, arg) in decl.inputs.iter().cloned().enumerate() { match arg { syn::FnArg::Receiver( .. ) => panic!( "`self` is not supported" ), syn::FnArg::Typed( syn::PatType { pat, ty, .. } ) => { match *pat { syn::Pat::Wild( _ ) => { let ident = syn::Ident::new( &format!( "__arg_{}", index ), Span::call_site() ); args.push( ExportArg { ident, ty: match_type( &ty ) }); }, syn::Pat::Ident( pat ) => { assert!( pat.by_ref.is_none(), "`ref` bindings are not supported" ); assert!( pat.mutability.is_none(), "`mut` bindings are not supported" ); assert!( pat.subpat.is_none(), "Subpatterns are not supported" ); args.push( ExportArg { ident: pat.ident, ty: match_type( &ty ) }); }, _ => panic!( "Argument patterns are not supported" ) } } } } Export { ident, return_ty, args } } pub fn js_export( attrs: TokenStream, input: TokenStream ) -> TokenStream { let input: proc_macro2::TokenStream = input.into(); let item: syn::Item = syn::parse2( input ).unwrap(); let mut exports = Vec::new(); if !attrs.is_empty() { panic!( "Extra attributes are not supported in `#[js_export]`!" ); } match item { syn::Item::Fn( ref function ) => { exports.push( into_export( &function.sig ) ); }, _ => panic!( "`#[js_export]` attached to an unsupported element!" ) } let generated = process( exports ); let output = quote! { #item #generated }; output.into() } stdweb-internal-macros-0.2.9/src/macro_js_raw.rs010064400017500001750000000131471354666444000201540ustar0000000000000000use proc_macro2::TokenStream; use syn; use syn::parse::{ParseStream, Parse, Result}; use attr_hack::AttrHack; use js_shim::js_shim_extern_code; use utils::{Target, dummy_idents}; #[cfg(test)] use testutils::assert_code_eq; fn parse_js_raw( input: TokenStream ) -> Result< JsRawInvocation > { syn::parse2( input ) } struct JsRawCode( String ); struct JsRawInvocation { code: String, args: Vec< syn::Expr > } impl Parse for JsRawCode { fn parse( input: ParseStream ) -> Result< Self > { if let Ok( ident ) = syn::Ident::parse( input ) { if ident == "stringify" { input.parse::< Token![!] >()?; let inner; parenthesized!( inner in input ); let code = inner.parse::< TokenStream >()?.to_string(); return Ok( JsRawCode( code ) ); } else if ident == "concat" { input.parse::< Token![!] >()?; let inner; parenthesized!( inner in input ); let code: syn::punctuated::Punctuated< JsRawCode, Token![,] > = inner.parse_terminated( JsRawCode::parse )?; let code: Vec< String > = code.into_iter().map( |chunk| chunk.0 ).collect(); let code = code.join( "" ); return Ok( JsRawCode( code ) ); } else { let ident_str = ident.to_string(); return Err( syn::Error::new_spanned( ident, format!( "unexpected ident '{}'", ident_str ) ) ); } } let literal: syn::LitStr = Parse::parse( input )?; let code = literal.value(); Ok( JsRawCode( code ) ) } } impl Parse for JsRawInvocation { fn parse( input: ParseStream ) -> Result< Self > { let code = input.parse::< JsRawCode >()?.0; let mut args = Vec::new(); while !input.is_empty() { syn::token::Comma::parse( input )?; if input.is_empty() { break; } let arg: syn::Expr = input.parse()?; args.push( arg ); } Ok( JsRawInvocation { code, args } ) } } fn js_raw_code( target: Target, js_raw: JsRawInvocation ) -> TokenStream { let (shim_name, shim) = js_shim_extern_code( target, &js_raw.code, js_raw.args.len(), None ); let args = js_raw.args; quote! {{ #shim unsafe { #shim_name( #((#args) as *const u8),* ) } }} } pub fn js_raw( target: Target, input: TokenStream ) -> Result< TokenStream > { let args = parse_js_raw( input )?; Ok( js_raw_code( target, args ) ) } // TODO: Delete this once expression procedural macros are stable. pub fn js_raw_attr( target: Target, input: TokenStream ) -> Result< TokenStream > { let wrapper: AttrHack< JsRawInvocation > = syn::parse2( input )?; let wrapper_name = wrapper.fn_name; let js_raw = wrapper.inner; let return_ty = wrapper.return_ty; let (shim_name, shim) = js_shim_extern_code( target, &js_raw.code, js_raw.args.len(), return_ty.clone() ); let return_signature = if let Some( ty ) = return_ty { quote! { -> #ty } } else { quote! {} }; let prototype_args = dummy_idents( js_raw.args.len() ).map( |name| quote! { #name: *const u8 } ); let call_args = dummy_idents( js_raw.args.len() ).map( |name| quote! { #name } ); let output = quote! { fn #wrapper_name( #(#prototype_args),* ) #return_signature { #shim unsafe { #shim_name( #(#call_args),* ) } } }; Ok( output ) } #[test] fn test_parse_js_raw_only_code() { let input = quote! { "function();" }; let js_raw = parse_js_raw( input ).unwrap(); assert_eq!( js_raw.code, "function();" ); assert!( js_raw.args.is_empty() ); } #[test] fn test_parse_js_raw_one_simple_arg() { let input = quote! { "function( $0 );", arg }; let js_raw = parse_js_raw( input ).unwrap(); assert_eq!( js_raw.code, "function( $0 );" ); assert_eq!( js_raw.args.len(), 1 ); assert_code_eq( &js_raw.args[ 0 ], quote! { arg } ); } #[test] fn test_parse_js_raw_complex() { let input = quote! { "dummy", { struct Foobar {} &[Foobar, Foobar] }, 1234, }; let js_raw = parse_js_raw( input ).unwrap(); assert_eq!( js_raw.code, "dummy" ); assert_eq!( js_raw.args.len(), 2 ); assert_code_eq( &js_raw.args[ 0 ], quote! { { struct Foobar {} &[Foobar, Foobar] } } ); assert_code_eq( &js_raw.args[ 1 ], quote! { 1234 } ); } #[test] fn test_parse_js_raw_stringify() { let input = quote! { stringify!( hello_world ) }; let js_raw = parse_js_raw( input ).unwrap(); assert_eq!( js_raw.code, "hello_world" ); assert!( js_raw.args.is_empty() ); } #[test] fn test_parse_js_raw_concat() { let input = quote! { concat!( "abc", "def", ) }; let js_raw = parse_js_raw( input ).unwrap(); assert_eq!( js_raw.code, "abcdef" ); assert!( js_raw.args.is_empty() ); } #[test] fn test_parse_js_raw_with_concat_and_stringify() { let input = quote! { concat!( "return foo( new ", stringify!( Bar ), "( baz )", " );" ), arg.as_ref().as_raw() }; let js_raw = parse_js_raw( input ).unwrap(); assert_eq!( js_raw.code, "return foo( new Bar( baz ) );" ); assert_eq!( js_raw.args.len(), 1 ); assert_code_eq( &js_raw.args[ 0 ], quote! { arg.as_ref().as_raw() } ); } #[test] fn test_js_raw_code_generation_succeeds() { let input = quote! { "function( $0 )", arg.as_ref().as_raw() }; js_raw( Target::Emscripten, input ).unwrap(); } stdweb-internal-macros-0.2.9/src/testutils.rs010064400017500001750000000032501347032716600175350ustar0000000000000000use proc_macro2; use quote::ToTokens; #[cfg(test)] pub fn rust_pretty_print( code: &proc_macro2::TokenStream ) -> String { use std::process::{Command, Stdio}; use std::mem; use std::io::{Read, Write}; let mut cmd = Command::new( "rustfmt" ); cmd.arg( "--version" ); let has_rustfmt = cmd.output().map( |output| output.status.success() ).unwrap_or( false ); if !has_rustfmt { return format!( "{}", code ); } let mut cmd = Command::new( "rustfmt" ); cmd.stdin( Stdio::piped() ); cmd.stdout( Stdio::piped() ); cmd.stderr( Stdio::null() ); let mut child = cmd.spawn().expect( "cannot spawn rustfmt" ); let mut stdin = child.stdin.take().unwrap(); write!( stdin, "{}", code ).unwrap(); mem::drop( stdin ); let mut pretty_code = String::new(); let mut stdout = child.stdout.take().unwrap(); stdout.read_to_string( &mut pretty_code ).unwrap(); child.wait().expect( "rustfmt failed" ); pretty_code } #[cfg(test)] pub fn assert_code_eq< T: ToTokens, U: ToTokens >( actual: T, expected: U ) { let actual = actual.into_token_stream(); let expected = expected.into_token_stream(); if format!( "{}", actual ) != format!( "{}", expected ) { let expected_pretty = rust_pretty_print( &expected ); let actual_pretty = rust_pretty_print( &actual ); if expected_pretty != actual_pretty { println!( "Expected:\n{}", expected_pretty ); println!( "Actual:\n{}", actual_pretty ); } else { println!( "Expected:\n{}", expected ); println!( "Actual:\n{}", actual ); } panic!( "Expected different generated code!" ); } } stdweb-internal-macros-0.2.9/src/utils.rs010064400017500001750000000005061347032716600166360ustar0000000000000000use syn; use proc_macro2::Span; #[derive(Copy, Clone)] pub enum Target { Emscripten, NativeWebAssembly, WasmBindgen } pub fn dummy_idents( count: usize ) -> impl Iterator< Item = syn::Ident > { (0..count).into_iter().map( |nth| { syn::Ident::new( &format!( "a{}", nth ), Span::call_site() ) }) } stdweb-internal-macros-0.2.9/.cargo_vcs_info.json0000644000000001120000000000000154610ustar00{ "git": { "sha1": "9b418d98df6fafaa4d4b87b04c304d0220292055" } }