Skip to content

Commit b172dc8

Browse files
committed
Feature: Add macro expand!() to expand a template
`expand!()` renders a template with arguments multiple times. ### Example: ```rust,ignore expand!(KEYED, // ignore duplicate by `K` (K, T, V) => {let K: T = V;}, (a, u64, 1), (a, u32, 2), // duplicate `a` will be ignored (c, Vec<u8>, vec![1,2]) ); ``` The above code will be transformed into: ```rust,ignore let a: u64 = 1; let c: Vec<u8> = vec![1, 2]; ```
1 parent 5776139 commit b172dc8

File tree

4 files changed

+349
-6
lines changed

4 files changed

+349
-6
lines changed

macros/src/expand.rs

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use std::collections::HashSet;
2+
3+
use proc_macro2::Ident;
4+
use quote::quote;
5+
use quote::ToTokens;
6+
use syn::parenthesized;
7+
use syn::parse::Parse;
8+
use syn::parse::ParseStream;
9+
use syn::Attribute;
10+
use syn::Expr;
11+
use syn::ExprTuple;
12+
use syn::Token;
13+
use syn::Type;
14+
use syn::__private::TokenStream2;
15+
16+
/// A type or an expression which is used as an argument in the `expand` macro.
17+
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
18+
enum TypeOrExpr {
19+
Attribute(Vec<Attribute>),
20+
Type(Type),
21+
Expr(Expr),
22+
Empty,
23+
}
24+
25+
impl ToTokens for TypeOrExpr {
26+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
27+
match self {
28+
TypeOrExpr::Attribute(attrs) => {
29+
for a in attrs {
30+
a.to_tokens(tokens)
31+
}
32+
}
33+
TypeOrExpr::Type(t) => t.to_tokens(tokens),
34+
TypeOrExpr::Expr(e) => e.to_tokens(tokens),
35+
TypeOrExpr::Empty => {}
36+
}
37+
}
38+
}
39+
40+
impl Parse for TypeOrExpr {
41+
fn parse(input: ParseStream) -> syn::Result<Self> {
42+
let res = input.call(Attribute::parse_outer);
43+
if let Ok(r) = res {
44+
if !r.is_empty() {
45+
return Ok(Self::Attribute(r));
46+
}
47+
}
48+
49+
let res = input.parse::<Type>();
50+
if let Ok(t) = res {
51+
return Ok(Self::Type(t));
52+
}
53+
54+
let res = input.parse::<Expr>();
55+
if let Ok(e) = res {
56+
return Ok(Self::Expr(e));
57+
}
58+
59+
let l = input.lookahead1();
60+
if l.peek(Token![,]) {
61+
Ok(Self::Empty)
62+
} else {
63+
Err(l.error())
64+
}
65+
}
66+
}
67+
68+
pub(crate) struct Expand {
69+
/// Whether to deduplicate by the first argument as key.
70+
pub(crate) keyed: bool,
71+
72+
/// The template variables
73+
pub(crate) idents: Vec<String>,
74+
75+
/// Template in tokens
76+
pub(crate) template: TokenStream2,
77+
78+
/// Multiple arguments lists for rendering the template
79+
args_list: Vec<Vec<TypeOrExpr>>,
80+
81+
/// The keys that have been present in one of the `args_list`.
82+
/// It is used for deduplication, if `keyed` is true.
83+
present_keys: HashSet<TypeOrExpr>,
84+
}
85+
86+
impl Expand {
87+
pub(crate) fn render(&self) -> TokenStream2 {
88+
let mut output_tokens = TokenStream2::new();
89+
90+
for values in self.args_list.iter() {
91+
for t in self.template.clone().into_iter() {
92+
if let proc_macro2::TokenTree::Ident(ident) = t {
93+
let ident_str = ident.to_string();
94+
95+
let ident_index = self.idents.iter().position(|x| x == &ident_str);
96+
if let Some(ident_index) = ident_index {
97+
let replacement = &values[ident_index];
98+
output_tokens.extend(replacement.to_token_stream());
99+
} else {
100+
output_tokens.extend(ident.into_token_stream());
101+
}
102+
} else {
103+
output_tokens.extend(t.into_token_stream());
104+
}
105+
}
106+
}
107+
108+
quote! {
109+
#output_tokens
110+
}
111+
}
112+
}
113+
114+
impl Parse for Expand {
115+
fn parse(input: ParseStream) -> syn::Result<Self> {
116+
let mut b = Expand {
117+
keyed: true,
118+
idents: vec![],
119+
template: Default::default(),
120+
args_list: vec![],
121+
present_keys: Default::default(),
122+
};
123+
124+
// KEYED, or !KEYED,
125+
{
126+
let not = input.parse::<Token![!]>();
127+
let not_keyed = not.is_ok();
128+
129+
let keyed_lit = input.parse::<Ident>()?;
130+
if keyed_lit != "KEYED" {
131+
return Err(syn::Error::new_spanned(&keyed_lit, "Expected KEYED"));
132+
};
133+
b.keyed = !not_keyed;
134+
}
135+
136+
input.parse::<Token![,]>()?;
137+
138+
// Template variables:
139+
// (K, V...)
140+
141+
let idents_tuple = input.parse::<ExprTuple>()?;
142+
143+
for expr in idents_tuple.elems.iter() {
144+
let Expr::Path(p) = expr else {
145+
return Err(syn::Error::new_spanned(expr, "Expected path"));
146+
};
147+
148+
let segment = p.path.segments.first().ok_or_else(|| syn::Error::new_spanned(p, "Expected ident"))?;
149+
let ident = segment.ident.to_string();
150+
151+
b.idents.push(ident);
152+
}
153+
154+
// Template body
155+
// => { ... }
156+
{
157+
input.parse::<Token![=>]>()?;
158+
159+
let brace_group = input.parse::<proc_macro2::TokenTree>()?;
160+
let proc_macro2::TokenTree::Group(tree) = brace_group else {
161+
return Err(syn::Error::new_spanned(brace_group, "Expected { ... }"));
162+
};
163+
b.template = tree.stream();
164+
}
165+
166+
// List of arguments tuples for rendering the template
167+
// , (K1, V1...)...
168+
169+
loop {
170+
if input.is_empty() {
171+
break;
172+
}
173+
174+
input.parse::<Token![,]>()?;
175+
176+
if input.is_empty() {
177+
break;
178+
}
179+
180+
// A tuple of arguments for each rendering
181+
// (K1, V1, V2...)
182+
{
183+
let content;
184+
let _parenthesis = parenthesized!(content in input);
185+
186+
let k = content.parse::<TypeOrExpr>()?;
187+
let mut args = vec![k.clone()];
188+
189+
loop {
190+
if content.is_empty() {
191+
break;
192+
}
193+
194+
content.parse::<Token![,]>()?;
195+
196+
if content.is_empty() {
197+
break;
198+
}
199+
200+
let v = content.parse::<TypeOrExpr>()?;
201+
args.push(v);
202+
}
203+
204+
// Ignore duplicates if keyed
205+
if b.present_keys.contains(&k) && b.keyed {
206+
continue;
207+
}
208+
209+
b.present_keys.insert(k);
210+
b.args_list.push(args);
211+
}
212+
}
213+
214+
Ok(b)
215+
}
216+
}

