| //! [![github]](https://github.com/dtolnay/paste) [![crates-io]](https://crates.io/crates/paste) [![docs-rs]](https://docs.rs/paste) |
| //! |
| //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github |
| //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust |
| //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs |
| //! |
| //! <br> |
| //! |
| //! The nightly-only [`concat_idents!`] macro in the Rust standard library is |
| //! notoriously underpowered in that its concatenated identifiers can only refer to |
| //! existing items, they can never be used to define something new. |
| //! |
| //! [`concat_idents!`]: https://doc.rust-lang.org/std/macro.concat_idents.html |
| //! |
| //! This crate provides a flexible way to paste together identifiers in a macro, |
| //! including using pasted identifiers to define new items. |
| //! |
| //! This approach works with any Rust compiler 1.31+. |
| //! |
| //! <br> |
| //! |
| //! # Pasting identifiers |
| //! |
| //! Within the `paste!` macro, identifiers inside `[<`...`>]` are pasted |
| //! together to form a single identifier. |
| //! |
| //! ``` |
| //! use paste::paste; |
| //! |
| //! paste! { |
| //! // Defines a const called `QRST`. |
| //! const [<Q R S T>]: &str = "success!"; |
| //! } |
| //! |
| //! fn main() { |
| //! assert_eq!( |
| //! paste! { [<Q R S T>].len() }, |
| //! 8, |
| //! ); |
| //! } |
| //! ``` |
| //! |
| //! <br><br> |
| //! |
| //! # More elaborate example |
| //! |
| //! The next example shows a macro that generates accessor methods for some |
| //! struct fields. It demonstrates how you might find it useful to bundle a |
| //! paste invocation inside of a macro\_rules macro. |
| //! |
| //! ``` |
| //! use paste::paste; |
| //! |
| //! macro_rules! make_a_struct_and_getters { |
| //! ($name:ident { $($field:ident),* }) => { |
| //! // Define a struct. This expands to: |
| //! // |
| //! // pub struct S { |
| //! // a: String, |
| //! // b: String, |
| //! // c: String, |
| //! // } |
| //! pub struct $name { |
| //! $( |
| //! $field: String, |
| //! )* |
| //! } |
| //! |
| //! // Build an impl block with getters. This expands to: |
| //! // |
| //! // impl S { |
| //! // pub fn get_a(&self) -> &str { &self.a } |
| //! // pub fn get_b(&self) -> &str { &self.b } |
| //! // pub fn get_c(&self) -> &str { &self.c } |
| //! // } |
| //! paste! { |
| //! impl $name { |
| //! $( |
| //! pub fn [<get_ $field>](&self) -> &str { |
| //! &self.$field |
| //! } |
| //! )* |
| //! } |
| //! } |
| //! } |
| //! } |
| //! |
| //! make_a_struct_and_getters!(S { a, b, c }); |
| //! |
| //! fn call_some_getters(s: &S) -> bool { |
| //! s.get_a() == s.get_b() && s.get_c().is_empty() |
| //! } |
| //! # |
| //! # fn main() {} |
| //! ``` |
| //! |
| //! <br><br> |
| //! |
| //! # Case conversion |
| //! |
| //! Use `$var:lower` or `$var:upper` in the segment list to convert an |
| //! interpolated segment to lower- or uppercase as part of the paste. For |
| //! example, `[<ld_ $reg:lower _expr>]` would paste to `ld_bc_expr` if invoked |
| //! with $reg=`Bc`. |
| //! |
| //! Use `$var:snake` to convert CamelCase input to snake\_case. |
| //! Use `$var:camel` to convert snake\_case to CamelCase. |
| //! These compose, so for example `$var:snake:upper` would give you SCREAMING\_CASE. |
| //! |
| //! The precise Unicode conversions are as defined by [`str::to_lowercase`] and |
| //! [`str::to_uppercase`]. |
| //! |
| //! [`str::to_lowercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase |
| //! [`str::to_uppercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_uppercase |
| //! |
| //! <br> |
| //! |
| //! # Pasting documentation strings |
| //! |
| //! Within the `paste!` macro, arguments to a #\[doc ...\] attribute are |
| //! implicitly concatenated together to form a coherent documentation string. |
| //! |
| //! ``` |
| //! use paste::paste; |
| //! |
| //! macro_rules! method_new { |
| //! ($ret:ident) => { |
| //! paste! { |
| //! #[doc = "Create a new `" $ret "` object."] |
| //! pub fn new() -> $ret { todo!() } |
| //! } |
| //! }; |
| //! } |
| //! |
| //! pub struct Paste {} |
| //! |
| //! method_new!(Paste); // expands to #[doc = "Create a new `Paste` object"] |
| //! ``` |
| |
| #![doc(html_root_url = "https://docs.rs/paste/1.0.12")] |
| #![allow( |
| clippy::derive_partial_eq_without_eq, |
| clippy::doc_markdown, |
| clippy::match_same_arms, |
| clippy::module_name_repetitions, |
| clippy::needless_doctest_main, |
| clippy::too_many_lines |
| )] |
| |
| extern crate proc_macro; |
| |
| mod attr; |
| mod error; |
| mod segment; |
| |
| use crate::attr::expand_attr; |
| use crate::error::{Error, Result}; |
| use crate::segment::Segment; |
| use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; |
| use std::char; |
| use std::iter; |
| use std::panic; |
| |
| #[proc_macro] |
| pub fn paste(input: TokenStream) -> TokenStream { |
| let mut contains_paste = false; |
| let flatten_single_interpolation = true; |
| match expand( |
| input.clone(), |
| &mut contains_paste, |
| flatten_single_interpolation, |
| ) { |
| Ok(expanded) => { |
| if contains_paste { |
| expanded |
| } else { |
| input |
| } |
| } |
| Err(err) => err.to_compile_error(), |
| } |
| } |
| |
| #[doc(hidden)] |
| #[proc_macro] |
| pub fn item(input: TokenStream) -> TokenStream { |
| paste(input) |
| } |
| |
| #[doc(hidden)] |
| #[proc_macro] |
| pub fn expr(input: TokenStream) -> TokenStream { |
| paste(input) |
| } |
| |
| fn expand( |
| input: TokenStream, |
| contains_paste: &mut bool, |
| flatten_single_interpolation: bool, |
| ) -> Result<TokenStream> { |
| let mut expanded = TokenStream::new(); |
| let mut lookbehind = Lookbehind::Other; |
| let mut prev_none_group = None::<Group>; |
| let mut tokens = input.into_iter().peekable(); |
| loop { |
| let token = tokens.next(); |
| if let Some(group) = prev_none_group.take() { |
| if match (&token, tokens.peek()) { |
| (Some(TokenTree::Punct(fst)), Some(TokenTree::Punct(snd))) => { |
| fst.as_char() == ':' && snd.as_char() == ':' && fst.spacing() == Spacing::Joint |
| } |
| _ => false, |
| } { |
| expanded.extend(group.stream()); |
| *contains_paste = true; |
| } else { |
| expanded.extend(iter::once(TokenTree::Group(group))); |
| } |
| } |
| match token { |
| Some(TokenTree::Group(group)) => { |
| let delimiter = group.delimiter(); |
| let content = group.stream(); |
| let span = group.span(); |
| if delimiter == Delimiter::Bracket && is_paste_operation(&content) { |
| let segments = parse_bracket_as_segments(content, span)?; |
| let pasted = segment::paste(&segments)?; |
| let tokens = pasted_to_tokens(pasted, span)?; |
| expanded.extend(tokens); |
| *contains_paste = true; |
| } else if flatten_single_interpolation |
| && delimiter == Delimiter::None |
| && is_single_interpolation_group(&content) |
| { |
| expanded.extend(content); |
| *contains_paste = true; |
| } else { |
| let mut group_contains_paste = false; |
| let is_attribute = delimiter == Delimiter::Bracket |
| && (lookbehind == Lookbehind::Pound || lookbehind == Lookbehind::PoundBang); |
| let mut nested = expand( |
| content, |
| &mut group_contains_paste, |
| flatten_single_interpolation && !is_attribute, |
| )?; |
| if is_attribute { |
| nested = expand_attr(nested, span, &mut group_contains_paste)?; |
| } |
| let group = if group_contains_paste { |
| let mut group = Group::new(delimiter, nested); |
| group.set_span(span); |
| *contains_paste = true; |
| group |
| } else { |
| group.clone() |
| }; |
| if delimiter != Delimiter::None { |
| expanded.extend(iter::once(TokenTree::Group(group))); |
| } else if lookbehind == Lookbehind::DoubleColon { |
| expanded.extend(group.stream()); |
| *contains_paste = true; |
| } else { |
| prev_none_group = Some(group); |
| } |
| } |
| lookbehind = Lookbehind::Other; |
| } |
| Some(TokenTree::Punct(punct)) => { |
| lookbehind = match punct.as_char() { |
| ':' if lookbehind == Lookbehind::JointColon => Lookbehind::DoubleColon, |
| ':' if punct.spacing() == Spacing::Joint => Lookbehind::JointColon, |
| '#' => Lookbehind::Pound, |
| '!' if lookbehind == Lookbehind::Pound => Lookbehind::PoundBang, |
| _ => Lookbehind::Other, |
| }; |
| expanded.extend(iter::once(TokenTree::Punct(punct))); |
| } |
| Some(other) => { |
| lookbehind = Lookbehind::Other; |
| expanded.extend(iter::once(other)); |
| } |
| None => return Ok(expanded), |
| } |
| } |
| } |
| |
| #[derive(PartialEq)] |
| enum Lookbehind { |
| JointColon, |
| DoubleColon, |
| Pound, |
| PoundBang, |
| Other, |
| } |
| |
| // https://github.com/dtolnay/paste/issues/26 |
| fn is_single_interpolation_group(input: &TokenStream) -> bool { |
| #[derive(PartialEq)] |
| enum State { |
| Init, |
| Ident, |
| Literal, |
| Apostrophe, |
| Lifetime, |
| Colon1, |
| Colon2, |
| } |
| |
| let mut state = State::Init; |
| for tt in input.clone() { |
| state = match (state, &tt) { |
| (State::Init, TokenTree::Ident(_)) => State::Ident, |
| (State::Init, TokenTree::Literal(_)) => State::Literal, |
| (State::Init, TokenTree::Punct(punct)) if punct.as_char() == '\'' => State::Apostrophe, |
| (State::Apostrophe, TokenTree::Ident(_)) => State::Lifetime, |
| (State::Ident, TokenTree::Punct(punct)) |
| if punct.as_char() == ':' && punct.spacing() == Spacing::Joint => |
| { |
| State::Colon1 |
| } |
| (State::Colon1, TokenTree::Punct(punct)) |
| if punct.as_char() == ':' && punct.spacing() == Spacing::Alone => |
| { |
| State::Colon2 |
| } |
| (State::Colon2, TokenTree::Ident(_)) => State::Ident, |
| _ => return false, |
| }; |
| } |
| |
| state == State::Ident || state == State::Literal || state == State::Lifetime |
| } |
| |
| fn is_paste_operation(input: &TokenStream) -> bool { |
| let mut tokens = input.clone().into_iter(); |
| |
| match &tokens.next() { |
| Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {} |
| _ => return false, |
| } |
| |
| let mut has_token = false; |
| loop { |
| match &tokens.next() { |
| Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => { |
| return has_token && tokens.next().is_none(); |
| } |
| Some(_) => has_token = true, |
| None => return false, |
| } |
| } |
| } |
| |
| fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>> { |
| let mut tokens = input.into_iter().peekable(); |
| |
| match &tokens.next() { |
| Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {} |
| Some(wrong) => return Err(Error::new(wrong.span(), "expected `<`")), |
| None => return Err(Error::new(scope, "expected `[< ... >]`")), |
| } |
| |
| let mut segments = segment::parse(&mut tokens)?; |
| |
| match &tokens.next() { |
| Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {} |
| Some(wrong) => return Err(Error::new(wrong.span(), "expected `>`")), |
| None => return Err(Error::new(scope, "expected `[< ... >]`")), |
| } |
| |
| if let Some(unexpected) = tokens.next() { |
| return Err(Error::new( |
| unexpected.span(), |
| "unexpected input, expected `[< ... >]`", |
| )); |
| } |
| |
| for segment in &mut segments { |
| if let Segment::String(string) = segment { |
| if string.value.starts_with("'\\u{") { |
| let hex = &string.value[4..string.value.len() - 2]; |
| if let Ok(unsigned) = u32::from_str_radix(hex, 16) { |
| if let Some(ch) = char::from_u32(unsigned) { |
| string.value.clear(); |
| string.value.push(ch); |
| continue; |
| } |
| } |
| } |
| if string.value.contains(&['#', '\\', '.', '+'][..]) |
| || string.value.starts_with("b'") |
| || string.value.starts_with("b\"") |
| || string.value.starts_with("br\"") |
| { |
| return Err(Error::new(string.span, "unsupported literal")); |
| } |
| let mut range = 0..string.value.len(); |
| if string.value.starts_with("r\"") { |
| range.start += 2; |
| range.end -= 1; |
| } else if string.value.starts_with(&['"', '\''][..]) { |
| range.start += 1; |
| range.end -= 1; |
| } |
| string.value = string.value[range].replace('-', "_"); |
| } |
| } |
| |
| Ok(segments) |
| } |
| |
| fn pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream> { |
| let mut tokens = TokenStream::new(); |
| |
| #[cfg(not(no_literal_fromstr))] |
| { |
| use proc_macro::{LexError, Literal}; |
| use std::str::FromStr; |
| |
| if pasted.starts_with(|ch: char| ch.is_ascii_digit()) { |
| let literal = match panic::catch_unwind(|| Literal::from_str(&pasted)) { |
| Ok(Ok(literal)) => TokenTree::Literal(literal), |
| Ok(Err(LexError { .. })) | Err(_) => { |
| return Err(Error::new( |
| span, |
| &format!("`{:?}` is not a valid literal", pasted), |
| )); |
| } |
| }; |
| tokens.extend(iter::once(literal)); |
| return Ok(tokens); |
| } |
| } |
| |
| if pasted.starts_with('\'') { |
| let mut apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint)); |
| apostrophe.set_span(span); |
| tokens.extend(iter::once(apostrophe)); |
| pasted.remove(0); |
| } |
| |
| let ident = match panic::catch_unwind(|| Ident::new(&pasted, span)) { |
| Ok(ident) => TokenTree::Ident(ident), |
| Err(_) => { |
| return Err(Error::new( |
| span, |
| &format!("`{:?}` is not a valid identifier", pasted), |
| )); |
| } |
| }; |
| |
| tokens.extend(iter::once(ident)); |
| Ok(tokens) |
| } |