code_product_lib/lib.rs
1#![doc = include_str!("../README.md")]
2
3use proc_macro2::TokenStream;
4
5#[cfg(test)]
6use quote::quote;
7
8mod product;
9use product::Product;
10mod token;
11
12/// The scope mode for the product expansion. This defines how scopes are handled at the top
13/// level.
14pub enum ScopeMode {
15 /// Expand products in all scopes, including the root scope.
16 Expand,
17 /// Expand products in sub-scopes but not in the root scope. Thus one has to insert at
18 /// least one manual scope with `${ }` to expand products.
19 SubExpand,
20 /// Insert scopes automatically and close/expand them after a ending brace or a
21 /// semicolon. Then start a new scope This is convenient when a lot structs or impls shall
22 /// be independently expanded.
23 ///
24 /// # Example
25 ///
26 /// ```text
27 /// impl Foo for $((First)(Second)) { }
28 /// impl Bar for $((Third)(Fourth)) { }
29 /// ```
30 ///
31 /// will treat each impl item as it is surrounded by it own scope and expand to:
32 ///
33 /// ```text
34 /// impl Foo for First { }
35 /// impl Foo for Second { }
36 /// impl Bar for Third { }
37 /// impl Bar for Fourth { }
38 /// ```
39 AutoBraceSemi,
40 // when you read this, you prolly want to add another mode. Just do and send a PR.
41}
42
43/// Expands a product syntax into a `TokenStream`.
44///
45/// # Examples
46///
47/// ## Complete expansion
48///
49/// ```
50/// # use quote::quote;
51/// # use code_product_lib::*;
52///
53/// let input = quote! {
54/// impl Foo for $((This)(That)) { }
55/// };
56///
57/// let output = expand_products(input, ScopeMode::Expand);
58/// assert_eq!(
59/// output.to_string(),
60/// "impl Foo for This { } \
61/// impl Foo for That { }");
62/// ```
63///
64/// ## Sub-expand
65///
66/// ```
67/// # use quote::quote;
68/// # use code_product_lib::*;
69///
70/// let input = quote! {
71/// let toplevel = thing ;
72/// ${ impl Foo for $((This)(That)) { } }
73/// ${ impl Bar for $((This)(That)) { } }
74/// };
75///
76/// let output = expand_products(input, ScopeMode::SubExpand);
77/// assert_eq!(
78/// output.to_string(),
79/// "let toplevel = thing ; \
80/// impl Foo for This { } \
81/// impl Foo for That { } \
82/// impl Bar for This { } \
83/// impl Bar for That { }"
84/// );
85/// ```
86///
87/// ## Auto-brace-semi
88///
89/// ```
90/// # use quote::quote;
91/// # use code_product_lib::*;
92///
93/// let input = quote! {
94/// impl Foo for $((This)(That)) { }
95/// impl Bar for $((This)(That)) { }
96/// };
97///
98/// let output = expand_products(input, ScopeMode::AutoBraceSemi);
99/// assert_eq!(
100/// output.to_string(),
101/// "impl Foo for This { } \
102/// impl Foo for That { } \
103/// impl Bar for This { } \
104/// impl Bar for That { }"
105/// );
106/// ```
107pub fn expand_products(input: TokenStream, mode: ScopeMode) -> TokenStream {
108 Product::new(input).parse(mode).expand()
109}
110
111#[cfg(test)]
112macro_rules! assert_expansion {
113 ($expand_mode:ident {$($in:tt)*} == {$($out:tt)*}) => {
114 let input = quote! { $($in)* };
115 let output = expand_products(input, ScopeMode::$expand_mode);
116 let expected = quote! { $($out)* };
117 assert_eq!(output.to_string(), expected.to_string());
118 };
119 ($($in:tt)*) => {
120 assert_expansion!(Expand {$($in)*} == {$($in)*});
121 };
122 (SubExpand $($in:tt)*) => {
123 assert_expansion!(SubExpand {$($in)*} == {$($in)*});
124 };
125}
126
127#[test]
128fn smoke() {
129 assert_expansion! {
130 #[newtype]
131 pub struct MyNewtype<T>(T);
132 };
133}
134
135#[test]
136fn smoke_sub() {
137 assert_expansion! {
138 noexpand
139 #[newtype]
140 pub struct MyNewtype<T>(T);
141 };
142}
143
144#[test]
145fn test_tt_groups_parsed_recursive() {
146 let input = quote! {
147 {{$$}}
148 };
149
150 let output = expand_products(input, ScopeMode::Expand);
151 assert_eq!(output.to_string(), "{ { $ } }");
152}
153
154#[test]
155fn test_scopes() {
156 assert_expansion! {
157 Expand {
158 foo ${bar ${baz}}
159 } == {
160 foo bar baz
161 }
162 };
163}
164
165#[test]
166fn test_sub_scopes() {
167 assert_expansion! {
168 SubExpand {
169 foo ${bar ${baz}}
170 } == {
171 foo bar baz
172 }
173 };
174}
175
176#[test]
177fn expand_at_escaping() {
178 assert_expansion! {
179 Expand {
180 $$ ${$$ foo}
181 } == {
182 $ $ foo
183 }
184 };
185}
186
187#[test]
188#[should_panic]
189fn single_at_is_invalid() {
190 assert_expansion! {
191 Expand {
192 $
193 } == {
194 $
195 }
196 };
197}
198
199#[test]
200fn noexpand_leaves_at_verbatim() {
201 assert_expansion! {
202 SubExpand {
203 $ ${$$}
204 } == {
205 $ $
206 }
207 };
208}
209
210#[test]
211fn simple_expand_unnamed() {
212 assert_expansion! {
213 Expand {
214 $((foo))
215 } == {
216 foo
217 }
218 };
219}
220
221#[test]
222fn simple_expand_named() {
223 assert_expansion! {
224 Expand {
225 $(bar: (foo))
226 $bar
227 } == {
228 foo
229 }
230 };
231}
232
233#[test]
234fn simple_expand_named_visible() {
235 assert_expansion! {
236 Expand {
237 $($bar: (foo))
238 } == {
239 foo
240 }
241 };
242}
243
244#[test]
245fn empty_defintions_expand() {
246 assert_expansion! {
247 Expand {
248 $(()()())
249 $((foo)(bar)) ;
250 } == {
251 foo ;
252 foo ;
253 foo ;
254 bar ;
255 bar ;
256 bar ;
257 }
258 };
259}