1 | //! [![github]](https://github.com/dtolnay/paste) [![crates-io]](https://crates.io/crates/paste) [![docs-rs]](https://docs.rs/paste) |
2 | //! |
3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github |
4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust |
5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs |
6 | //! |
7 | //! <br> |
8 | //! |
9 | //! The nightly-only [`concat_idents!`] macro in the Rust standard library is |
10 | //! notoriously underpowered in that its concatenated identifiers can only refer to |
11 | //! existing items, they can never be used to define something new. |
12 | //! |
13 | //! [`concat_idents!`]: https://doc.rust-lang.org/std/macro.concat_idents.html |
14 | //! |
15 | //! This crate provides a flexible way to paste together identifiers in a macro, |
16 | //! including using pasted identifiers to define new items. |
17 | //! |
18 | //! This approach works with any Rust compiler 1.31+. |
19 | //! |
20 | //! <br> |
21 | //! |
22 | //! # Pasting identifiers |
23 | //! |
24 | //! Within the `paste!` macro, identifiers inside `[<`...`>]` are pasted |
25 | //! together to form a single identifier. |
26 | //! |
27 | //! ``` |
28 | //! use paste::paste; |
29 | //! |
30 | //! paste! { |
31 | //! // Defines a const called `QRST`. |
32 | //! const [<Q R S T>]: &str = "success!" ; |
33 | //! } |
34 | //! |
35 | //! fn main() { |
36 | //! assert_eq!( |
37 | //! paste! { [<Q R S T>].len() }, |
38 | //! 8, |
39 | //! ); |
40 | //! } |
41 | //! ``` |
42 | //! |
43 | //! <br><br> |
44 | //! |
45 | //! # More elaborate example |
46 | //! |
47 | //! The next example shows a macro that generates accessor methods for some |
48 | //! struct fields. It demonstrates how you might find it useful to bundle a |
49 | //! paste invocation inside of a macro\_rules macro. |
50 | //! |
51 | //! ``` |
52 | //! use paste::paste; |
53 | //! |
54 | //! macro_rules! make_a_struct_and_getters { |
55 | //! ($name:ident { $($field:ident),* }) => { |
56 | //! // Define a struct. This expands to: |
57 | //! // |
58 | //! // pub struct S { |
59 | //! // a: String, |
60 | //! // b: String, |
61 | //! // c: String, |
62 | //! // } |
63 | //! pub struct $name { |
64 | //! $( |
65 | //! $field: String, |
66 | //! )* |
67 | //! } |
68 | //! |
69 | //! // Build an impl block with getters. This expands to: |
70 | //! // |
71 | //! // impl S { |
72 | //! // pub fn get_a(&self) -> &str { &self.a } |
73 | //! // pub fn get_b(&self) -> &str { &self.b } |
74 | //! // pub fn get_c(&self) -> &str { &self.c } |
75 | //! // } |
76 | //! paste! { |
77 | //! impl $name { |
78 | //! $( |
79 | //! pub fn [<get_ $field>](&self) -> &str { |
80 | //! &self.$field |
81 | //! } |
82 | //! )* |
83 | //! } |
84 | //! } |
85 | //! } |
86 | //! } |
87 | //! |
88 | //! make_a_struct_and_getters!(S { a, b, c }); |
89 | //! |
90 | //! fn call_some_getters(s: &S) -> bool { |
91 | //! s.get_a() == s.get_b() && s.get_c().is_empty() |
92 | //! } |
93 | //! # |
94 | //! # fn main() {} |
95 | //! ``` |
96 | //! |
97 | //! <br><br> |
98 | //! |
99 | //! # Case conversion |
100 | //! |
101 | //! Use `$var:lower` or `$var:upper` in the segment list to convert an |
102 | //! interpolated segment to lower- or uppercase as part of the paste. For |
103 | //! example, `[<ld_ $reg:lower _expr>]` would paste to `ld_bc_expr` if invoked |
104 | //! with $reg=`Bc`. |
105 | //! |
106 | //! Use `$var:snake` to convert CamelCase input to snake\_case. |
107 | //! Use `$var:camel` to convert snake\_case to CamelCase. |
108 | //! These compose, so for example `$var:snake:upper` would give you SCREAMING\_CASE. |
109 | //! |
110 | //! The precise Unicode conversions are as defined by [`str::to_lowercase`] and |
111 | //! [`str::to_uppercase`]. |
112 | //! |
113 | //! [`str::to_lowercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase |
114 | //! [`str::to_uppercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_uppercase |
115 | //! |
116 | //! <br> |
117 | //! |
118 | //! # Pasting documentation strings |
119 | //! |
120 | //! Within the `paste!` macro, arguments to a #\[doc ...\] attribute are |
121 | //! implicitly concatenated together to form a coherent documentation string. |
122 | //! |
123 | //! ``` |
124 | //! use paste::paste; |
125 | //! |
126 | //! macro_rules! method_new { |
127 | //! ($ret:ident) => { |
128 | //! paste! { |
129 | //! #[doc = "Create a new `" $ret "` object." ] |
130 | //! pub fn new() -> $ret { todo!() } |
131 | //! } |
132 | //! }; |
133 | //! } |
134 | //! |
135 | //! pub struct Paste {} |
136 | //! |
137 | //! method_new!(Paste); // expands to #[doc = "Create a new `Paste` object"] |
138 | //! ``` |
139 | |
140 | #![doc (html_root_url = "https://docs.rs/paste/1.0.14" )] |
141 | #![allow ( |
142 | clippy::derive_partial_eq_without_eq, |
143 | clippy::doc_markdown, |
144 | clippy::match_same_arms, |
145 | clippy::module_name_repetitions, |
146 | clippy::needless_doctest_main, |
147 | clippy::too_many_lines |
148 | )] |
149 | |
150 | extern crate proc_macro; |
151 | |
152 | mod attr; |
153 | mod error; |
154 | mod segment; |
155 | |
156 | use crate::attr::expand_attr; |
157 | use crate::error::{Error, Result}; |
158 | use crate::segment::Segment; |
159 | use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; |
160 | use std::char; |
161 | use std::iter; |
162 | use std::panic; |
163 | |
164 | #[proc_macro ] |
165 | pub fn paste(input: TokenStream) -> TokenStream { |
166 | let mut contains_paste: bool = false; |
167 | let flatten_single_interpolation: bool = true; |
168 | match expand( |
169 | input.clone(), |
170 | &mut contains_paste, |
171 | flatten_single_interpolation, |
172 | ) { |
173 | Ok(expanded: TokenStream) => { |
174 | if contains_paste { |
175 | expanded |
176 | } else { |
177 | input |
178 | } |
179 | } |
180 | Err(err: Error) => err.to_compile_error(), |
181 | } |
182 | } |
183 | |
184 | #[doc (hidden)] |
185 | #[proc_macro ] |
186 | pub fn item(input: TokenStream) -> TokenStream { |
187 | paste(input) |
188 | } |
189 | |
190 | #[doc (hidden)] |
191 | #[proc_macro ] |
192 | pub fn expr(input: TokenStream) -> TokenStream { |
193 | paste(input) |
194 | } |
195 | |
196 | fn expand( |
197 | input: TokenStream, |
198 | contains_paste: &mut bool, |
199 | flatten_single_interpolation: bool, |
200 | ) -> Result<TokenStream> { |
201 | let mut expanded = TokenStream::new(); |
202 | let mut lookbehind = Lookbehind::Other; |
203 | let mut prev_none_group = None::<Group>; |
204 | let mut tokens = input.into_iter().peekable(); |
205 | loop { |
206 | let token = tokens.next(); |
207 | if let Some(group) = prev_none_group.take() { |
208 | if match (&token, tokens.peek()) { |
209 | (Some(TokenTree::Punct(fst)), Some(TokenTree::Punct(snd))) => { |
210 | fst.as_char() == ':' && snd.as_char() == ':' && fst.spacing() == Spacing::Joint |
211 | } |
212 | _ => false, |
213 | } { |
214 | expanded.extend(group.stream()); |
215 | *contains_paste = true; |
216 | } else { |
217 | expanded.extend(iter::once(TokenTree::Group(group))); |
218 | } |
219 | } |
220 | match token { |
221 | Some(TokenTree::Group(group)) => { |
222 | let delimiter = group.delimiter(); |
223 | let content = group.stream(); |
224 | let span = group.span(); |
225 | if delimiter == Delimiter::Bracket && is_paste_operation(&content) { |
226 | let segments = parse_bracket_as_segments(content, span)?; |
227 | let pasted = segment::paste(&segments)?; |
228 | let tokens = pasted_to_tokens(pasted, span)?; |
229 | expanded.extend(tokens); |
230 | *contains_paste = true; |
231 | } else if flatten_single_interpolation |
232 | && delimiter == Delimiter::None |
233 | && is_single_interpolation_group(&content) |
234 | { |
235 | expanded.extend(content); |
236 | *contains_paste = true; |
237 | } else { |
238 | let mut group_contains_paste = false; |
239 | let is_attribute = delimiter == Delimiter::Bracket |
240 | && (lookbehind == Lookbehind::Pound || lookbehind == Lookbehind::PoundBang); |
241 | let mut nested = expand( |
242 | content, |
243 | &mut group_contains_paste, |
244 | flatten_single_interpolation && !is_attribute, |
245 | )?; |
246 | if is_attribute { |
247 | nested = expand_attr(nested, span, &mut group_contains_paste)?; |
248 | } |
249 | let group = if group_contains_paste { |
250 | let mut group = Group::new(delimiter, nested); |
251 | group.set_span(span); |
252 | *contains_paste = true; |
253 | group |
254 | } else { |
255 | group.clone() |
256 | }; |
257 | if delimiter != Delimiter::None { |
258 | expanded.extend(iter::once(TokenTree::Group(group))); |
259 | } else if lookbehind == Lookbehind::DoubleColon { |
260 | expanded.extend(group.stream()); |
261 | *contains_paste = true; |
262 | } else { |
263 | prev_none_group = Some(group); |
264 | } |
265 | } |
266 | lookbehind = Lookbehind::Other; |
267 | } |
268 | Some(TokenTree::Punct(punct)) => { |
269 | lookbehind = match punct.as_char() { |
270 | ':' if lookbehind == Lookbehind::JointColon => Lookbehind::DoubleColon, |
271 | ':' if punct.spacing() == Spacing::Joint => Lookbehind::JointColon, |
272 | '#' => Lookbehind::Pound, |
273 | '!' if lookbehind == Lookbehind::Pound => Lookbehind::PoundBang, |
274 | _ => Lookbehind::Other, |
275 | }; |
276 | expanded.extend(iter::once(TokenTree::Punct(punct))); |
277 | } |
278 | Some(other) => { |
279 | lookbehind = Lookbehind::Other; |
280 | expanded.extend(iter::once(other)); |
281 | } |
282 | None => return Ok(expanded), |
283 | } |
284 | } |
285 | } |
286 | |
287 | #[derive (PartialEq)] |
288 | enum Lookbehind { |
289 | JointColon, |
290 | DoubleColon, |
291 | Pound, |
292 | PoundBang, |
293 | Other, |
294 | } |
295 | |
296 | // https://github.com/dtolnay/paste/issues/26 |
297 | fn is_single_interpolation_group(input: &TokenStream) -> bool { |
298 | #[derive (PartialEq)] |
299 | enum State { |
300 | Init, |
301 | Ident, |
302 | Literal, |
303 | Apostrophe, |
304 | Lifetime, |
305 | Colon1, |
306 | Colon2, |
307 | } |
308 | |
309 | let mut state = State::Init; |
310 | for tt in input.clone() { |
311 | state = match (state, &tt) { |
312 | (State::Init, TokenTree::Ident(_)) => State::Ident, |
313 | (State::Init, TokenTree::Literal(_)) => State::Literal, |
314 | (State::Init, TokenTree::Punct(punct)) if punct.as_char() == ' \'' => State::Apostrophe, |
315 | (State::Apostrophe, TokenTree::Ident(_)) => State::Lifetime, |
316 | (State::Ident, TokenTree::Punct(punct)) |
317 | if punct.as_char() == ':' && punct.spacing() == Spacing::Joint => |
318 | { |
319 | State::Colon1 |
320 | } |
321 | (State::Colon1, TokenTree::Punct(punct)) |
322 | if punct.as_char() == ':' && punct.spacing() == Spacing::Alone => |
323 | { |
324 | State::Colon2 |
325 | } |
326 | (State::Colon2, TokenTree::Ident(_)) => State::Ident, |
327 | _ => return false, |
328 | }; |
329 | } |
330 | |
331 | state == State::Ident || state == State::Literal || state == State::Lifetime |
332 | } |
333 | |
334 | fn is_paste_operation(input: &TokenStream) -> bool { |
335 | let mut tokens: IntoIter = input.clone().into_iter(); |
336 | |
337 | match &tokens.next() { |
338 | Some(TokenTree::Punct(punct: &Punct)) if punct.as_char() == '<' => {} |
339 | _ => return false, |
340 | } |
341 | |
342 | let mut has_token: bool = false; |
343 | loop { |
344 | match &tokens.next() { |
345 | Some(TokenTree::Punct(punct: &Punct)) if punct.as_char() == '>' => { |
346 | return has_token && tokens.next().is_none(); |
347 | } |
348 | Some(_) => has_token = true, |
349 | None => return false, |
350 | } |
351 | } |
352 | } |
353 | |
354 | fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>> { |
355 | let mut tokens = input.into_iter().peekable(); |
356 | |
357 | match &tokens.next() { |
358 | Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {} |
359 | Some(wrong) => return Err(Error::new(wrong.span(), "expected `<`" )), |
360 | None => return Err(Error::new(scope, "expected `[< ... >]`" )), |
361 | } |
362 | |
363 | let mut segments = segment::parse(&mut tokens)?; |
364 | |
365 | match &tokens.next() { |
366 | Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {} |
367 | Some(wrong) => return Err(Error::new(wrong.span(), "expected `>`" )), |
368 | None => return Err(Error::new(scope, "expected `[< ... >]`" )), |
369 | } |
370 | |
371 | if let Some(unexpected) = tokens.next() { |
372 | return Err(Error::new( |
373 | unexpected.span(), |
374 | "unexpected input, expected `[< ... >]`" , |
375 | )); |
376 | } |
377 | |
378 | for segment in &mut segments { |
379 | if let Segment::String(string) = segment { |
380 | if string.value.starts_with("' \\u{" ) { |
381 | let hex = &string.value[4..string.value.len() - 2]; |
382 | if let Ok(unsigned) = u32::from_str_radix(hex, 16) { |
383 | if let Some(ch) = char::from_u32(unsigned) { |
384 | string.value.clear(); |
385 | string.value.push(ch); |
386 | continue; |
387 | } |
388 | } |
389 | } |
390 | if string.value.contains(&['#' , ' \\' , '.' , '+' ][..]) |
391 | || string.value.starts_with("b'" ) |
392 | || string.value.starts_with("b \"" ) |
393 | || string.value.starts_with("br \"" ) |
394 | { |
395 | return Err(Error::new(string.span, "unsupported literal" )); |
396 | } |
397 | let mut range = 0..string.value.len(); |
398 | if string.value.starts_with("r \"" ) { |
399 | range.start += 2; |
400 | range.end -= 1; |
401 | } else if string.value.starts_with(&['"' , ' \'' ][..]) { |
402 | range.start += 1; |
403 | range.end -= 1; |
404 | } |
405 | string.value = string.value[range].replace('-' , "_" ); |
406 | } |
407 | } |
408 | |
409 | Ok(segments) |
410 | } |
411 | |
412 | fn pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream> { |
413 | let mut tokens = TokenStream::new(); |
414 | |
415 | #[cfg (not(no_literal_fromstr))] |
416 | { |
417 | use proc_macro::{LexError, Literal}; |
418 | use std::str::FromStr; |
419 | |
420 | if pasted.starts_with(|ch: char| ch.is_ascii_digit()) { |
421 | let literal = match panic::catch_unwind(|| Literal::from_str(&pasted)) { |
422 | Ok(Ok(literal)) => TokenTree::Literal(literal), |
423 | Ok(Err(LexError { .. })) | Err(_) => { |
424 | return Err(Error::new( |
425 | span, |
426 | &format!("` {:?}` is not a valid literal" , pasted), |
427 | )); |
428 | } |
429 | }; |
430 | tokens.extend(iter::once(literal)); |
431 | return Ok(tokens); |
432 | } |
433 | } |
434 | |
435 | if pasted.starts_with(' \'' ) { |
436 | let mut apostrophe = TokenTree::Punct(Punct::new(' \'' , Spacing::Joint)); |
437 | apostrophe.set_span(span); |
438 | tokens.extend(iter::once(apostrophe)); |
439 | pasted.remove(0); |
440 | } |
441 | |
442 | let ident = match panic::catch_unwind(|| Ident::new(&pasted, span)) { |
443 | Ok(ident) => TokenTree::Ident(ident), |
444 | Err(_) => { |
445 | return Err(Error::new( |
446 | span, |
447 | &format!("` {:?}` is not a valid identifier" , pasted), |
448 | )); |
449 | } |
450 | }; |
451 | |
452 | tokens.extend(iter::once(ident)); |
453 | Ok(tokens) |
454 | } |
455 | |