1 use std::fmt::Write;
2 use proc_macro2::{TokenTree, Delimiter};
3 use syn;
4 use syn::buffer::Cursor;
5 use syn::parse::{Result, ParseStream};
6 use quote::ToTokens;
7 
8 enum Chunk {
9     Text( String ),
10     Block( syn::Block )
11 }
12 
13 pub struct StringifiedCode {
14     chunks: Vec< Chunk >
15 }
16 
can_trim_whitespace_next_to( ch: char ) -> bool17 fn can_trim_whitespace_next_to( ch: char ) -> bool {
18     ch.is_whitespace() || "=+-*/%<>&^|~?:{}()[];.,".contains( ch )
19 }
20 
21 impl StringifiedCode {
push( &mut self, string: &str )22     fn push( &mut self, string: &str ) {
23         match self.chunks.last_mut() {
24             None | Some( Chunk::Block( .. ) ) => {},
25             Some( Chunk::Text( ref mut buffer ) ) => {
26                 let l = buffer.chars().last().unwrap_or( ' ' );
27                 let r = string.chars().next().unwrap_or( ' ' );
28                 if !can_trim_whitespace_next_to( l ) && !can_trim_whitespace_next_to( r ) {
29                     buffer.push( ' ' );
30                 }
31                 buffer.push_str( string );
32                 return;
33             }
34         }
35 
36         self.chunks.push( Chunk::Text( string.into() ) );
37     }
38 
push_block( &mut self, block: syn::Block )39     fn push_block( &mut self, block: syn::Block ) {
40         self.chunks.push( Chunk::Block( block ) );
41     }
42 
arg_count( &self ) -> usize43     pub fn arg_count( &self ) -> usize {
44         self.chunks.iter().filter( |chunk|
45             match chunk {
46                 Chunk::Block( .. ) => true,
47                 _ => false
48             }
49         ).count()
50     }
51 
code( &self, initial_placeholder_index: usize ) -> String52     pub fn code( &self, initial_placeholder_index: usize ) -> String {
53         let capacity = self.chunks.iter().map( |chunk|
54             match chunk {
55                 Chunk::Text( text ) => text.len(),
56                 Chunk::Block( .. ) => 4
57             }
58         ).fold( 0, |sum, len| sum + len );
59 
60         let mut counter = initial_placeholder_index;
61         let mut output = String::with_capacity( capacity );
62         for chunk in &self.chunks {
63             match chunk {
64                 Chunk::Text( text ) => output.push_str( text ),
65                 Chunk::Block( _ ) => {
66                     write!( output, "(${})", counter ).unwrap();
67                     counter += 1;
68                 }
69             }
70         }
71 
72         output
73     }
74 }
75 
stringify< 'a >( mut cursor: Cursor< 'a >, output: &mut StringifiedCode ) -> Result< Cursor< 'a > >76 fn stringify< 'a >( mut cursor: Cursor< 'a >, output: &mut StringifiedCode ) -> Result< Cursor< 'a > > {
77     while let Some( (tt, next) ) = cursor.token_tree() {
78         cursor = match tt {
79             TokenTree::Punct( ref punct ) if punct.as_char() == '@' && next.group( Delimiter::Brace ).is_some() => {
80                 let (tt, next_next) = next.token_tree().unwrap();
81                 output.push_block( syn::parse2( tt.into_token_stream() )? );
82                 next_next
83             },
84             TokenTree::Group( ref group ) => {
85                 let (start, end) = match group.delimiter() {
86                     Delimiter::Brace => ("{", "}"),
87                     Delimiter::Bracket => ("[", "]"),
88                     Delimiter::Parenthesis => ("(", ")"),
89                     Delimiter::None => ("", "")
90                 };
91 
92                 output.push( start );
93                 let inner = cursor.group( group.delimiter() ).unwrap().0;
94                 stringify( inner, output )?;
95                 output.push( end );
96                 next
97             },
98             _ => {
99                 let token = tt.to_string();
100                 output.push( &token );
101                 next
102             }
103         };
104     }
105 
106     Ok( cursor )
107 }
108 
109 impl syn::parse::Parse for StringifiedCode {
parse( input: ParseStream ) -> Result< Self >110     fn parse( input: ParseStream ) -> Result< Self > {
111         input.step( |cursor| {
112             let mut output = StringifiedCode {
113                 chunks: Vec::new()
114             };
115             let cursor = stringify( *cursor, &mut output )?;
116             Ok( (output, cursor) )
117         })
118     }
119 }
120 
121 #[cfg(test)]
122 mod tests {
123     use super::StringifiedCode;
124     use proc_macro2::TokenStream;
125 
assert_stringify( input: TokenStream, initial_placeholder: usize, expected: &str )126     fn assert_stringify( input: TokenStream, initial_placeholder: usize, expected: &str ) {
127         let snippet: StringifiedCode = syn::parse2( input ).unwrap();
128         assert_eq!( snippet.code( initial_placeholder ), expected );
129     }
130 
131     #[test]
test_stringify()132     fn test_stringify() {
133         assert_stringify( quote! { return thing; }, 0, "return thing;" );
134         assert_stringify( quote! { console.log }, 0, "console.log" );
135         assert_stringify( quote! { 1.0 }, 0, "1.0" );
136         assert_stringify( quote! { [ 1.0 ] }, 0, "[1.0]" );
137         assert_stringify( quote! { { 1.0 } }, 0, "{1.0}" );
138         assert_stringify( quote! { ( 1.0 ) }, 0, "(1.0)" );
139         assert_stringify( quote! { a b }, 0, "a b" );
140         assert_stringify( quote! { === }, 0, "===" );
141         assert_stringify( quote! { ++i }, 0, "++i" );
142         assert_stringify( quote! { i++ }, 0, "i++" );
143         assert_stringify( quote! { --i }, 0, "--i" );
144         assert_stringify( quote! { i-- }, 0, "i--" );
145         assert_stringify( quote! { return _.sum([1, 2]); }, 0, "return _.sum([1,2]);" );
146         assert_stringify( quote! { return $; }, 0, "return $;" );
147         assert_stringify( quote! { ( @{1} ); }, 0, "(($0));" );
148         assert_stringify(
149             quote! { console.log( "Hello!", @{1234i32} ); },
150             0,
151             "console.log(\"Hello!\",($0));"
152         );
153         assert_stringify(
154             quote! { @{a}.fn( @{b} ); },
155             0,
156             "($0).fn(($1));"
157         );
158         assert_stringify(
159             quote! { @{a}.fn( @{b} ); },
160             1,
161             "($1).fn(($2));"
162         );
163     }
164 }
165