aboutsummaryrefslogtreecommitdiff
path: root/src/doc.rs
blob: 5fa2ad9879b52ac5eff2772dee7d929a1e6f4de2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use crate::error::Result;
use crate::segment::{self, Segment};
use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
use std::iter;
use std::mem;
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) -> Result<TokenStream> {
    let mut expanded = TokenStream::new();
    let mut tokens = attr.clone().into_iter().peekable();
    expanded.extend(tokens.by_ref().take(2)); // `doc =`

    let mut segments = segment::parse(&mut tokens)?;

    for segment in &mut segments {
        if let Segment::String(string) = segment {
            if let Some(open_quote) = string.value.find('"') {
                if open_quote == 0 {
                    string.value.truncate(string.value.len() - 1);
                    string.value.remove(0);
                } else {
                    let begin = open_quote + 1;
                    let end = string.value.rfind('"').unwrap();
                    let raw_string = mem::replace(&mut string.value, String::new());
                    for ch in raw_string[begin..end].chars() {
                        string.value.extend(ch.escape_default());
                    }
                }
            }
        }
    }

    let mut lit = segment::paste(&segments)?;
    lit.insert(0, '"');
    lit.push('"');

    let mut lit = TokenStream::from_str(&lit)
        .unwrap()
        .into_iter()
        .next()
        .unwrap();
    lit.set_span(span);
    expanded.extend(iter::once(lit));
    Ok(expanded)
}

fn is_stringlike(token: &TokenTree) -> bool {
    match token {
        TokenTree::Ident(_) => true,
        TokenTree::Literal(literal) => {
            let repr = literal.to_string();
            !repr.starts_with('b') && !repr.starts_with('\'')
        }
        TokenTree::Group(group) => {
            if group.delimiter() != Delimiter::None {
                return false;
            }
            let mut inner = group.stream().into_iter();
            match inner.next() {
                Some(first) => inner.next().is_none() && is_stringlike(&first),
                None => false,
            }
        }
        TokenTree::Punct(punct) => punct.as_char() == '\'' || punct.as_char() == ':',
    }
}