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
150extern crate proc_macro;
151
152mod attr;
153mod error;
154mod segment;
155
156use crate::attr::expand_attr;
157use crate::error::{Error, Result};
158use crate::segment::Segment;
159use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
160use std::char;
161use std::iter;
162use std::panic;
163
164#[proc_macro]
165pub 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]
186pub fn item(input: TokenStream) -> TokenStream {
187 paste(input)
188}
189
190#[doc(hidden)]
191#[proc_macro]
192pub fn expr(input: TokenStream) -> TokenStream {
193 paste(input)
194}
195
196fn 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)]
288enum Lookbehind {
289 JointColon,
290 DoubleColon,
291 Pound,
292 PoundBang,
293 Other,
294}
295
296// https://github.com/dtolnay/paste/issues/26
297fn 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
334fn 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
354fn 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
412fn 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