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
extern crate proc_macro;

use std::collections::hash_map;
use std::hash::Hasher;

use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{bracketed, parse_macro_input, Path, Token};

struct Input {
    krate: Option<Path>,
    expr: TokenStream,
}

impl Parse for Input {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(Input {
            krate: {
                // #![crate = gflags]
                if input.peek(Token![#]) && input.peek2(Token![!]) {
                    input.parse::<Token![#]>()?;
                    input.parse::<Token![!]>()?;
                    let content;
                    bracketed!(content in input);
                    content.parse::<Token![crate]>()?;
                    content.parse::<Token![=]>()?;
                    let krate = content.parse()?;
                    Some(krate)
                } else {
                    None
                }
            },
            expr: input.parse()?,
        })
    }
}

#[proc_macro]
#[doc(hidden)]
pub fn submit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as Input);

    let expr = input.expr;
    let init = Ident::new(&format!("__init{}", hash(&expr)), Span::call_site());
    let prefix = match input.krate {
        Some(krate) => quote!(#krate::),
        None => quote!(),
    };

    let expanded = quote! {
        #[allow(non_upper_case_globals)]
        #[#prefix inventory::ctor]
        fn #init() {
            // TODO: once existential type is stable, store the caller's
            // expression into a static and string those statics together into
            // an intrusive linked list without needing allocation.
            //
            //     existential type This;
            //
            //     static mut VALUE: Option<inventory::Node<This>> = None;
            //
            //     fn value() -> This {
            //         #expr
            //     }
            //
            //     unsafe {
            //         VALUE = Some(inventory::Node {
            //             value: value(),
            //             next: None,
            //         });
            //         inventory::submit(VALUE.as_mut().unwrap());
            //     }

            #prefix inventory::submit({ #expr });
        }
    };

    proc_macro::TokenStream::from(expanded)
}

fn hash(input: &TokenStream) -> u64 {
    let mut hasher = hash_map::DefaultHasher::new();
    hasher.write(input.to_string().as_bytes());
    hasher.finish()
}