orchestra_proc_macro/parse/
parse_cfg.rs

1// Copyright (C) 2021 Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use 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/// Top-level cfg expression item without the initial "cfg" attribute but
32/// including parenthesis.
33/// Examples:
34///	- `(feature = "feature1")`
35///	- `(any(feature = "feature1", not(feature = "feature2")))`
36#[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/// Potentially nested cfg predicate for parsing.
51///	Examples:
52///	- `feature = "feature1"`
53///	- `all(feature = "feature1", feature = "feature2")`
54///	- `not(feature = "feature2")`
55#[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	/// Recursively sort this predicate and all nested predicates.
65	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	/// Consume self and return conjunction with another predicate.
77	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}