drone_macros_core/
cfg_cond.rs

1use crate::parse_ident;
2use proc_macro2::TokenStream;
3use quote::quote;
4use std::collections::HashMap;
5use syn::{
6    bracketed, parenthesized,
7    parse::{Parse, ParseStream, Result},
8    Ident, LitStr, Token,
9};
10
11/// List of conditional compilation clauses.
12#[derive(Default, Clone, Debug)]
13pub struct CfgCond {
14    /// CNF of clauses.
15    clauses: Vec<Vec<(Ident, LitStr)>>,
16    inverse: bool,
17}
18
19impl Parse for CfgCond {
20    fn parse(input: ParseStream<'_>) -> Result<Self> {
21        let mut clauses = Vec::new();
22        if input.peek(Token![#]) {
23            input.parse::<Token![#]>()?;
24            let input2;
25            bracketed!(input2 in input);
26            parse_ident!(input2, "cfg");
27            let input3;
28            parenthesized!(input3 in input2);
29            let ident = input3.parse::<Ident>()?;
30            if ident == "any" {
31                let input4;
32                parenthesized!(input4 in input3);
33                let mut last_comma = true;
34                while last_comma && !input4.is_empty() {
35                    let ident = input4.parse::<Ident>()?;
36                    input4.parse::<Token![=]>()?;
37                    clauses.push((ident, input4.parse()?));
38                    last_comma = input4.parse::<Option<Token![,]>>()?.is_some();
39                }
40            } else {
41                input3.parse::<Token![=]>()?;
42                clauses.push((ident, input3.parse()?));
43                if !input3.is_empty() {
44                    return Err(input3.error("Unsupported attribute"));
45                }
46            }
47        }
48        Ok(Self {
49            clauses: if clauses.is_empty() { vec![] } else { vec![clauses] },
50            inverse: false,
51        })
52    }
53}
54
55impl CfgCond {
56    /// Copies `rhs` clauses into `self`.
57    ///
58    /// # Panics
59    ///
60    /// If `rhs` or `self` is a result of [`CfgCondExt::transpose`].
61    pub fn add_clause(&mut self, rhs: &Self) {
62        assert!(!self.inverse);
63        assert!(!rhs.inverse);
64        self.clauses.append(&mut rhs.clauses.clone());
65    }
66
67    /// Returns a `TokenStream` for conditional compilation.
68    pub fn attrs(&self) -> TokenStream {
69        let Self { clauses, inverse } = self;
70        let tokens = clauses
71            .iter()
72            .map(|clauses| {
73                clauses.iter().map(|(key, value)| quote!(#key = #value)).collect::<Vec<_>>()
74            })
75            .collect::<Vec<_>>();
76        if tokens.is_empty() {
77            quote!()
78        } else if *inverse {
79            quote!(#[cfg(not(any(#(all(#(#tokens),*)),*)))])
80        } else {
81            quote!(#(#[cfg(any(#(#tokens),*))])*)
82        }
83    }
84
85    /// Converts to DNF.
86    fn to_dnf(&self) -> Vec<Vec<(Ident, LitStr)>> {
87        assert!(!self.inverse);
88        match self.clauses.iter().map(Vec::as_slice).collect::<Vec<_>>().as_slice() {
89            [] | [[]] | [[], []] => Vec::new(),
90            [x] | [x, []] | [[], x] => x.iter().map(|x| vec![x.clone()]).collect(),
91            [x, y] => {
92                let mut dnf = Vec::new();
93                for x in x.iter() {
94                    for y in y.iter() {
95                        dnf.push(vec![x.clone(), y.clone()]);
96                    }
97                }
98                dnf
99            }
100            x => panic!("{} clauses of CNF is unsupported", x.len()),
101        }
102    }
103}
104
105/// [`CfgCond`] helper extension trait for slices.
106pub trait CfgCondExt<T: Clone> {
107    /// Converts a sequence of `T` into a sequence of combinations of `T` for
108    /// each possible condition.
109    fn transpose(self) -> Vec<(CfgCond, Vec<T>)>;
110}
111
112impl<T: Clone> CfgCondExt<T> for &[(CfgCond, T)] {
113    fn transpose(self) -> Vec<(CfgCond, Vec<T>)> {
114        let mut map: HashMap<_, Vec<_>> = HashMap::new();
115        let mut default = Vec::new();
116        for (clauses, item) in self {
117            let clauses = clauses.to_dnf();
118            if clauses.is_empty() {
119                default.push(item.clone());
120            } else {
121                for cond in clauses {
122                    map.entry(cond).or_default().push(item.clone());
123                }
124            }
125        }
126        let mut result = Vec::new();
127        for (clauses, mut items) in map {
128            let clauses = CfgCond { clauses: vec![clauses], inverse: false };
129            items.append(&mut default.clone());
130            result.push((clauses, items));
131        }
132        let clauses = result.iter().flat_map(|(x, _)| x.clauses.clone()).collect();
133        let clauses = CfgCond { clauses, inverse: true };
134        result.push((clauses, default));
135        result
136    }
137}