macros/src/lib.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![doc = include_str!("lib_readme.md")]
22

3+
mod expand;
34
mod since;
45
pub(crate) mod utils;
56

@@ -157,3 +158,68 @@ fn do_since(args: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Er
157158
let tokens = since.append_since_doc(item)?;
158159
Ok(tokens)
159160
}
161+
162+
/// Render a template with arguments multiple times.
163+
///
164+
/// The template to expand is defined as `(K,V) => { ... }`, where `K` and `V` are tempalte
165+
/// variables.
166+
///
167+
/// - The template must contain at least 1 variable.
168+
/// - If the first macro argument is `KEYED`, the first variable serve as the key for deduplication.
169+
/// Otherwise, the first macro argument should be `!KEYED`, and no deduplication will be
170+
/// performed.
171+
///
172+
/// # Example: `KEYED` for deduplication
173+
///
174+
/// The following code builds a series of let statements:
175+
/// ```
176+
/// # use openraft_macros::expand;
177+
/// # fn foo () {
178+
/// expand!(
179+
/// KEYED,
180+
/// // Template with variables K and V, and template body, excluding the braces.
181+
/// (K, T, V) => {let K: T = V;},
182+
/// // Arguments for rendering the template
183+
/// (a, u64, 1),
184+
/// (b, String, "foo".to_string()),
185+
/// (a, u32, 2), // duplicate a will be ignored
186+
/// (c, Vec<u8>, vec![1,2])
187+
/// );
188+
/// # }
189+
/// ```
190+
///
191+
/// The above code will be transformed into:
192+
///
193+
/// ```
194+
/// # fn foo () {
195+
/// let a: u64 = 1;
196+
/// let b: String = "foo".to_string();
197+
/// let c: Vec<u8> = vec![1, 2];
198+
/// # }
199+
/// ```
200+
///
201+
/// # Example: `!KEYED` for no deduplication
202+
///
203+
/// ```
204+
/// # use openraft_macros::expand;
205+
/// # fn foo () {
206+
/// expand!(!KEYED, (K, T, V) => {let K: T = V;},
207+
/// (c, u8, 8),
208+
/// (c, u16, 16),
209+
/// );
210+
/// # }
211+
/// ```
212+
///
213+
/// The above code will be transformed into:
214+
///
215+
/// ```
216+
/// # fn foo () {
217+
/// let c: u8 = 8;
218+
/// let c: u16 = 16;
219+
/// # }
220+
/// ```
221+
#[proc_macro]
222+
pub fn expand(item: TokenStream) -> TokenStream {
223+
let repeat = parse_macro_input!(item as expand::Expand);
224+
repeat.render().into()
225+
}

