parse_suffix/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro::{
4    Delimiter, Group, Ident, Literal, Punct, Spacing::*, Span, TokenStream,
5    TokenTree,
6};
7
8#[must_use]
9fn stream<I>(iter: I) -> TokenStream
10where I: IntoIterator,
11      TokenStream: FromIterator<I::Item>,
12{
13    TokenStream::from_iter(iter)
14}
15
16fn err<T>(msg: &str, span: Span) -> Result<T, TokenStream> {
17    let s = |mut t: TokenTree| {
18        t.set_span(span);
19        t
20    };
21    Err(stream([
22        s(Punct::new(':', Joint).into()),
23        s(Punct::new(':', Joint).into()),
24        s(Ident::new("core", span).into()),
25        s(Punct::new(':', Joint).into()),
26        s(Punct::new(':', Joint).into()),
27        s(Ident::new("compile_error", span).into()),
28        s(Punct::new('!', Joint).into()),
29        s(Group::new(Delimiter::Brace, stream([
30            s(Literal::string(msg).into()),
31        ])).into()),
32    ]))
33}
34
35fn split_suffix(s: &str, span: Span) -> Result<(&str, &str), TokenStream> {
36    if s.starts_with('"') {
37        let i = s.rfind('"').unwrap();
38        return Ok(s.split_at(i+1));
39    }
40    if !s.starts_with('r') {
41        return err("invalid string literal", span);
42    }
43    let i = s.rfind(['"', '#']).unwrap();
44    Ok(s.split_at(i+1))
45}
46
47/// Process the string suffix as `.parse::<suffix>().unwrap()`
48///
49/// # Examples
50/// ```
51/// use std::{net::Ipv4Addr, path::PathBuf};
52///
53/// #[parse_suffix::parse_string_suffix]
54/// fn test() {
55///     assert_eq!("23"i32, 23);
56///     assert_eq!("23"PathBuf, PathBuf::from("23"));
57///     assert_eq!("true"bool, true);
58///     assert_eq!("false"bool, false);
59///     assert_eq!("192.168.1.1"Ipv4Addr, Ipv4Addr::new(192, 168, 1, 1));
60///     assert_eq!(r"192.168.1.1"Ipv4Addr, Ipv4Addr::new(192, 168, 1, 1));
61///     assert_eq!(r#"192.168.1.1"#Ipv4Addr, Ipv4Addr::new(192, 168, 1, 1));
62/// }
63/// # test()
64/// ```
65#[proc_macro_attribute]
66pub fn parse_string_suffix(attr: TokenStream, item: TokenStream) -> TokenStream {
67    if let Some(attr) = attr.into_iter().next() {
68        return err::<()>("invalid input", attr.span()).unwrap_err();
69    }
70    item.into_iter()
71        .map(do_token)
72        .collect()
73}
74
75fn gen_parse(str: &str, suf: &str, span: Span) -> TokenTree {
76    let s = |mut tt1: TokenTree| {
77        tt1.set_span(span);
78        tt1
79    };
80    s(Group::new(Delimiter::None, stream([
81        s(str.parse::<Literal>().unwrap().into()),
82        s(Punct::new('.', Joint).into()),
83        Ident::new("parse", span).into(),
84        s(Punct::new(':', Joint).into()),
85        s(Punct::new(':', Joint).into()),
86        s(Punct::new('<', Joint).into()),
87        Ident::new(suf, span).into(),
88        s(Punct::new('>', Joint).into()),
89        s(Group::new(Delimiter::Parenthesis, TokenStream::new()).into()),
90        s(Punct::new('.', Joint).into()),
91        Ident::new("unwrap", span).into(),
92        s(Group::new(Delimiter::Parenthesis, TokenStream::new()).into()),
93    ])).into())
94}
95
96fn do_token(tt: TokenTree) -> TokenTree {
97    let s = |mut tt1: TokenTree| {
98        tt1.set_span(tt.span());
99        tt1
100    };
101    match tt {
102        TokenTree::Group(ref group) => {
103            let t = parse_string_suffix(TokenStream::new(), group.stream());
104            s(Group::new(group.delimiter(), t).into())
105        },
106        TokenTree::Ident(_) => tt,
107        TokenTree::Punct(_) => tt,
108        TokenTree::Literal(ref lit) => {
109            match split_suffix(&lit.to_string(), lit.span()) {
110                Ok((str, suf)) if !suf.is_empty() => {
111                    gen_parse(str, suf, tt.span())
112                },
113                _ => tt,
114            }
115        },
116    }
117}