blob: 81d418452361eed45d996dd3e5b1658df96db0e1 [file] [log] [blame]
use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
use std::iter;
use std::str::FromStr;
pub fn is_pasted_doc(input: &TokenStream) -> bool {
#[derive(PartialEq)]
enum State {
Init,
Doc,
Equal,
First,
Rest,
}
let mut state = State::Init;
for tt in input.clone() {
state = match (state, &tt) {
(State::Init, TokenTree::Ident(ident)) if ident.to_string() == "doc" => State::Doc,
(State::Doc, TokenTree::Punct(punct)) if punct.as_char() == '=' => State::Equal,
(State::Equal, tt) if is_stringlike(tt) => State::First,
(State::First, tt) | (State::Rest, tt) if is_stringlike(tt) => State::Rest,
_ => return false,
};
}
state == State::Rest
}
pub fn do_paste_doc(attr: &TokenStream, span: Span) -> TokenStream {
let mut expanded = TokenStream::new();
let mut tokens = attr.clone().into_iter();
expanded.extend(tokens.by_ref().take(2)); // `doc =`
let mut lit = String::new();
lit.push('"');
for token in tokens {
lit += &escaped_string_value(&token).unwrap();
}
lit.push('"');
let mut lit = TokenStream::from_str(&lit)
.unwrap()
.into_iter()
.next()
.unwrap();
lit.set_span(span);
expanded.extend(iter::once(lit));
expanded
}
fn is_stringlike(token: &TokenTree) -> bool {
escaped_string_value(token).is_some()
}
fn escaped_string_value(token: &TokenTree) -> Option<String> {
match token {
TokenTree::Ident(ident) => Some(ident.to_string()),
TokenTree::Literal(literal) => {
let mut repr = literal.to_string();
if repr.starts_with('b') || repr.starts_with('\'') {
None
} else if repr.starts_with('"') {
repr.truncate(repr.len() - 1);
repr.remove(0);
Some(repr)
} else if repr.starts_with('r') {
let begin = repr.find('"').unwrap() + 1;
let end = repr.rfind('"').unwrap();
let mut escaped = String::new();
for ch in repr[begin..end].chars() {
escaped.extend(ch.escape_default());
}
Some(escaped)
} else {
Some(repr)
}
}
TokenTree::Group(group) => {
if group.delimiter() != Delimiter::None {
return None;
}
let mut inner = group.stream().into_iter();
let first = inner.next()?;
if inner.next().is_none() {
escaped_string_value(&first)
} else {
None
}
}
TokenTree::Punct(_) => None,
}
}