macros/src/lib_readme.md

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Supporting utils for [Openraft](https://crates.io/crates/openraft).
22

33
# `add_async_trait`
44

5-
`#[add_async_trait]` adds `Send` bounds to an async trait.
5+
[`#[add_async_trait]`](`macro@crate::add_async_trait`) adds `Send` bounds to an async trait.
66

77
## Example
88

@@ -24,14 +24,12 @@ trait MyTrait {
2424

2525
# `since`
2626

27-
`#[since(version = "1.0.0")]` adds a doc line `/// Since: 1.0.0`.
27+
[`#[since(version = "1.0.0")]`](`macro@crate::since`) adds a doc line `/// Since: 1.0.0`.
2828

2929
## Example
3030

3131
```rust,ignore
3232
/// Foo function
33-
///
34-
/// Does something.
3533
#[since(version = "1.0.0")]
3634
fn foo() {}
3735
```
@@ -41,8 +39,34 @@ The above code will be transformed into:
4139
```rust,ignore
4240
/// Foo function
4341
///
44-
/// Does something.
45-
///
4642
/// Since: 1.0.0
4743
fn foo() {}
4844
```
45+
46+
47+
# `expand` a template
48+
49+
[`expand!()`](`crate::expand!`) renders a template with arguments multiple times.
50+
51+
# Example:
52+
53+
```rust
54+
# use openraft_macros::expand;
55+
# fn foo () {
56+
expand!(KEYED, // ignore duplicate by `K`
57+
(K, T, V) => {let K: T = V;},
58+
(a, u64, 1),
59+
(a, u32, 2), // duplicate `a` will be ignored
60+
(c, Vec<u8>, vec![1,2])
61+
);
62+
# }
63+
```
64+
65+
The above code will be transformed into:
66+
67+
```rust
68+
# fn foo () {
69+
let a: u64 = 1;
70+
let c: Vec<u8> = vec![1, 2];
71+
# }
72+
```

macros/tests/test_default_types.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use openraft_macros::expand;
2+
3+
#[allow(dead_code)]
4+
#[allow(unused_variables)]
5+
fn foo() {
6+
expand!(
7+
KEYED,
8+
// template with variables K and V
9+
(K, T, V) => {let K: T = V;},
10+
// arguments for rendering the template
11+
(a, u64, 1),
12+
(b, String, "foo".to_string()),
13+
(a, u32, 2), // duplicate `a` will be ignored
14+
(c, Vec<u8>, vec![1,2]),
15+
);
16+
17+
let _x = 1;
18+
19+
expand!(
20+
!KEYED,
21+
(K, T, V) => {let K: T = V;},
22+
(c, u8, 8),
23+
(c, u16, 16),
24+
);
25+
26+
expand!(
27+
!KEYED,
28+
(K, M, T) => {M let K: T;},
29+
(c, , u8, ),
30+
(c, #[allow(dead_code)] , u16),
31+
(c, #[allow(dead_code)] #[allow(dead_code)] , u16),
32+
);
33+
}
34+
35+
// #[parse_it]
36+
// #[allow(dead_code)]
37+
// fn bar() {}

0 commit comments

Comments
 (0)