├── .gitignore ├── Cargo.toml ├── README.md ├── rustfmt.toml ├── src └── lib.rs ├── template_quote_impl ├── Cargo.toml └── src │ └── lib.rs └── tests ├── conditional.rs ├── interpolation.rs ├── repetition.rs ├── test.rs └── types.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template-quote" 3 | version = "0.4.2" 4 | edition = "2021" 5 | authors = ["Yasuo Ozu "] 6 | description = "A new-fasioned quote! macro implementation with pretty template-engine like syntax" 7 | license = "MIT" 8 | keywords = ["syn", "quote", "proc-macro", "macros"] 9 | categories = ["development-tools::procedural-macro-helpers"] 10 | repository = "https://github.com/yasuo-ozu/template_quote" 11 | 12 | 13 | [dependencies] 14 | template-quote-impl = { path = "./template_quote_impl", version = "0.4.2" } 15 | quote = "1.0" 16 | 17 | [dev-dependencies] 18 | proc-macro2 = "1.0.47" 19 | 20 | [workspace] 21 | members = [ 22 | ".", 23 | "template_quote_impl" 24 | ] 25 | resolver = "2" 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This crate provides Rust's quasi-quoting macro, intended to use in your proc-macro, to generate `TokenStream` 4 | expanding variable interporation and expanding templates. 5 | 6 | This macro is constructed based on `proc_macro` crate. 7 | 8 | # Interporation 9 | 10 | Original `quote!` macro syntax is fully supported. See [quote's doc](https://docs.rs/quote/1.0.23/quote/). 11 | 12 | For backward compatibility, interporation rule is same as traditional 13 | `quote!` macro. The interporation is done with `#var` (similar to the 14 | variable `$var` in `macro_rules!`). Most variables in `Syn` crate are 15 | interporated using [`::proc_quote::ToTokens`] trait. 16 | 17 | ## Rules 18 | 19 | Repetition is done using syntax like `#(...)*` or `#(...),*`. It repeats the 20 | variables (`#var`) inside this syntax, which implements 21 | [`::proc_quote::Repeat`]. 22 | 23 | - `#(...)*` - repeat ... with no separators. at least one variable should be 24 | included in ... 25 | - `#(...),*` - same as before, but interporates with separator ','. 26 | 27 | ## Problem 28 | 29 | The interporation rule is **rough**, so I implemented new 'template' syntax. 30 | For example, the following code will not allowed, because `#var1` cannot be 31 | iterated double. 32 | 33 | ``` 34 | # use template_quote::quote; 35 | let var1 = vec!['a', 'b']; 36 | let var2 = vec![vec![1, 2], vec![3, 4]]; 37 | let tokens = quote!{ 38 | #(#(#var1 #var2)*)* 39 | }; 40 | assert_eq!("'a' 1i32 'a' 2i32 'b' 3i32 'b' 4i32", tokens.to_string()); 41 | ``` 42 | 43 | # Template syntax 44 | 45 | Template syntax is proceedual-like syntax, which allows you to use structual 46 | statementsinside the macro. 47 | 48 | ## If syntax 49 | 50 | This code iterates around `#i` (with interporation), and emits `i32` into 51 | `TokenStream` while the number meets the condition. 52 | 53 | ``` 54 | # use template_quote::quote; 55 | let i = vec![1, 2, 3]; 56 | let tokens = quote!{ 57 | #( 58 | #(if i > &2) { 59 | #i 60 | } 61 | )* 62 | }; 63 | assert_eq!("3i32", tokens.to_string()); 64 | ``` 65 | 66 | The if-else and if-else-if is also allowed. 67 | 68 | ``` 69 | # use template_quote::quote; 70 | let i = vec![1, 2, 3]; 71 | let tokens = quote!{ 72 | #( 73 | #(if i > &2) { 74 | + #i 75 | } 76 | #(else) { 77 | - #i 78 | } 79 | )* 80 | }; 81 | assert_eq!("- 1i32 - 2i32 + 3i32", tokens.to_string()); 82 | ``` 83 | 84 | ``` 85 | # use template_quote::quote; 86 | let i = vec![1, 2, 3, 4, 5]; 87 | let tokens = quote!{ 88 | #( 89 | #(if i % &2 == 0) { 90 | + #i 91 | } 92 | #(else if i % &3 == 0) { 93 | - #i 94 | } 95 | #(else) { 96 | #i 97 | } 98 | )* 99 | }; 100 | assert_eq!("1i32 + 2i32 - 3i32 + 4i32 5i32", tokens.to_string()); 101 | ``` 102 | 103 | ## For syntax 104 | 105 | For syntax iterates around the variable (like interporation), but it 106 | specifies which variable to iterate. 107 | 108 | ``` 109 | # use template_quote::quote; 110 | let v1 = vec![1, 2]; 111 | let v2 = vec!['a', 'b']; 112 | let tokens = quote!{ 113 | #(for i1 in &v1) { 114 | #(for i2 in &v2) { 115 | #i1 -> #i2 116 | } 117 | } 118 | }; 119 | assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string()); 120 | ``` 121 | 122 | Internal loop can be replaced with interporation: 123 | 124 | ``` 125 | # use template_quote::quote; 126 | let v1 = vec![1, 2]; 127 | let v2 = vec!['a', 'b']; 128 | let tokens = quote!{ 129 | #(for i1 in &v1) { 130 | #( 131 | #i1 -> #v2 132 | )* 133 | } 134 | }; 135 | assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string()); 136 | ``` 137 | 138 | You can also specify separator with for statement. 139 | 140 | ``` 141 | # use template_quote::quote; 142 | let v = vec![1, 2]; 143 | let tokens = quote!{ 144 | #(for i in v) | { #i } 145 | }; 146 | assert_eq!("1i32 | 2i32", tokens.to_string()); 147 | ``` 148 | 149 | Interporation is not usable with variables binded in for syntax. For 150 | example, 151 | 152 | ```compile_fail 153 | # use template_quote::quote; 154 | let v = vec![vec![1, 2], vec![3]]; 155 | let tokens = quote!{ 156 | #( 157 | #(for i in v) { #i } 158 | ),* 159 | }; 160 | assert_eq!("1i32 2i32 , 3i32", tokens.to_string()); 161 | ``` 162 | 163 | will fail into error because no variables is available in the interporation 164 | syntax. 165 | 166 | ```text 167 | error: proc macro panicked 168 | --> *** 169 | | 170 | 6 | let tokens = quote!{ 171 | | ______________^ 172 | 7 | | #( 173 | 8 | | #(for i in v) { #i } 174 | 9 | | )* 175 | 10 | | }; 176 | | |_^ 177 | | 178 | = help: message: Iterative vals not found 179 | ``` 180 | 181 | In this case, you can use `#(for i in #v)` syntax to specify which variable 182 | to iterate with interporation: 183 | 184 | ``` 185 | # use template_quote::quote; 186 | let v = vec![vec![1, 2], vec![3]]; 187 | let tokens = quote!{ 188 | #( 189 | #(for i in #v) { #i } 190 | ),* 191 | }; 192 | assert_eq!("1i32 2i32 , 3i32", tokens.to_string()); 193 | ``` 194 | 195 | ## While syntax 196 | 197 | ``` 198 | # use template_quote::quote; 199 | let mut v = vec![1, 2].into_iter(); 200 | let tokens = quote!{ 201 | #(while v.next().is_some()) { hello } 202 | }; 203 | assert_eq!("hello hello", tokens.to_string()); 204 | ``` 205 | 206 | ## While-Let syntax 207 | 208 | ``` 209 | # use template_quote::quote; 210 | let mut v = vec![1, 2].into_iter(); 211 | let tokens = quote!{ 212 | #(while let Some(i) = v.next()) { #i } 213 | }; 214 | assert_eq!("1i32 2i32", tokens.to_string()); 215 | ``` 216 | 217 | Same as 'for' syntax, the binded valiables in 'while' is not iteratable with 218 | interporation syntax. For example, 219 | 220 | ```compile_fail 221 | # use template_quote::quote; 222 | let mut v = vec![1, 2].into_iter(); 223 | quote!{ 224 | #( 225 | #(while let Some(i) = v.next()) { #i } 226 | )* 227 | }; 228 | ``` 229 | 230 | will fail. 231 | 232 | ## Let syntax 233 | 234 | Let syntax bind new variables usable inside the block. 235 | 236 | ``` 237 | # use template_quote::quote; 238 | let v = vec![(1, 'a'), (2, 'b')]; 239 | let tokens = quote!{ 240 | #(for i in v), { 241 | #(let (n, c) = i) { 242 | #n -> #c 243 | } 244 | } 245 | }; 246 | assert_eq!("1i32 -> 'a' , 2i32 -> 'b'", tokens.to_string()); 247 | ``` 248 | 249 | Here, `#n` and `#c` is not iteratable with interporation syntax. 250 | 251 | ## Inline expression 252 | 253 | You can place inline expression in `quote!` macro. 254 | 255 | ``` 256 | # use template_quote::quote; 257 | let v = vec![1, 2]; 258 | let tokens = quote!{ 259 | #(for i in v){ 260 | #i -> #{ i.to_string() } 261 | } 262 | }; 263 | assert_eq!("1i32 -> \"1\" 2i32 -> \"2\"", tokens.to_string()); 264 | ``` 265 | 266 | The following example will fail to compile because it does not understand 267 | which variable to be interpolated: 268 | 269 | ```compile_fail 270 | # use template_quote::quote; 271 | let v = vec![1, 2]; 272 | let tokens = quote!{ 273 | #( 274 | #{ v.to_string() } 275 | )* 276 | }; 277 | assert_eq!("\"1\" \"2\"", tokens.to_string()); 278 | ``` 279 | 280 | In this case, you can use `#i` syntax in inline expression to specify which 281 | variable to iterate with interporation syntax. 282 | 283 | ``` 284 | # use template_quote::quote; 285 | let v = vec![1, 2]; 286 | let tokens = quote!{ 287 | #( 288 | #{ #v.to_string() } 289 | )* 290 | }; 291 | assert_eq!("\"1\" \"2\"", tokens.to_string()); 292 | ``` 293 | 294 | ## Inline statement 295 | 296 | You can place arbitrary statement inside this macro. For example, 297 | 298 | ``` 299 | # use template_quote::quote; 300 | let v = vec![1, 2, 3]; 301 | let tokens = quote!{ 302 | #( 303 | #v 304 | #{ eprintln!("debug: {}", &v); } 305 | )* 306 | }; 307 | assert_eq!("1i32 2i32 3i32", tokens.to_string()); 308 | ``` 309 | 310 | will print: 311 | 312 | ```text 313 | debug: 1 314 | debug: 2 315 | debug: 3 316 | ``` 317 | 318 | To be distinguishable, all statements have to end with ';'. For example, 319 | 'if' statement in inline statement syntax should placed with extra ';'. 320 | 321 | ``` 322 | # use template_quote::quote; 323 | let v = vec![1, 2, 3]; 324 | quote!{ 325 | #( 326 | #v 327 | #{ if v >= &2 { eprintln!("debug: {}", &v); } ; } 328 | )* 329 | }; 330 | ``` 331 | 332 | ## Break, Continue 333 | 334 | You can put control statement like `break` or `continue` in inline 335 | statement, but it is a bit danger. 336 | 337 | If you use `break;` inside block (like `{ ... }` or `( ... )`), `break` will 338 | suddenly give up emitting whole group, and nothing will be emitted. For 339 | example, the following code does not emit any group: 340 | 341 | ``` 342 | # use template_quote::quote; 343 | let v = vec![1, 2, 3]; 344 | let tokens = quote!{ 345 | #(for i in v) { 346 | #i // this is emitted once 347 | // The block is not emitted 348 | { 349 | #i 350 | #{ break; } 351 | } 352 | } 353 | }; 354 | assert_eq!("1i32", tokens.to_string()); 355 | ``` 356 | 357 | `break` also affects on interporation syntax like: 358 | 359 | ``` 360 | # use template_quote::quote; 361 | let v = vec![1, 2, 3]; 362 | let tokens = quote!{ 363 | #( 364 | #v 365 | #{ break; } 366 | ),* 367 | }; 368 | assert_eq!("1i32", tokens.to_string()); 369 | ``` 370 | 371 | Unfortunately, `break` will leak outside of `quote!` macro. This is example 372 | which the internal `break` affects on 'for' loop, which is placed outer of 373 | the `quote!` macro. 374 | 375 | ``` 376 | # use template_quote::quote; 377 | let mut v = Vec::new(); 378 | for _ in 0..3 { 379 | let tokens = quote!{ 380 | #{ break; } 381 | }; 382 | v.push(tokens); 383 | } 384 | assert_eq!(v.len(), 0); 385 | ``` 386 | 387 | This crate provides quasi-quoting macros like [quote](https://github.com/dtolnay/quote). 388 | This crate has backward-compatibility with original `quote!` macro and also provides 389 | new template-engine like syntax. 390 | 391 | This crate is get some inspiration from [proc-quote](https://crates.io/crates/proc-quote). 392 | 393 | # Using this crate 394 | 395 | This crate is useful for developing proc-macro. Usually an proc-macro crate using template_quote is placed with following `Cargo.toml`: 396 | 397 | ```Cargo.toml 398 | [package] 399 | name = "your_crate_name" 400 | version = "0.0.0" 401 | edition = "2021" 402 | 403 | [lib] 404 | proc-macro = true 405 | 406 | [dependencies] 407 | template-quote = "0.2" 408 | proc-macro2 = "1.0" 409 | ``` 410 | 411 | and with following `src/lib.rs` code: 412 | 413 | ```lib.rs 414 | extern crate proc_macro; 415 | extern crate proc_macro2; 416 | extern crate template_quote; 417 | 418 | use template_quote::quote; 419 | use proc_macro::TokenStream; 420 | use proc_macro2::TokenStream as TokenStream2; 421 | 422 | #[proc_macro] 423 | pub fn my_macro(_: TokenStream) -> TokenStream { 424 | quote! { /* something here */ }.into() 425 | } 426 | ``` 427 | 428 | then you will be able to use it like: 429 | 430 | ```rust 431 | extern crate your_crate_name; 432 | use your_crate_name::my_macro; 433 | 434 | my_macro!() 435 | ``` 436 | 437 | 438 | # Limitation 439 | 440 | - If the punct token before '#' in the macro body has `Spacing::Join`, then the emitting punct also has same spacing, whether the '#' token is processed by the macro or not. 441 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | wrap_comments = true 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This create provide [`quote!`] macro. 2 | 3 | /// The entrypoint with fully backward-compatibility with traditional `quote!` 4 | /// macro. 5 | /// 6 | /// This macro is intended to use in your proc-macro, to generate `TokenStream` 7 | /// expanding variable interporation and expanding templates. 8 | /// 9 | /// This macro is constructed based on `proc_macro` crate. 10 | /// 11 | /// # Interporation 12 | /// 13 | /// For backward compatibility, interporation rule is same as traditional 14 | /// `quote!` macro. The interporation is done with `#var` (similar to the 15 | /// variable `$var` in `macro_rules!`). Most variables in `Syn` crate are 16 | /// interporated using [`::proc_quote::ToTokens`] trait. 17 | /// 18 | /// ## Rules 19 | /// 20 | /// Repetition is done using syntax like `#(...)*` or `#(...),*`. It repeats the 21 | /// variables (`#var`) inside this syntax, which implements 22 | /// [`::proc_quote::Repeat`]. 23 | /// 24 | /// - `#(...)*` - repeat ... with no separators. at least one variable should be 25 | /// included in ... 26 | /// - `#(...),*` - same as before, but interporates with separator ','. 27 | /// 28 | /// ## Problem 29 | /// 30 | /// The interporation rule is **rough**, so I implemented new 'template' syntax. 31 | /// For example, the following code will not allowed, because `#var1` cannot be 32 | /// iterated double. 33 | /// 34 | /// ``` 35 | /// # use template_quote::quote; 36 | /// let var1 = vec!['a', 'b']; 37 | /// let var2 = vec![vec![1, 2], vec![3, 4]]; 38 | /// let tokens = quote!{ 39 | /// #(#(#var1 #var2)*)* 40 | /// }; 41 | /// assert_eq!("'a' 1i32 'a' 2i32 'b' 3i32 'b' 4i32", tokens.to_string()); 42 | /// ``` 43 | /// 44 | /// # Template syntax 45 | /// 46 | /// Template syntax is proceedual-like syntax, which allows you to use structual 47 | /// statementsinside the macro. 48 | /// 49 | /// ## If syntax 50 | /// 51 | /// This code iterates around `#i` (with interporation), and emits `i32` into 52 | /// `TokenStream` while the number meets the condition. 53 | /// 54 | /// ``` 55 | /// # use template_quote::quote; 56 | /// let i = vec![1, 2, 3]; 57 | /// let tokens = quote!{ 58 | /// #( 59 | /// #(if i > &2) { 60 | /// #i 61 | /// } 62 | /// )* 63 | /// }; 64 | /// assert_eq!("3i32", tokens.to_string()); 65 | /// ``` 66 | /// 67 | /// The if-else and if-else-if is also allowed. 68 | /// 69 | /// ``` 70 | /// # use template_quote::quote; 71 | /// let i = vec![1, 2, 3]; 72 | /// let tokens = quote!{ 73 | /// #( 74 | /// #(if i > &2) { 75 | /// + #i 76 | /// } 77 | /// #(else) { 78 | /// - #i 79 | /// } 80 | /// )* 81 | /// }; 82 | /// assert_eq!("- 1i32 - 2i32 + 3i32", tokens.to_string()); 83 | /// ``` 84 | /// 85 | /// ``` 86 | /// # use template_quote::quote; 87 | /// let i = vec![1, 2, 3, 4, 5]; 88 | /// let tokens = quote!{ 89 | /// #( 90 | /// #(if i % &2 == 0) { 91 | /// + #i 92 | /// } 93 | /// #(else if i % &3 == 0) { 94 | /// - #i 95 | /// } 96 | /// #(else) { 97 | /// #i 98 | /// } 99 | /// )* 100 | /// }; 101 | /// assert_eq!("1i32 + 2i32 - 3i32 + 4i32 5i32", tokens.to_string()); 102 | /// ``` 103 | /// 104 | /// ## For syntax 105 | /// 106 | /// For syntax iterates around the variable (like interporation), but it 107 | /// specifies which variable to iterate. 108 | /// 109 | /// ``` 110 | /// # use template_quote::quote; 111 | /// let v1 = vec![1, 2]; 112 | /// let v2 = vec!['a', 'b']; 113 | /// let tokens = quote!{ 114 | /// #(for i1 in &v1) { 115 | /// #(for i2 in &v2) { 116 | /// #i1 -> #i2 117 | /// } 118 | /// } 119 | /// }; 120 | /// assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string()); 121 | /// ``` 122 | /// 123 | /// Internal loop can be replaced with interporation: 124 | /// 125 | /// ``` 126 | /// # use template_quote::quote; 127 | /// let v1 = vec![1, 2]; 128 | /// let v2 = vec!['a', 'b']; 129 | /// let tokens = quote!{ 130 | /// #(for i1 in &v1) { 131 | /// #( 132 | /// #i1 -> #v2 133 | /// )* 134 | /// } 135 | /// }; 136 | /// assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string()); 137 | /// ``` 138 | /// 139 | /// You can also specify separator with for statement. 140 | /// 141 | /// ``` 142 | /// # use template_quote::quote; 143 | /// let v = vec![1, 2]; 144 | /// let tokens = quote!{ 145 | /// #(for i in v) | { #i } 146 | /// }; 147 | /// assert_eq!("1i32 | 2i32", tokens.to_string()); 148 | /// ``` 149 | /// 150 | /// Interporation is not usable with variables binded in for syntax. For 151 | /// example, 152 | /// 153 | /// ```compile_fail 154 | /// # use template_quote::quote; 155 | /// let v = vec![vec![1, 2], vec![3]]; 156 | /// let tokens = quote!{ 157 | /// #( 158 | /// #(for i in v) { #i } 159 | /// ),* 160 | /// }; 161 | /// assert_eq!("1i32 2i32 , 3i32", tokens.to_string()); 162 | /// ``` 163 | /// 164 | /// will fail into error because no variables is available in the interporation 165 | /// syntax. 166 | /// 167 | /// ```text 168 | /// error: proc macro panicked 169 | /// --> *** 170 | /// | 171 | /// 6 | let tokens = quote!{ 172 | /// | ______________^ 173 | /// 7 | | #( 174 | /// 8 | | #(for i in v) { #i } 175 | /// 9 | | )* 176 | /// 10 | | }; 177 | /// | |_^ 178 | /// | 179 | /// = help: message: Iterative vals not found 180 | /// ``` 181 | /// 182 | /// In this case, you can use `#(for i in #v)` syntax to specify which variable 183 | /// to iterate with interporation: 184 | /// 185 | /// ``` 186 | /// # use template_quote::quote; 187 | /// let v = vec![vec![1, 2], vec![3]]; 188 | /// let tokens = quote!{ 189 | /// #( 190 | /// #(for i in #v) { #i } 191 | /// ),* 192 | /// }; 193 | /// assert_eq!("1i32 2i32 , 3i32", tokens.to_string()); 194 | /// ``` 195 | /// 196 | /// ## While syntax 197 | /// 198 | /// ``` 199 | /// # use template_quote::quote; 200 | /// let mut v = vec![1, 2].into_iter(); 201 | /// let tokens = quote!{ 202 | /// #(while v.next().is_some()) { hello } 203 | /// }; 204 | /// assert_eq!("hello hello", tokens.to_string()); 205 | /// ``` 206 | /// 207 | /// ## While-Let syntax 208 | /// 209 | /// ``` 210 | /// # use template_quote::quote; 211 | /// let mut v = vec![1, 2].into_iter(); 212 | /// let tokens = quote!{ 213 | /// #(while let Some(i) = v.next()) { #i } 214 | /// }; 215 | /// assert_eq!("1i32 2i32", tokens.to_string()); 216 | /// ``` 217 | /// 218 | /// Same as 'for' syntax, the binded valiables in 'while' is not iteratable with 219 | /// interporation syntax. For example, 220 | /// 221 | /// ```compile_fail 222 | /// # use template_quote::quote; 223 | /// let mut v = vec![1, 2].into_iter(); 224 | /// quote!{ 225 | /// #( 226 | /// #(while let Some(i) = v.next()) { #i } 227 | /// )* 228 | /// }; 229 | /// ``` 230 | /// 231 | /// will fail. 232 | /// 233 | /// ## Let syntax 234 | /// 235 | /// Let syntax bind new variables usable inside the block. 236 | /// 237 | /// ``` 238 | /// # use template_quote::quote; 239 | /// let v = vec![(1, 'a'), (2, 'b')]; 240 | /// let tokens = quote!{ 241 | /// #(for i in v), { 242 | /// #(let (n, c) = i) { 243 | /// #n -> #c 244 | /// } 245 | /// } 246 | /// }; 247 | /// assert_eq!("1i32 -> 'a' , 2i32 -> 'b'", tokens.to_string()); 248 | /// ``` 249 | /// 250 | /// Here, `#n` and `#c` is not iteratable with interporation syntax. 251 | /// 252 | /// ## Inline expression 253 | /// 254 | /// You can place inline expression in `quote!` macro. 255 | /// 256 | /// ``` 257 | /// # use template_quote::quote; 258 | /// let v = vec![1, 2]; 259 | /// let tokens = quote!{ 260 | /// #(for i in v){ 261 | /// #i -> #{ i.to_string() } 262 | /// } 263 | /// }; 264 | /// assert_eq!("1i32 -> \"1\" 2i32 -> \"2\"", tokens.to_string()); 265 | /// ``` 266 | /// 267 | /// The following example will fail to compile because it does not understand 268 | /// which variable to be interpolated: 269 | /// 270 | /// ```compile_fail 271 | /// # use template_quote::quote; 272 | /// let v = vec![1, 2]; 273 | /// let tokens = quote!{ 274 | /// #( 275 | /// #{ v.to_string() } 276 | /// )* 277 | /// }; 278 | /// assert_eq!("\"1\" \"2\"", tokens.to_string()); 279 | /// ``` 280 | /// 281 | /// In this case, you can use `#i` syntax in inline expression to specify which 282 | /// variable to iterate with interporation syntax. 283 | /// 284 | /// ``` 285 | /// # use template_quote::quote; 286 | /// let v = vec![1, 2]; 287 | /// let tokens = quote!{ 288 | /// #( 289 | /// #{ #v.to_string() } 290 | /// )* 291 | /// }; 292 | /// assert_eq!("\"1\" \"2\"", tokens.to_string()); 293 | /// ``` 294 | /// 295 | /// ## Inline statement 296 | /// 297 | /// You can place arbitrary statement inside this macro. For example, 298 | /// 299 | /// ``` 300 | /// # use template_quote::quote; 301 | /// let v = vec![1, 2, 3]; 302 | /// let tokens = quote!{ 303 | /// #( 304 | /// #v 305 | /// #{ eprintln!("debug: {}", &v); } 306 | /// )* 307 | /// }; 308 | /// assert_eq!("1i32 2i32 3i32", tokens.to_string()); 309 | /// ``` 310 | /// 311 | /// will print: 312 | /// 313 | /// ```text 314 | /// debug: 1 315 | /// debug: 2 316 | /// debug: 3 317 | /// ``` 318 | /// 319 | /// To be distinguishable, all statements have to end with ';'. For example, 320 | /// 'if' statement in inline statement syntax should placed with extra ';'. 321 | /// 322 | /// ``` 323 | /// # use template_quote::quote; 324 | /// let v = vec![1, 2, 3]; 325 | /// quote!{ 326 | /// #( 327 | /// #v 328 | /// #{ if v >= &2 { eprintln!("debug: {}", &v); } ; } 329 | /// )* 330 | /// }; 331 | /// ``` 332 | /// 333 | /// ## Break, Continue 334 | /// 335 | /// You can put control statement like `break` or `continue` in inline 336 | /// statement, but it is a bit danger. 337 | /// 338 | /// If you use `break;` inside block (like `{ ... }` or `( ... )`), `break` will 339 | /// suddenly give up emitting whole group, and nothing will be emitted. For 340 | /// example, the following code does not emit any group: 341 | /// 342 | /// ``` 343 | /// # use template_quote::quote; 344 | /// let v = vec![1, 2, 3]; 345 | /// let tokens = quote!{ 346 | /// #(for i in v) { 347 | /// #i // this is emitted once 348 | /// // The block is not emitted 349 | /// { 350 | /// #i 351 | /// #{ break; } 352 | /// } 353 | /// } 354 | /// }; 355 | /// assert_eq!("1i32", tokens.to_string()); 356 | /// ``` 357 | /// 358 | /// `break` also affects on interporation syntax like: 359 | /// 360 | /// ``` 361 | /// # use template_quote::quote; 362 | /// let v = vec![1, 2, 3]; 363 | /// let tokens = quote!{ 364 | /// #( 365 | /// #v 366 | /// #{ break; } 367 | /// ),* 368 | /// }; 369 | /// assert_eq!("1i32", tokens.to_string()); 370 | /// ``` 371 | /// 372 | /// Unfortunately, `break` will leak outside of `quote!` macro. This is example 373 | /// which the internal `break` affects on 'for' loop, which is placed outer of 374 | /// the `quote!` macro. 375 | /// 376 | /// ``` 377 | /// # use template_quote::quote; 378 | /// let mut v = Vec::new(); 379 | /// for _ in 0..3 { 380 | /// let tokens = quote!{ 381 | /// #{ break; } 382 | /// }; 383 | /// v.push(tokens); 384 | /// } 385 | /// assert_eq!(v.len(), 0); 386 | /// ``` 387 | pub use template_quote_impl::quote; 388 | 389 | /// [`quote_configured!`] macro is configurable version of [`quote!`]. 390 | /// 391 | /// ```ignore 392 | /// # use template_quote::quote_configured; 393 | /// quote_configured! { 394 | /// { 395 | /// proc_macro2: ::proc_macro2, 396 | /// quote: ::quote, 397 | /// core: ::core, // core crate in std 398 | /// quote: ::quote, 399 | /// span: ::some_span, 400 | /// } => 401 | /// ... 402 | /// }; 403 | /// ``` 404 | pub use template_quote_impl::quote_configured; 405 | 406 | /// [`quote_spanned!`] macro emit `TokenTree` with specified 407 | /// `Span`. 408 | /// 409 | /// ```ignore 410 | /// use syn::Span; 411 | /// let span = Span::call_site(); 412 | /// let tokens = quote_spanned! {span => ... }; 413 | /// ``` 414 | pub use template_quote_impl::quote_spanned; 415 | 416 | pub use imp::Repeat; 417 | pub use quote::ToTokens; 418 | 419 | mod imp { 420 | use quote::ToTokens; 421 | use std::borrow::Borrow; 422 | use std::slice; 423 | 424 | // This trait is from `proc-quote` crate. 425 | pub unsafe trait Repeat { 426 | #[allow(non_snake_case)] 427 | #[doc(hidden)] 428 | fn __template_quote__as_repeat(self) -> T; 429 | 430 | #[allow(non_snake_case)] 431 | #[doc(hidden)] 432 | fn __template_quote_is_iterable(&self) -> bool; 433 | } 434 | 435 | unsafe impl> Repeat for I { 436 | fn __template_quote__as_repeat(self) -> I { 437 | self 438 | } 439 | 440 | fn __template_quote_is_iterable(&self) -> bool { 441 | true 442 | } 443 | } 444 | 445 | unsafe impl<'a, T: 'a, S: Borrow<[T]>> Repeat> for &'a S { 446 | fn __template_quote__as_repeat(self) -> slice::Iter<'a, T> { 447 | (*self).borrow().iter() 448 | } 449 | 450 | fn __template_quote_is_iterable(&self) -> bool { 451 | true 452 | } 453 | } 454 | 455 | unsafe impl<'a, T: ToTokens + 'a> Repeat> for &'a T { 456 | fn __template_quote__as_repeat(self) -> ToTokensRepeat<'a, T> { 457 | ToTokensRepeat(self) 458 | } 459 | 460 | fn __template_quote_is_iterable(&self) -> bool { 461 | false 462 | } 463 | } 464 | 465 | pub struct ToTokensRepeat<'a, T: ToTokens + 'a>(&'a T); 466 | impl<'a, T: ToTokens + 'a> Iterator for ToTokensRepeat<'a, T> { 467 | type Item = &'a T; 468 | fn next(&mut self) -> Option { 469 | Some(self.0) 470 | } 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /template_quote_impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template-quote-impl" 3 | version = "0.4.2" 4 | edition = "2021" 5 | authors = ["Yasuo Ozu "] 6 | description = "A new-fasioned quote! macro implementation with pretty template-engine like syntax" 7 | license = "MIT" 8 | keywords = ["syn", "quote", "proc-macro", "macros"] 9 | categories = ["development-tools::procedural-macro-helpers"] 10 | repository = "https://github.com/yasuo-ozu/template_quote" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "1.0.103", features = [ "full", "derive", "printing" ]} 17 | proc-macro2 = "1.0.47" 18 | quote = "1.0.21" 19 | proc-macro-error = "1.0.4" 20 | -------------------------------------------------------------------------------- /template_quote_impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate proc_macro2; 3 | extern crate syn; 4 | 5 | use proc_macro::TokenStream; 6 | use proc_macro2::TokenStream as TokenStream2; 7 | use proc_macro2::{Delimiter, Literal, Punct, Spacing, Span, TokenTree}; 8 | use proc_macro_error::ResultExt; 9 | use quote::{quote as qquote, quote_spanned as qquote_spanned, TokenStreamExt}; 10 | use std::collections::{HashSet, VecDeque}; 11 | use syn::{parse_quote, Expr, Ident, Path, Token}; 12 | 13 | /// The parser for `quote!` macro. 14 | /// 15 | /// this struct has some global configs to be initialized using 16 | /// `Default::default()` or `syn::parse::Parse::parse()`. User can specify 17 | /// configs using `quote_configured!` macro. 18 | struct ParseEnvironment { 19 | span: Expr, 20 | path_proc_macro2: Path, 21 | path_quote: Path, 22 | path_core: Path, 23 | id_stream: Ident, 24 | id_repeat: Ident, 25 | id_counter: Ident, 26 | } 27 | 28 | impl syn::parse::Parse for ParseEnvironment { 29 | fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { 30 | // Read comma-separated `key = value` fields 31 | let mut fields: Vec<(Ident, Expr)> = Vec::new(); 32 | while !input.is_empty() { 33 | let ident = input.parse()?; 34 | input.parse::()?; 35 | let expr = input.parse()?; 36 | fields.push((ident, expr)); 37 | if !input.is_empty() { 38 | input.parse::()?; 39 | } 40 | } 41 | let mut this: ParseEnvironment = Default::default(); 42 | for (id, expr) in fields { 43 | match (id.to_string().as_str(), expr) { 44 | ("proc_macro2", Expr::Path(pat)) => { 45 | this.path_proc_macro2 = pat.path; 46 | } 47 | ("quote", Expr::Path(pat)) => { 48 | this.path_quote = pat.path; 49 | } 50 | ("core", Expr::Path(pat)) => { 51 | this.path_core = pat.path; 52 | } 53 | ("span", expr) => { 54 | this.span = expr; 55 | } 56 | (key, _) => { 57 | return Err(input.error(&format!("Bad config name: {}", key))); 58 | } 59 | } 60 | } 61 | Ok(this) 62 | } 63 | } 64 | 65 | impl core::default::Default for ParseEnvironment { 66 | fn default() -> Self { 67 | Self { 68 | span: parse_quote! { ::proc_macro2::Span::call_site() }, 69 | path_proc_macro2: parse_quote! { ::proc_macro2 }, 70 | path_quote: parse_quote! { ::template_quote }, 71 | path_core: parse_quote! { ::core }, 72 | id_stream: parse_quote! { __template_quote_stream }, 73 | id_repeat: parse_quote! { __TemplateQuote_Repeat }, 74 | id_counter: parse_quote! { __template_quote_idcnt }, 75 | } 76 | } 77 | } 78 | 79 | fn eat_terminator(input: &mut VecDeque) -> bool { 80 | match (input.pop_front(), input.pop_front(), input.pop_front()) { 81 | (Some(TokenTree::Punct(p1)), Some(TokenTree::Punct(p2)), None) 82 | if p1.as_char() == '.' 83 | && p2.as_char() == '.' 84 | && p1.spacing() == Spacing::Joint 85 | && p2.spacing() == Spacing::Alone => 86 | { 87 | return true; 88 | } 89 | (Some(tt1), Some(tt2), Some(tt3)) => { 90 | input.push_front(tt3); 91 | input.push_front(tt2); 92 | input.push_front(tt1); 93 | } 94 | (Some(tt1), Some(tt2), None) => { 95 | input.push_front(tt2); 96 | input.push_front(tt1); 97 | } 98 | (Some(tt1), None, None) => { 99 | input.push_front(tt1); 100 | } 101 | _ => (), 102 | } 103 | false 104 | } 105 | fn eat_comma(input: &mut VecDeque) -> bool { 106 | match input.pop_front() { 107 | Some(TokenTree::Punct(p)) if p.as_char() == ',' && p.spacing() == Spacing::Alone => true, 108 | Some(tt) => { 109 | input.push_front(tt); 110 | false 111 | } 112 | _ => false, 113 | } 114 | } 115 | fn collect_ident(input: &mut VecDeque) -> Result, ()> { 116 | fn collect_paren_stream( 117 | mut input: VecDeque, 118 | ret: &mut Vec, 119 | ) -> Result<(), ()> { 120 | while input.len() > 0 && !eat_terminator(&mut input) { 121 | ret.extend(collect_ident(&mut input)?); 122 | if input.len() > 0 && !eat_comma(&mut input) { 123 | return Err(()); 124 | } 125 | } 126 | Ok(()) 127 | } 128 | let mut ret = Vec::new(); 129 | match input.pop_front() { 130 | // Parse `let (..) = ..` 131 | Some(TokenTree::Group(g)) 132 | if (g.delimiter() == Delimiter::Parenthesis || g.delimiter() == Delimiter::Bracket) => 133 | { 134 | collect_paren_stream(g.stream().into_iter().collect(), &mut ret)?; 135 | Ok(ret) 136 | } 137 | Some(TokenTree::Ident(id)) => { 138 | loop { 139 | match input.pop_front() { 140 | Some(TokenTree::Punct(colon)) 141 | if colon.as_char() == ':' && colon.spacing() == Spacing::Joint => 142 | { 143 | if matches!( 144 | input.pop_front(), 145 | Some(TokenTree::Punct(colon)) if colon.as_char() == ':' && colon.spacing() == Spacing::Alone 146 | ) && matches!(input.pop_front(), Some(TokenTree::Ident(_))) 147 | { 148 | continue; 149 | } else { 150 | return Err(()); 151 | } 152 | } 153 | Some(o) => input.push_front(o), 154 | None => (), 155 | } 156 | break; 157 | } 158 | match input.pop_front() { 159 | // Parse `let Ident ( .. ) = ..` 160 | Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => { 161 | collect_paren_stream(g.stream().into_iter().collect(), &mut ret)?; 162 | Ok(ret) 163 | } 164 | // Parse `let Ident { key: value, value2, ... } = ..` 165 | Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => { 166 | let mut inner: VecDeque = g.stream().into_iter().collect(); 167 | while inner.len() > 0 && !eat_terminator(&mut inner) { 168 | match inner.pop_front().unwrap() { 169 | TokenTree::Ident(key) => match (inner.pop_front(), inner.pop_front()) { 170 | (Some(TokenTree::Punct(colon)), Some(TokenTree::Ident(value))) 171 | if colon.as_char() == ':' 172 | && colon.spacing() == Spacing::Alone => 173 | { 174 | ret.push(value); 175 | } 176 | (item1, item2) => { 177 | if let Some(tt2) = item2 { 178 | inner.push_front(tt2); 179 | } 180 | if let Some(tt1) = item1 { 181 | inner.push_front(tt1); 182 | } 183 | ret.push(key) 184 | } 185 | }, 186 | _ => return Err(()), 187 | } 188 | if inner.len() > 0 && !eat_comma(&mut inner) { 189 | return Err(()); 190 | } 191 | } 192 | Ok(ret) 193 | } 194 | // Parse `let ident = ..` 195 | Some(o) => { 196 | input.push_front(o); 197 | ret.push(id); 198 | Ok(ret) 199 | } 200 | None => { 201 | ret.push(id); 202 | Ok(ret) 203 | } 204 | } 205 | } 206 | _ => Err(()), 207 | } 208 | } 209 | 210 | fn collect_ident_eq(input: &mut VecDeque) -> Result, ()> { 211 | let v = collect_ident(input)?; 212 | match input.pop_front() { 213 | Some(TokenTree::Punct(eq)) if eq.as_char() == '=' && eq.spacing() == Spacing::Alone => { 214 | Ok(v) 215 | } 216 | _ => Err(()), 217 | } 218 | } 219 | 220 | fn collect_punct_in_expr(mut stream: VecDeque) -> (Vec, VecDeque) { 221 | let mut output = VecDeque::new(); 222 | let mut ids = Vec::new(); 223 | while let Some(tt) = stream.pop_front() { 224 | match tt { 225 | TokenTree::Punct(punct) if punct.as_char() == '#' => match stream.pop_front() { 226 | Some(TokenTree::Ident(id)) => { 227 | ids.push(id.clone()); 228 | output.push_back(TokenTree::Ident(id)); 229 | } 230 | Some(o) => { 231 | output.push_back(TokenTree::Punct(punct)); 232 | output.push_back(o); 233 | } 234 | None => { 235 | output.push_back(TokenTree::Punct(punct)); 236 | } 237 | }, 238 | o => output.push_back(o), 239 | } 240 | } 241 | (ids, output) 242 | } 243 | 244 | impl ParseEnvironment { 245 | fn emit_ident(&self, ident: &Ident) -> TokenStream2 { 246 | let Self { 247 | span, 248 | path_proc_macro2, 249 | path_quote, 250 | id_stream, 251 | .. 252 | } = self; 253 | let s = ident.to_string(); 254 | if s.starts_with("r#") { 255 | let s = &s[2..]; 256 | qquote! { 257 | <_ as #path_quote::ToTokens>::to_tokens(&#path_proc_macro2::Ident::new_raw(#s, (#span)), &mut #id_stream); 258 | } 259 | } else { 260 | qquote! { 261 | <_ as #path_quote::ToTokens>::to_tokens(&#path_proc_macro2::Ident::new(#s, (#span)), &mut #id_stream); 262 | } 263 | } 264 | } 265 | 266 | fn emit_literal(&self, lit: &Literal) -> TokenStream2 { 267 | let Self { 268 | span, 269 | path_proc_macro2, 270 | id_stream, 271 | .. 272 | } = self; 273 | let s = lit.to_string(); 274 | qquote! { 275 | { 276 | let ts: #path_proc_macro2::TokenStream = #s.parse().expect("Invalid literal str"); 277 | #id_stream.extend(ts.into_iter().map(|mut t| { 278 | t.set_span(#span); 279 | t 280 | })); 281 | } 282 | } 283 | } 284 | 285 | fn emit_punct(&self, punct: &Punct) -> TokenStream2 { 286 | let Self { 287 | span, 288 | path_proc_macro2, 289 | path_quote, 290 | id_stream, 291 | .. 292 | } = self; 293 | let p = punct.as_char(); 294 | let spacing = match punct.spacing() { 295 | Spacing::Alone => qquote! {#path_proc_macro2::Spacing::Alone}, 296 | Spacing::Joint => qquote! {#path_proc_macro2::Spacing::Joint}, 297 | }; 298 | qquote! { 299 | <_ as #path_quote::ToTokens>::to_tokens(&{ 300 | let mut p = #path_proc_macro2::Punct::new(#p, #spacing); 301 | p.set_span(#span); 302 | p 303 | }, &mut #id_stream); 304 | } 305 | } 306 | 307 | fn emit_group(&self, delim: &Delimiter, inner: TokenStream2) -> TokenStream2 { 308 | let Self { 309 | span, 310 | path_proc_macro2, 311 | path_core, 312 | id_stream, 313 | .. 314 | } = self; 315 | let delim = match delim { 316 | Delimiter::Parenthesis => qquote! { #path_proc_macro2::Delimiter::Parenthesis }, 317 | Delimiter::Brace => qquote! { #path_proc_macro2::Delimiter::Brace }, 318 | Delimiter::Bracket => qquote! { #path_proc_macro2::Delimiter::Bracket }, 319 | Delimiter::None => qquote! { #path_proc_macro2::Delimiter::None }, 320 | }; 321 | qquote! { 322 | #id_stream.extend( 323 | #path_core::option::Option::Some( 324 | #path_proc_macro2::TokenTree::Group( 325 | { 326 | let mut g = #path_proc_macro2::Group::new(#delim, { 327 | let mut #id_stream = #path_proc_macro2::TokenStream::new(); 328 | { #inner } 329 | #id_stream 330 | }); 331 | g.set_span(#span); 332 | g 333 | } 334 | ) 335 | ) 336 | ); 337 | } 338 | } 339 | 340 | fn parse_conditional( 341 | &self, 342 | conditional: TokenStream2, 343 | input: VecDeque, 344 | vals: &mut HashSet, 345 | sep: Option, 346 | inline_expr_dict: &mut Vec<(Ident, TokenStream2, Span)>, 347 | ) -> TokenStream2 { 348 | fn parse_if_inner( 349 | mut cond: VecDeque, 350 | ) -> (Vec, Vec, Vec) { 351 | let mut bak: Vec<_> = cond.iter().cloned().collect(); 352 | match cond.pop_front() { 353 | Some(TokenTree::Ident(id_let)) if &id_let.to_string() == "let" => { 354 | let removing_ids = 355 | collect_ident_eq(&mut cond).expect("Bad format in if-let conditional"); 356 | bak.truncate(bak.len() - cond.len()); 357 | let (appending_ids, rem) = collect_punct_in_expr(cond); 358 | bak.extend(rem); 359 | (removing_ids, appending_ids, bak) 360 | } 361 | Some(o) => { 362 | cond.push_front(o); 363 | bak.truncate(bak.len() - cond.len()); 364 | let (appending_ids, rem) = collect_punct_in_expr(cond); 365 | bak.extend(rem); 366 | (vec![], appending_ids, bak) 367 | } 368 | None => panic!("if syntax is empty"), 369 | } 370 | } 371 | let mut cond: VecDeque = conditional.clone().into_iter().collect(); 372 | let cond_len = cond.len(); 373 | let (removing_ids, appending_ids, cond) = match (cond.pop_front(), sep.is_some()) { 374 | (Some(TokenTree::Ident(id)), false) if &id.to_string() == "if" => { 375 | let (removing_ids, appending_ids, rem) = parse_if_inner(cond); 376 | ( 377 | removing_ids, 378 | appending_ids, 379 | Some(TokenTree::Ident(id)).into_iter().chain(rem).collect(), 380 | ) 381 | } 382 | (Some(TokenTree::Ident(id)), false) if &id.to_string() == "else" => { 383 | match cond.pop_front() { 384 | // else if 385 | Some(TokenTree::Ident(id_if)) if &id_if.to_string() == "if" => { 386 | let (removing_ids, appending_ids, rem) = parse_if_inner(cond); 387 | ( 388 | removing_ids, 389 | appending_ids, 390 | vec![TokenTree::Ident(id), TokenTree::Ident(id_if)] 391 | .into_iter() 392 | .chain(rem) 393 | .collect(), 394 | ) 395 | } 396 | Some(_) => panic!("Bad format in else conditional"), 397 | None => (vec![], vec![], conditional), 398 | } 399 | } 400 | (Some(TokenTree::Ident(id)), false) if id.to_string() == "let" => { 401 | let removing_ids = collect_ident_eq(&mut cond).expect("Bad format in let binding"); 402 | let n = cond_len - cond.len(); 403 | let (appending_ids, rem) = collect_punct_in_expr(cond); 404 | let conditional: TokenStream2 = 405 | conditional.clone().into_iter().take(n).chain(rem).collect(); 406 | (removing_ids, appending_ids, qquote! {#conditional ;}) 407 | } 408 | (Some(TokenTree::Ident(id)), _) if id.to_string() == "while" => { 409 | match cond.pop_front() { 410 | Some(TokenTree::Ident(id_let)) if &id_let.to_string() == "let" => { 411 | let removing_ids = 412 | collect_ident_eq(&mut cond).expect("Bad format in while-let loop"); 413 | let n = cond_len - cond.len(); 414 | let (appending_ids, rem) = collect_punct_in_expr(cond); 415 | let conditional: TokenStream2 = 416 | conditional.clone().into_iter().take(n).chain(rem).collect(); 417 | (removing_ids, appending_ids, conditional) 418 | } 419 | Some(o) => { 420 | cond.push_front(o); 421 | let n = cond_len - cond.len(); 422 | let (appending_ids, rem) = collect_punct_in_expr(cond); 423 | let conditional: TokenStream2 = 424 | conditional.clone().into_iter().take(n).chain(rem).collect(); 425 | (vec![], appending_ids, conditional) 426 | } 427 | None => panic!("while syntax is empty"), 428 | } 429 | } 430 | (Some(TokenTree::Ident(id)), _) if id.to_string() == "for" => { 431 | match (collect_ident(&mut cond), cond.pop_front()) { 432 | (Ok(v), Some(TokenTree::Ident(id_in))) if &id_in.to_string() == "in" => { 433 | let n = cond_len - cond.len(); 434 | let (appending_ids, rem) = collect_punct_in_expr(cond); 435 | let conditional: TokenStream2 = 436 | conditional.clone().into_iter().take(n).chain(rem).collect(); 437 | (v, appending_ids, conditional) 438 | } 439 | _ => panic!("Bad format in for loop"), 440 | } 441 | } 442 | _ => panic!("Bad format in conditional"), 443 | }; 444 | let inner = self.parse_inner(input, vals, inline_expr_dict); 445 | for id in removing_ids { 446 | vals.remove(&id); 447 | } 448 | for id in appending_ids { 449 | vals.insert(id); 450 | } 451 | if let Some(sep) = sep { 452 | let code_sep = self.emit_punct(&sep); 453 | let id_counter = &self.id_counter; 454 | qquote! { 455 | { 456 | let mut #id_counter = false; 457 | #cond { 458 | if #id_counter { #code_sep } 459 | #id_counter = true; 460 | #inner 461 | } 462 | } 463 | } 464 | } else { 465 | qquote! { 466 | #cond { 467 | #inner 468 | } 469 | } 470 | } 471 | } 472 | 473 | fn parse_iteration( 474 | &self, 475 | input: VecDeque, 476 | vals: &mut HashSet, 477 | sep: Option, 478 | inline_expr_dict: &mut Vec<(Ident, TokenStream2, Span)>, 479 | ) -> TokenStream2 { 480 | let Self { 481 | path_quote, 482 | id_repeat, 483 | .. 484 | } = self; 485 | let debug_str = input 486 | .iter() 487 | .take(6) 488 | .map(|item| match item { 489 | TokenTree::Group(g) => match g.delimiter() { 490 | Delimiter::Parenthesis => "( .. )".to_owned(), 491 | Delimiter::Brace => "{ .. }".to_owned(), 492 | Delimiter::Bracket => "[ .. ]".to_owned(), 493 | Delimiter::None => "..".to_owned(), 494 | }, 495 | _ => format!("{}", item), 496 | }) 497 | .chain(if input.len() > 6 { 498 | Some("..".to_owned()) 499 | } else { 500 | None 501 | }) 502 | .collect::>() 503 | .join(" "); 504 | let mut inner_vals = HashSet::new(); 505 | let inner_output = self.parse_inner(input, &mut inner_vals, inline_expr_dict); 506 | let code_sep = sep.map(|sep| self.emit_punct(&Punct::new(sep.as_char(), Spacing::Alone))); 507 | let val_nam = code_sep 508 | .as_ref() 509 | .map(|_| self.id_counter.clone()) 510 | .into_iter() 511 | .collect::>(); 512 | vals.extend(inner_vals.iter().cloned()); 513 | let mut iter = inner_vals.iter(); 514 | let first = iter.next().expect("Iterative vals not found"); 515 | let idents_in_tuple = iter.clone().cloned().fold(qquote! {#first}, |prev, next| { 516 | qquote! { 517 | (#prev, #next) 518 | } 519 | }); 520 | let zip_iterators = iter 521 | .clone() 522 | .map(|ident| { 523 | qquote! { 524 | .zip(#ident .__template_quote__as_repeat()) 525 | } 526 | }) 527 | .collect::>(); 528 | let zip_iterator_checkers = inner_vals.iter().fold(qquote! {false}, |acc, ident| { 529 | qquote! {#acc || (&#ident).__template_quote_is_iterable()} 530 | }); 531 | qquote! { 532 | { 533 | #(let mut #val_nam = false;)* 534 | use #path_quote::Repeat as #id_repeat; 535 | if !(#zip_iterator_checkers) { 536 | ::core::panic!("Cannot iterate the group: #( {} )*", #debug_str); 537 | } 538 | for #idents_in_tuple in #first .__template_quote__as_repeat() #(#zip_iterators)* { 539 | #( 540 | if #val_nam { #code_sep } 541 | #val_nam = true; 542 | )* 543 | #inner_output 544 | } 545 | } 546 | } 547 | } 548 | 549 | fn parse(&self, input: TokenStream2) -> TokenStream2 { 550 | let Self { 551 | path_proc_macro2, 552 | id_stream, 553 | .. 554 | } = self; 555 | let mut hs = HashSet::new(); 556 | let mut dict = Vec::new(); 557 | let result = self.parse_inner(input.into_iter().collect(), &mut hs, &mut dict); 558 | let inline_vals_code = 559 | dict.into_iter() 560 | .fold(TokenStream2::new(), |acc, (id, inner, span)| { 561 | qquote_spanned! { span => 562 | #acc 563 | let #id = { #inner }; 564 | } 565 | }); 566 | qquote! { 567 | { 568 | let mut #id_stream= #path_proc_macro2::TokenStream::new(); 569 | #inline_vals_code 570 | { #result } 571 | #id_stream 572 | } 573 | } 574 | } 575 | 576 | fn parse_inner( 577 | &self, 578 | mut input: VecDeque, 579 | vals: &mut HashSet, 580 | inline_expr_dict: &mut Vec<(Ident, TokenStream2, Span)>, 581 | ) -> TokenStream2 { 582 | let Self { 583 | path_quote, 584 | id_stream, 585 | .. 586 | } = self; 587 | let mut output = TokenStream2::new(); 588 | while let Some(token) = input.pop_front() { 589 | match token { 590 | TokenTree::Group(group) => { 591 | let inner = group.stream().into_iter().collect(); 592 | let result = self.parse_inner(inner, vals, inline_expr_dict); 593 | let result = self.emit_group(&group.delimiter(), result); 594 | output.append_all(result); 595 | } 596 | TokenTree::Punct(punct) => match (punct.as_char(), input.pop_front()) { 597 | // # val 598 | ('#', Some(TokenTree::Ident(ident))) => { 599 | vals.insert(ident.clone()); 600 | output.append_all( 601 | qquote! { <_ as #path_quote::ToTokens>::to_tokens(&#ident, &mut #id_stream); }, 602 | ); 603 | } 604 | // # { ... } 605 | ('#', Some(TokenTree::Group(group))) 606 | if group.delimiter() == Delimiter::Brace => 607 | { 608 | let inner = group.stream().into_iter().collect::>(); 609 | // Check if the inner stream ends with ';' 610 | let is_expr = match inner.get(core::cmp::max(0, inner.len() - 1)) { 611 | Some(TokenTree::Punct(p)) if p.as_char() == ';' => false, 612 | _ => true, 613 | }; 614 | let (appending_ids, inner) = collect_punct_in_expr(inner); 615 | for id in appending_ids { 616 | vals.insert(id); 617 | } 618 | let stream: TokenStream2 = inner.into_iter().collect(); 619 | if is_expr { 620 | output.append_all(qquote! { 621 | <_ as #path_quote::ToTokens>::to_tokens(&{ 622 | #stream 623 | }, &mut #id_stream); 624 | }); 625 | } else { 626 | output.append_all(qquote! { 627 | { #stream } 628 | }); 629 | } 630 | } 631 | ('#', Some(TokenTree::Group(group))) 632 | if group.delimiter() == Delimiter::Parenthesis => 633 | { 634 | match input.pop_front() { 635 | // # ( ... ) { ... } 636 | Some(TokenTree::Group(group2)) 637 | if group2.delimiter() == Delimiter::Brace => 638 | { 639 | output.append_all(self.parse_conditional( 640 | group.stream().into(), 641 | group2.stream().into_iter().collect(), 642 | vals, 643 | None, 644 | inline_expr_dict, 645 | )); 646 | } 647 | // # ( ... ) * 648 | Some(TokenTree::Punct(punct)) if punct.as_char() == '*' => output 649 | .append_all(self.parse_iteration( 650 | group.stream().into_iter().collect(), 651 | vals, 652 | None, 653 | inline_expr_dict, 654 | )), 655 | Some(TokenTree::Punct(punct0)) => match input.pop_front() { 656 | // # ( ... ) [SEP] * 657 | Some(TokenTree::Punct(punct1)) if punct1.as_char() == '*' => output 658 | .append_all(self.parse_iteration( 659 | group.stream().into_iter().collect(), 660 | vals, 661 | Some(punct0), 662 | inline_expr_dict, 663 | )), 664 | // # ( ... ) [SEP] { ... } 665 | Some(TokenTree::Group(group2)) 666 | if group2.delimiter() == Delimiter::Brace => 667 | { 668 | output.append_all(self.parse_conditional( 669 | group.stream().into(), 670 | group2.stream().into_iter().collect(), 671 | vals, 672 | Some(punct0), 673 | inline_expr_dict, 674 | )); 675 | } 676 | o => { 677 | if let Some(o) = o { 678 | input.push_front(o); 679 | } 680 | input.push_front(TokenTree::Punct(punct0)); 681 | input.push_front(TokenTree::Group(group)); 682 | output.append_all(self.emit_punct(&punct)); 683 | } 684 | }, 685 | o => { 686 | if let Some(o) = o { 687 | input.push_front(o) 688 | } 689 | input.push_front(TokenTree::Group(group)); 690 | output.append_all(self.emit_punct(&punct)); 691 | } 692 | } 693 | } 694 | (_, o) => { 695 | if let Some(o) = o { 696 | input.push_front(o); 697 | } 698 | output.append_all(self.emit_punct(&punct)); 699 | } 700 | }, 701 | TokenTree::Ident(o) => output.append_all(self.emit_ident(&o)), 702 | TokenTree::Literal(o) => output.append_all(self.emit_literal(&o)), 703 | } 704 | } 705 | output 706 | } 707 | } 708 | 709 | #[proc_macro] 710 | pub fn quote(input: TokenStream) -> TokenStream { 711 | let env: ParseEnvironment = Default::default(); 712 | env.parse(input.into()).into() 713 | } 714 | 715 | #[proc_macro] 716 | pub fn quote_configured(input: TokenStream) -> TokenStream { 717 | let input0: TokenStream2 = input.into(); 718 | let mut input = VecDeque::new(); 719 | input.extend(input0.into_iter()); 720 | let env: ParseEnvironment = match input.pop_front() { 721 | Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => { 722 | syn::parse2(g.stream()).expect_or_abort("Bad config format") 723 | } 724 | _ => panic!("Bad config format"), 725 | }; 726 | match input.pop_front() { 727 | Some(TokenTree::Punct(p)) if p.as_char() == '=' && p.spacing() == Spacing::Joint => (), 728 | _ => panic!("Bad config format"), 729 | } 730 | match input.pop_front() { 731 | Some(TokenTree::Punct(p)) if p.as_char() == '>' && p.spacing() == Spacing::Alone => (), 732 | _ => panic!("Bad config format"), 733 | } 734 | let mut stream = TokenStream2::new(); 735 | stream.extend(input); 736 | env.parse(stream).into() 737 | } 738 | 739 | #[proc_macro] 740 | pub fn quote_spanned(input: TokenStream) -> TokenStream { 741 | let input0: TokenStream2 = input.into(); 742 | let mut input = VecDeque::new(); 743 | input.extend(input0.into_iter()); 744 | let mut span = TokenStream2::new(); // Tokens before '=>' 745 | loop { 746 | match input.pop_front() { 747 | Some(TokenTree::Punct(p)) if p.as_char() == '=' && p.spacing() == Spacing::Joint => { 748 | match input.pop_front() { 749 | Some(TokenTree::Punct(p)) 750 | if p.as_char() == '>' && p.spacing() == Spacing::Alone => 751 | { 752 | // Found '=>'. 753 | break; 754 | } 755 | Some(o) => { 756 | span.extend(Some(TokenTree::Punct(p))); 757 | input.push_front(o); 758 | } 759 | None => { 760 | span.extend(Some(TokenTree::Punct(p))); 761 | } 762 | } 763 | } 764 | Some(o) => span.extend(Some(o)), 765 | None => panic!("wrong quote_spanned format"), 766 | } 767 | } 768 | let mut env: ParseEnvironment = Default::default(); 769 | env.span = syn::parse2(span).expect_or_abort("Span must be expr"); 770 | let mut stream = TokenStream2::new(); 771 | stream.extend(input); 772 | env.parse(stream).into() 773 | } 774 | -------------------------------------------------------------------------------- /tests/conditional.rs: -------------------------------------------------------------------------------- 1 | extern crate template_quote; 2 | 3 | use template_quote::quote; 4 | 5 | #[test] 6 | fn test_conditional_if() { 7 | let v = vec![1, 10, 2, 13, 4, 19]; 8 | let tokens = quote! { 9 | #( 10 | #(if v >= &10){ 11 | #v 12 | } 13 | )* 14 | }; 15 | assert_eq!("10i32 13i32 19i32", tokens.to_string()); 16 | let tokens = quote! { 17 | #( 18 | #(if v >= &10){ 19 | #v 20 | } 21 | #(else){ 22 | -#v 23 | } 24 | )* 25 | }; 26 | assert_eq!("-1i32 10i32 -2i32 13i32 -4i32 19i32", tokens.to_string()); 27 | } 28 | 29 | #[test] 30 | fn test_conditional_for() { 31 | let v = vec![(1, "one"), (2, "two"), (3, "three")]; 32 | let tokens = quote! { 33 | #(for (i, name) in v),{ 34 | #i is #name 35 | } 36 | }; 37 | assert_eq!( 38 | "1i32 is \"one\" , 2i32 is \"two\" , 3i32 is \"three\"", 39 | tokens.to_string() 40 | ); 41 | let v = vec![vec![1, 2, 3], vec![4, 5, 6]]; 42 | let vv = vec![1]; 43 | let tokens = quote! { 44 | #( 45 | #(for inner in &v);{ 46 | [#( #inner ),*] 47 | } 48 | #vv 49 | )* 50 | }; 51 | assert_eq!( 52 | "[1i32 , 2i32 , 3i32] ; [4i32 , 5i32 , 6i32] 1i32", 53 | tokens.to_string() 54 | ); 55 | #[allow(unreachable_code)] 56 | let tokens = quote! { 57 | #(for inner in 0..10);{ 58 | { 59 | #inner 60 | #{break;} 61 | } 62 | } 63 | }; 64 | assert_eq!("", tokens.to_string()); 65 | } 66 | 67 | #[test] 68 | fn test_conditional_let() { 69 | let v = vec![1, 2, 3]; 70 | let b = vec![true]; 71 | let tokens = quote! { 72 | #( 73 | #(let (i, s) = (&v, v.iter().map(|i| i.to_string()).collect::>())){ 74 | #(#i is #s),* 75 | } 76 | #b 77 | )* 78 | }; 79 | assert_eq!( 80 | "1i32 is \"1\" , 2i32 is \"2\" , 3i32 is \"3\" true", 81 | tokens.to_string() 82 | ); 83 | #[allow(unused)] 84 | struct STuple(i32, i32); 85 | struct SStruct { 86 | a: i32, 87 | b: i32, 88 | _c: i32, 89 | } 90 | let vv = vec![true]; 91 | let tup = STuple(123, 456); 92 | let sct = SStruct { 93 | a: 789, 94 | b: 12, 95 | _c: 345, 96 | }; 97 | let tokens = quote! { 98 | #( 99 | #(let STuple(a, ..) = tup) { 100 | #a 101 | } 102 | #(let SStruct{ a: d, b, .. } = sct) { 103 | #d, #b 104 | #vv 105 | } 106 | )* 107 | }; 108 | assert_eq!("123i32 789i32 , 12i32 true", tokens.to_string()); 109 | } 110 | 111 | #[test] 112 | fn test_conditional_while() { 113 | let mut v = vec![1, 2, 3].into_iter(); 114 | let tokens = quote! { 115 | #(while let Some(i) = v.next()){ 116 | #i 117 | } 118 | }; 119 | assert_eq!("1i32 2i32 3i32", tokens.to_string()); 120 | let mut v = vec![1, 2, 3].into_iter(); 121 | let tokens = quote! { 122 | #(while let Some(i) = v.next()){ 123 | #i 124 | #{ 125 | if i > 1 { break; }; 126 | } 127 | } 128 | }; 129 | assert_eq!("1i32 2i32", tokens.to_string()); 130 | let mut v = vec![vec![1, 2, 3], vec![4, 5]].into_iter(); 131 | let tokens = quote! { 132 | { 133 | #(while let Some(i) = v.next()){ 134 | #(#i),* 135 | #{break;} 136 | } 137 | } 138 | }; 139 | assert_eq!("{ 1i32 , 2i32 , 3i32 }", tokens.to_string()); 140 | } 141 | 142 | #[test] 143 | fn test_break() { 144 | let v = vec![1, 2, 3]; 145 | let tokens = quote! { 146 | #( 147 | #v 148 | #{ if v >= &2 { break; }; } 149 | )* 150 | }; 151 | assert_eq!("1i32 2i32", tokens.to_string()); 152 | } 153 | 154 | #[test] 155 | fn test_punct() { 156 | let v = vec![vec![1, 2], vec![3]]; 157 | let tokens = quote! { 158 | #( 159 | #(for i in #v) { 160 | #i 161 | } 162 | ),* 163 | }; 164 | assert_eq!("1i32 2i32 , 3i32", tokens.to_string()); 165 | let tokens = quote! { 166 | #( 167 | #(for i in #v.iter().map(|a| a + 1)) { 168 | #i 169 | } 170 | ),* 171 | }; 172 | assert_eq!("2i32 3i32 , 4i32", tokens.to_string()); 173 | let v = vec![1, 2, 3]; 174 | let tokens = quote! { 175 | #( 176 | #(for i in 0..*#v) { 177 | #i 178 | } 179 | ),* 180 | }; 181 | assert_eq!("0i32 , 0i32 1i32 , 0i32 1i32 2i32", tokens.to_string()); 182 | let tokens = quote! { 183 | #( 184 | #{#v.to_string()} 185 | ),* 186 | }; 187 | assert_eq!("\"1\" , \"2\" , \"3\"", tokens.to_string()); 188 | let v = vec![1, 2, 3]; 189 | let tokens = quote! { 190 | #( 191 | #(if #v > &2) { 192 | + #v 193 | } 194 | #(else) { 195 | - #v 196 | } 197 | )* 198 | }; 199 | assert_eq!("- 1i32 - 2i32 + 3i32", tokens.to_string()); 200 | } 201 | 202 | #[test] 203 | fn test_complex_assign() { 204 | let v = vec![1, 2, 3]; 205 | let v = Some(v.as_slice()); 206 | let tokens = quote! { 207 | #(if let Option::Some([a, b, c]) = v) { 208 | #a - #b - #c 209 | } 210 | }; 211 | assert_eq!("1i32 - 2i32 - 3i32", tokens.to_string()); 212 | } 213 | -------------------------------------------------------------------------------- /tests/interpolation.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span, TokenStream, TokenTree}; 2 | use template_quote::quote; 3 | 4 | struct X; 5 | 6 | impl template_quote::ToTokens for X { 7 | fn to_tokens(&self, tokens: &mut TokenStream) { 8 | tokens.extend(Some(TokenTree::Ident(Ident::new("X", Span::call_site())))); 9 | } 10 | } 11 | 12 | #[test] 13 | fn test_ident() { 14 | let foo = Ident::new("Foo", Span::call_site()); 15 | let bar = Ident::new(&format!("Bar{}", 7), Span::call_site()); 16 | let tokens = quote!(struct #foo; enum #bar {}); 17 | let expected = "struct Foo ; enum Bar7 { }"; 18 | assert_eq!(expected, tokens.to_string()); 19 | } 20 | 21 | #[test] 22 | fn test_duplicate() { 23 | let ch = 'x'; 24 | 25 | let tokens = quote!(#ch #ch); 26 | 27 | let expected = "'x' 'x'"; 28 | assert_eq!(expected, tokens.to_string()); 29 | } 30 | 31 | #[test] 32 | fn test_substitution() { 33 | let x = X; 34 | let tokens = quote!(#x < #x > (#x) [#x] {#x}); 35 | 36 | let expected = "X < X > (X) [X] { X }"; 37 | 38 | assert_eq!(expected, tokens.to_string()); 39 | } 40 | 41 | #[test] 42 | fn test_advanced() { 43 | let generics = quote!( <'a, T> ); 44 | 45 | let where_clause = quote!( where T: Serialize ); 46 | 47 | let field_ty = quote!(String); 48 | 49 | let item_ty = quote!(Cow<'a, str>); 50 | 51 | let path = quote!(SomeTrait::serialize_with); 52 | 53 | let value = quote!(self.x); 54 | 55 | let tokens = quote! { 56 | struct SerializeWith #generics #where_clause { 57 | value: &'a #field_ty, 58 | phantom: ::std::marker::PhantomData<#item_ty>, 59 | } 60 | 61 | impl #generics ::serde::Serialize for SerializeWith #generics #where_clause { 62 | fn serialize(&self, s: &mut S) -> Result<(), S::Error> 63 | where S: ::serde::Serializer 64 | { 65 | #path(self.value, s) 66 | } 67 | } 68 | 69 | SerializeWith { 70 | value: #value, 71 | phantom: ::std::marker::PhantomData::<#item_ty>, 72 | } 73 | }; 74 | 75 | let expected = concat!( 76 | "struct SerializeWith < 'a , T > where T : Serialize { ", 77 | "value : & 'a String , ", 78 | "phantom : :: std :: marker :: PhantomData >, ", 79 | "} ", 80 | "impl < 'a , T > :: serde :: Serialize for SerializeWith < 'a , T > where T : Serialize { ", 81 | "fn serialize < S > (& self , s : & mut S) -> Result < () , S :: Error > ", 82 | "where S : :: serde :: Serializer ", 83 | "{ ", 84 | "SomeTrait :: serialize_with (self . value , s) ", 85 | "} ", 86 | "} ", 87 | "SerializeWith { ", 88 | "value : self . x , ", 89 | "phantom : :: std :: marker :: PhantomData :: >, ", 90 | "}" 91 | ); 92 | 93 | assert_eq!(expected, tokens.to_string()); 94 | } 95 | -------------------------------------------------------------------------------- /tests/repetition.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span, TokenStream, TokenTree}; 2 | use template_quote::quote; 3 | 4 | struct X; 5 | 6 | impl template_quote::ToTokens for X { 7 | fn to_tokens(&self, tokens: &mut TokenStream) { 8 | tokens.extend(Some(TokenTree::Ident(Ident::new("X", Span::call_site())))); 9 | } 10 | } 11 | 12 | #[test] 13 | fn test_repetition_simple() { 14 | let primes = &[X, X, X, X]; 15 | 16 | assert_eq!("X X X X", quote!(#(#primes)*).to_string()); 17 | 18 | assert_eq!("X , X , X , X ,", quote!(#(#primes,)*).to_string()); 19 | 20 | assert_eq!("X , X , X , X", quote!(#(#primes),*).to_string()); 21 | } 22 | 23 | #[test] 24 | fn test_repetition_two_vars() { 25 | let foo = vec!["a", "b"]; 26 | let bar = vec![true, false]; 27 | 28 | let tokens = quote! { 29 | #(#foo: #bar),* 30 | }; 31 | 32 | let expected = r#""a" : true , "b" : false"#; 33 | assert_eq!(expected, tokens.to_string()); 34 | } 35 | 36 | #[test] 37 | fn test_repetition_nested() { 38 | let nested = vec![vec!['a', 'b', 'c'], vec!['x', 'y', 'z']]; 39 | 40 | let tokens = quote! { 41 | #( 42 | #(#nested)* 43 | ),* 44 | }; 45 | 46 | let expected = "'a' 'b' 'c' , 'x' 'y' 'z'"; 47 | assert_eq!(expected, tokens.to_string()); 48 | } 49 | 50 | #[test] 51 | fn test_var_name_conflict() { 52 | // The implementation of `#(...),*` uses the variable `__i` but it should be 53 | // fine, if a little confusing when debugging. 54 | let __i = vec!['a', 'b']; 55 | let tokens = quote! { 56 | #(#__i),* 57 | }; 58 | let expected = "'a' , 'b'"; 59 | assert_eq!(expected, tokens.to_string()); 60 | } 61 | 62 | #[test] 63 | fn test_repetition_same_iter_twice() { 64 | let a = 1..=3; 65 | let b = quote! { 66 | #(#a #a)* 67 | }; 68 | assert_eq!("1i32 1i32 2i32 2i32 3i32 3i32", b.to_string()); 69 | } 70 | 71 | #[test] 72 | fn test_repetition_iterators() { 73 | let a = vec!["a", "b", "c"].into_iter(); 74 | let b = 2..6; 75 | let c = [5, 6, 7].into_iter().map(|_| X); 76 | let q = quote! { 77 | #(#a #b #c)* 78 | }; 79 | assert_eq!("\"a\" 2i32 X \"b\" 3i32 X \"c\" 4i32 X", q.to_string()); 80 | } 81 | 82 | #[test] 83 | fn test_repetition_slices() { 84 | let a = vec!["a", "b", "c"]; 85 | let b = &[2, 3, 4, 5]; 86 | let c = [5, 6, 7]; 87 | 88 | let q = quote! { 89 | #(#a #b #c)* 90 | }; 91 | assert_eq!( 92 | "\"a\" 2i32 5i32 \"b\" 3i32 6i32 \"c\" 4i32 7i32", 93 | q.to_string() 94 | ); 95 | } 96 | 97 | #[test] 98 | fn test_repetition_totokens() { 99 | let i = 1..3; // Iterator just to avoid infinite loop 100 | let a = 5; 101 | let b = "a"; 102 | let c = X; 103 | 104 | let q = quote! { 105 | #(#i #a #b #c)* 106 | }; 107 | assert_eq!("1i32 5i32 \"a\" X 2i32 5i32 \"a\" X", q.to_string()); 108 | } 109 | 110 | #[test] 111 | fn test_repetition_without_consuming() { 112 | // The following types should not be consumed by `quote!` 113 | let a = [1, 2, 3]; 114 | let b = vec!['a', 'b', 'c']; 115 | let c = 5.0; 116 | 117 | let q1 = quote! { 118 | #(#a #b #c)* #(#a #b #c)* // Usable in two different patterns 119 | }; 120 | let q2 = quote! { 121 | #(#a #b #c)* // Usable in a different `quote!` 122 | }; 123 | let d = (a[0], b[0], c); // Still usable in the following code 124 | 125 | assert_eq!( 126 | "1i32 'a' 5f64 2i32 'b' 5f64 3i32 'c' 5f64 1i32 'a' 5f64 2i32 'b' 5f64 3i32 'c' 5f64", 127 | q1.to_string() 128 | ); 129 | assert_eq!("1i32 'a' 5f64 2i32 'b' 5f64 3i32 'c' 5f64", q2.to_string()); 130 | assert_eq!(1, d.0); 131 | assert_eq!('a', d.1); 132 | assert_eq!(5.0, d.2); 133 | } 134 | 135 | #[test] 136 | fn test_repetition_trait_name_collision() { 137 | let a = 0..3; // Avoid infinite loop 138 | let x = X; 139 | 140 | let q = quote! { 141 | #(#a #x)* 142 | }; 143 | 144 | assert_eq!("0i32 X 1i32 X 2i32 X", q.to_string()); 145 | } 146 | 147 | #[test] 148 | #[ignore] // TODO(#7) trait fn collision 149 | fn test_repetition_trait_fn_collision() { 150 | 151 | // trait Repeat { 152 | // fn __template_quote__as_repeat(self) -> !; 153 | // } 154 | // impl Repeat for X { 155 | // fn __template_quote__as_repeat(self) -> ! { panic!("Wrong trait 156 | // called.") // } } 157 | 158 | // let a = 0..3; // Avoid infinite loop 159 | // let x = X; 160 | 161 | // let q = quote! { 162 | // #(#a #x)* 163 | // }; 164 | 165 | // assert_eq!("0i32 X 1i32 X 2i32 X", q.to_string()); 166 | } 167 | 168 | #[test] 169 | fn test_repetition_impl_fn_collision() { 170 | struct R; 171 | impl quote::ToTokens for R { 172 | fn to_tokens(&self, tokens: &mut TokenStream) { 173 | tokens.extend(quote!(X)); 174 | } 175 | } 176 | 177 | let a = 0..3; // Avoid infinite loop 178 | let r = R; 179 | 180 | let q = quote! { 181 | #(#a #r)* 182 | }; 183 | 184 | assert_eq!("0i32 X 1i32 X 2i32 X", q.to_string()); 185 | } 186 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use template_quote::quote; 3 | 4 | #[test] 5 | fn test_quote_impl() { 6 | let tokens = quote! { 7 | impl<'a, T: ToTokens> ToTokens for &'a T { 8 | fn to_tokens(&self, tokens: &mut TokenStream) { 9 | (**self).to_tokens(tokens) 10 | } 11 | } 12 | }; 13 | 14 | let expected = concat!( 15 | "impl < 'a , T : ToTokens > ToTokens for & 'a T { ", 16 | "fn to_tokens (& self , tokens : & mut TokenStream) { ", 17 | "(** self) . to_tokens (tokens) ", 18 | "} ", 19 | "}" 20 | ); 21 | 22 | assert_eq!(expected, tokens.to_string()); 23 | } 24 | 25 | #[test] 26 | fn test_var_name_conflict() { 27 | // The implementation of `quote!` uses the variable `__stream` but it should 28 | // fine, if a little confusing when debugging. 29 | let __stream = 'a'; 30 | let tokens = quote!( #__stream ); 31 | let expected = "'a'"; 32 | assert_eq!(expected, tokens.to_string()); 33 | } 34 | 35 | #[test] 36 | fn test_empty_quote() { 37 | let tokens = quote!(); 38 | assert_eq!("", tokens.to_string()); 39 | } 40 | 41 | #[test] 42 | fn test_append_tokens() { 43 | let mut a = quote!(a); 44 | let b = quote!(b); 45 | a.extend(b); 46 | assert_eq!("a b", a.to_string()); 47 | } 48 | 49 | #[test] 50 | fn test_closure() { 51 | fn field_i(i: usize) -> Ident { 52 | Ident::new(&format!("__field{}", i), Span::call_site()) 53 | } 54 | 55 | let fields = (0usize..3) 56 | .map(field_i as fn(_) -> _) 57 | .map(|var| quote!( #var )); 58 | 59 | let tokens = quote!( #(#fields)* ); 60 | assert_eq!("__field0 __field1 __field2", tokens.to_string()); 61 | } 62 | 63 | #[test] 64 | fn test_raw_ident() { 65 | let tokens = quote!(r#struct r#the); 66 | let expected = "r#struct r#the"; 67 | assert_eq!(expected, tokens.to_string()); 68 | } 69 | -------------------------------------------------------------------------------- /tests/types.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro2; 2 | extern crate template_quote; 3 | 4 | use std::borrow::Cow; 5 | 6 | use proc_macro2::{Ident, Span}; 7 | use template_quote::quote; 8 | 9 | #[test] 10 | fn test_integer() { 11 | let ii8 = -1i8; 12 | let ii16 = -1i16; 13 | let ii32 = -1i32; 14 | let ii64 = -1i64; 15 | let iisize = -1isize; 16 | let uu8 = 1u8; 17 | let uu16 = 1u16; 18 | let uu32 = 1u32; 19 | let uu64 = 1u64; 20 | let uusize = 1usize; 21 | 22 | let tokens = quote! { 23 | #ii8 #ii16 #ii32 #ii64 #iisize 24 | #uu8 #uu16 #uu32 #uu64 #uusize 25 | }; 26 | let expected = "- 1i8 - 1i16 - 1i32 - 1i64 - 1isize 1u8 1u16 1u32 1u64 1usize"; 27 | assert_eq!(expected, tokens.to_string()); 28 | } 29 | 30 | #[test] 31 | fn test_floating() { 32 | let e32 = 2.345f32; 33 | 34 | let e64 = 2.345f64; 35 | 36 | let tokens = quote! { 37 | #e32 38 | #e64 39 | }; 40 | let expected = concat!("2.345f32 2.345f64"); 41 | assert_eq!(expected, tokens.to_string()); 42 | } 43 | 44 | #[test] 45 | fn test_integer128() { 46 | let ii128 = -1i128; 47 | let uu128 = 1u128; 48 | 49 | let tokens = quote! { 50 | #ii128 #uu128 51 | }; 52 | let expected = "- 1i128 1u128"; 53 | assert_eq!(expected, tokens.to_string()); 54 | } 55 | 56 | #[test] 57 | fn test_char() { 58 | let zero = '\0'; 59 | let pound = '#'; 60 | let quote = '"'; 61 | let apost = '\''; 62 | let newline = '\n'; 63 | // ISSUE #19 https://github.com/Goncalerta/proc-quote/issues/19 64 | //let heart = '\u{2764}'; 65 | 66 | let tokens = quote! { 67 | #zero #pound #quote #apost #newline 68 | }; 69 | let expected = r#"'\0' '#' '"' '\'' '\n'"#; 70 | assert_eq!(expected, tokens.to_string()); 71 | } 72 | 73 | #[test] 74 | fn test_str() { 75 | let s = "\0 a 'b \" c"; 76 | let tokens = quote!(#s); 77 | let expected = r#""\0 a 'b \" c""#; 78 | assert_eq!(expected, tokens.to_string()); 79 | } 80 | 81 | #[test] 82 | fn test_string() { 83 | let s = "\0 a 'b \" c".to_string(); 84 | let tokens = quote!(#s); 85 | let expected = r#""\0 a 'b \" c""#; 86 | assert_eq!(expected, tokens.to_string()); 87 | } 88 | 89 | #[test] 90 | fn test_box_str() { 91 | let b = "str".to_owned().into_boxed_str(); 92 | let tokens = quote!( #b ); 93 | assert_eq!("\"str\"", tokens.to_string()); 94 | } 95 | 96 | #[test] 97 | fn test_cow() { 98 | let owned: Cow = Cow::Owned(Ident::new("owned", Span::call_site())); 99 | 100 | let ident = Ident::new("borrowed", Span::call_site()); 101 | let borrowed = Cow::Borrowed(&ident); 102 | 103 | let tokens = quote!( #owned #borrowed ); 104 | assert_eq!("owned borrowed", tokens.to_string()); 105 | } 106 | 107 | #[test] 108 | fn test_literal() { 109 | let tokens = quote! { 110 | "123" 12i34 56 'c' r#id r"abc" r#"def"# 111 | }; 112 | assert_eq!( 113 | "\"123\" 12i34 56 'c' r#id r\"abc\" r#\"def\"#", 114 | tokens.to_string() 115 | ); 116 | } 117 | --------------------------------------------------------------------------------