orchestra_proc_macro/parse/
parse_cfg.rs1use quote::{quote, ToTokens};
17use syn::{
18 parenthesized,
19 parse::{Parse, ParseStream},
20 punctuated::Punctuated,
21 LitStr, Result, Token,
22};
23
24mod kw {
25 syn::custom_keyword!(not);
26 syn::custom_keyword!(all);
27 syn::custom_keyword!(any);
28 syn::custom_keyword!(feature);
29}
30
31#[derive(Debug, Clone)]
37pub(crate) struct CfgExpressionRoot {
38 pub(crate) predicate: CfgPredicate,
39}
40
41impl Parse for CfgExpressionRoot {
42 fn parse(input: ParseStream) -> Result<Self> {
43 let content;
44 let _paren_token = parenthesized!(content in input);
45
46 Ok(Self { predicate: content.parse()? })
47 }
48}
49
50#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
56pub(crate) enum CfgPredicate {
57 Feature(String),
58 All(Vec<CfgPredicate>),
59 Not(Box<CfgPredicate>),
60 Any(Vec<CfgPredicate>),
61}
62
63impl CfgPredicate {
64 pub(crate) fn sort_recursive(&mut self) {
66 match self {
67 CfgPredicate::All(predicates) | CfgPredicate::Any(predicates) => {
68 predicates.sort();
69 predicates.iter_mut().for_each(|p| p.sort_recursive());
70 },
71 CfgPredicate::Not(p) => p.sort_recursive(),
72 _ => {},
73 }
74 }
75
76 pub(crate) fn merge(self, other: CfgPredicate) -> CfgPredicate {
78 Self::All(vec![self, other])
79 }
80}
81
82impl ToTokens for CfgPredicate {
83 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
84 let ts = match self {
85 CfgPredicate::Feature(name) => quote! { feature = #name },
86 CfgPredicate::All(predicates) => quote! { all(#(#predicates),*) },
87 CfgPredicate::Not(predicate) => quote! { not(#predicate) },
88 CfgPredicate::Any(predicates) => quote! { any(#(#predicates),*) },
89 };
90 tokens.extend(ts);
91 }
92}
93
94fn parse_cfg_group<T: Parse>(input: ParseStream) -> Result<Vec<CfgPredicate>> {
95 let _ = input.parse::<T>()?;
96 let content;
97 let _ = parenthesized!(content in input);
98 let cfg_items: Punctuated<CfgPredicate, Token![,]> = Punctuated::parse_terminated(&content)?;
99 Ok(Vec::from_iter(cfg_items))
100}
101
102impl Parse for CfgPredicate {
103 fn parse(input: ParseStream) -> Result<Self> {
104 let lookahead = input.lookahead1();
105
106 if lookahead.peek(kw::all) {
107 let cfg_items = parse_cfg_group::<kw::all>(input)?;
108
109 Ok(Self::All(Vec::from_iter(cfg_items)))
110 } else if lookahead.peek(kw::any) {
111 let cfg_items = parse_cfg_group::<kw::any>(input)?;
112
113 Ok(Self::Any(Vec::from_iter(cfg_items)))
114 } else if lookahead.peek(kw::not) {
115 input.parse::<kw::not>()?;
116 let content;
117 parenthesized!(content in input);
118
119 Ok(Self::Not(content.parse::<CfgPredicate>()?.into()))
120 } else if lookahead.peek(kw::feature) {
121 input.parse::<kw::feature>()?;
122 input.parse::<Token![=]>()?;
123
124 Ok(Self::Feature(input.parse::<LitStr>()?.value()))
125 } else {
126 Err(lookahead.error())
127 }
128 }
129}
130
131#[cfg(test)]
132mod test {
133 use super::CfgPredicate::{self, *};
134 use assert_matches::assert_matches;
135 use quote::ToTokens;
136 use syn::parse_quote;
137
138 #[test]
139 fn cfg_parsing_works() {
140 let cfg: CfgPredicate = parse_quote! {
141 feature = "f1"
142 };
143
144 assert_matches!(cfg, CfgPredicate::Feature(item) => {assert_eq!("f1", item)} );
145
146 let cfg: CfgPredicate = parse_quote! {
147 all(feature = "f1", feature = "f2", any(feature = "f3", not(feature = "f4")))
148 };
149
150 assert_matches!(cfg, All(all_items) => {
151 assert_matches!(all_items.get(0).unwrap(), CfgPredicate::Feature(item) => {assert_eq!("f1", item)});
152 assert_matches!(all_items.get(1).unwrap(), CfgPredicate::Feature(item) => {assert_eq!("f2", item)});
153 assert_matches!(all_items.get(2).unwrap(), CfgPredicate::Any(any_items) => {
154 assert_matches!(any_items.get(0).unwrap(), CfgPredicate::Feature(item) => {assert_eq!("f3", item)});
155 assert_matches!(any_items.get(1).unwrap(), CfgPredicate::Not(not_item) => {
156 assert_matches!(**not_item, CfgPredicate::Feature(ref inner) => {assert_eq!("f4", inner)})
157 });
158 });
159 });
160 }
161
162 #[test]
163 fn cfg_item_sorts_and_compares_correctly() {
164 let item1: CfgPredicate = parse_quote! { feature = "f1"};
165 let item2: CfgPredicate = parse_quote! { feature = "f1"};
166 assert_eq!(true, item1 == item2);
167
168 let item1: CfgPredicate = parse_quote! { feature = "f1"};
169 let item2: CfgPredicate = parse_quote! { feature = "f2"};
170 assert_eq!(false, item1 == item2);
171 let mut item1: CfgPredicate = parse_quote! {
172 any(
173 all(feature = "f1", feature = "f2", feature = "f3"),
174 any(feature = "any1", feature = "any2"),
175 not(feature = "no")
176 )
177 };
178 let mut item2: CfgPredicate = parse_quote! {
179 any(
180 not(feature = "no"),
181 any(feature = "any2", feature = "any1"),
182 all(feature = "f2", feature = "f1", feature = "f3")
183 )
184 };
185 item1.sort_recursive();
186 item2.sort_recursive();
187 assert_eq!(true, item1 == item2);
188 }
189
190 #[test]
191 fn cfg_item_is_converted_to_tokens() {
192 let to_parse: CfgPredicate = parse_quote! {
193 any(
194 not(feature = "no"),
195 any(feature = "any2", feature = "any1"),
196 all(feature = "f2", feature = "f1", feature = "f3")
197 )
198 };
199 assert_eq!("any (not (feature = \"no\") , any (feature = \"any2\" , feature = \"any1\") , all (feature = \"f2\" , feature = \"f1\" , feature = \"f3\"))"
200 , to_parse.to_token_stream().to_string());
201 }
202}