zng_var_proc_macros/
util.rs

1use proc_macro2::{Span, TokenStream, TokenTree};
2use quote::ToTokens;
3
4/// Returns `true` if `a` and `b` have the same tokens in the same order (ignoring span).
5pub fn token_stream_eq(a: TokenStream, b: TokenStream) -> bool {
6    let mut a = a.into_iter();
7    let mut b = b.into_iter();
8    use TokenTree::*;
9    loop {
10        match (a.next(), b.next()) {
11            (Some(a), Some(b)) => match (a, b) {
12                (Group(a), Group(b)) if a.delimiter() == b.delimiter() && token_stream_eq(a.stream(), b.stream()) => continue,
13                (Ident(a), Ident(b)) if a == b => continue,
14                (Punct(a), Punct(b)) if a.as_char() == b.as_char() && a.spacing() == b.spacing() => continue,
15                (Literal(a), Literal(b)) if a.to_string() == b.to_string() => continue,
16                _ => return false,
17            },
18            (None, None) => return true,
19            _ => return false,
20        }
21    }
22}
23
24/// Generate a [`String`] that is a valid [`Ident`] from an arbitrary [`TokenStream`].
25pub fn tokens_to_ident_str(tokens: &TokenStream) -> String {
26    let tokens = tokens.to_string();
27    let max = tokens.len().min(40);
28    let mut tokens = tokens[(tokens.len() - max)..]
29        .replace(&['.', ':', ' '][..], "_")
30        .replace('!', "not")
31        .replace("&&", "and")
32        .replace("||", "or")
33        .replace('(', "p")
34        .replace(')', "b")
35        .replace("==", "eq");
36
37    tokens.retain(|c| c == '_' || c.is_alphanumeric());
38
39    tokens
40}
41
42/// Collection of compile errors.
43#[derive(Default)]
44pub struct Errors {
45    tokens: TokenStream,
46}
47impl Errors {
48    /// Push a compile error.
49    pub fn push(&mut self, error: impl ToString, span: Span) {
50        let error = error.to_string();
51        self.tokens.extend(quote_spanned! {span=>
52            compile_error!{#error}
53        })
54    }
55
56    pub fn is_empty(&self) -> bool {
57        self.tokens.is_empty()
58    }
59}
60impl ToTokens for Errors {
61    fn to_tokens(&self, tokens: &mut TokenStream) {
62        tokens.extend(self.tokens.clone())
63    }
64    fn to_token_stream(&self) -> TokenStream {
65        self.tokens.clone()
66    }
67    fn into_token_stream(self) -> TokenStream {
68        self.tokens
69    }
70}
71
72/// Generates a return of a compile_error message in the given span.
73macro_rules! abort {
74    ($span:expr, $($tt:tt)*) => {{
75        let error = format!($($tt)*);
76        let error = syn::LitStr::new(&error, proc_macro2::Span::call_site());
77
78        return quote_spanned!($span=> compile_error!{#error}).into();
79    }};
80}
81
82/// Generates a return of a compile_error message in the call_site span.
83macro_rules! abort_call_site {
84    ($($tt:tt)*) => {
85        abort!(proc_macro2::Span::call_site(), $($tt)*)
86    };
87}
88
89/// Input error not caused by the user.
90macro_rules! non_user_error {
91    ($e:expr) => {
92        panic!("[{}:{}] invalid non-user input: {}", file!(), line!(), $e)
93    };
94    ($fmt:tt, $($args:tt)+) => {
95        non_user_error! {
96            format_args!($fmt, $($args)+)
97        }
98    }
99}
100
101/// `Ident` with custom span.
102macro_rules! ident_spanned {
103    ($span:expr=> $($format_name:tt)+) => {
104        proc_macro2::Ident::new(&format!($($format_name)+), $span)
105    };
106}
107
108/// `Ident` with call_site span.
109macro_rules! ident {
110    ($($tt:tt)*) => {
111        ident_spanned!(proc_macro2::Span::call_site()=> $($tt)*)
112    };
113}