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