Skip to content

Commit 6b3e450

Browse files
authored
Merge pull request #3 from buttercrab/add-default-args-attribute
Add attribute macro
2 parents feb3c35 + 84456a6 commit 6b3e450

File tree

4 files changed

+641
-495
lines changed

4 files changed

+641
-495
lines changed

src/core.rs

Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
use proc_macro2::Ident;
2+
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
3+
use syn::parse::{Parse, ParseStream};
4+
use syn::punctuated::Punctuated;
5+
use syn::spanned::Spanned;
6+
use syn::{
7+
parenthesized, token, Abi, Attribute, Block, Expr, FnArg, Generics, PatType, ReturnType, Token,
8+
Visibility,
9+
};
10+
11+
/// Structure for arguments
12+
///
13+
/// This contains arguments of function and default values like: `a: u32, b: u32 = 0`
14+
pub struct Args {
15+
pub all: Punctuated<FnArg, Token![,]>,
16+
pub required: Vec<PatType>,
17+
pub optional: Vec<(PatType, Expr)>,
18+
}
19+
20+
impl ToTokens for Args {
21+
/// This function changes to normal signature of function which is `self.parsed`
22+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
23+
self.all.to_tokens(tokens)
24+
}
25+
}
26+
27+
/// Module for export keyword
28+
///
29+
/// export keyword would make macro export (by adding `#[macro_export]`
30+
pub mod export {
31+
use syn::custom_keyword;
32+
33+
custom_keyword!(export);
34+
}
35+
36+
/// Structure for Default Argument function
37+
///
38+
/// This contains the signature of function like
39+
/// `#[hello] export pub const async unsafe extern "C" fn crate::foo::bar<T>(a: T, b: u32 = 0) -> String where T: Display { format!("{}, {}", a, b) }`
40+
pub struct DefaultArgs {
41+
pub attrs: Vec<Attribute>,
42+
pub export: Option<export::export>,
43+
pub vis: Visibility,
44+
pub constness: Option<Token![const]>,
45+
pub asyncness: Option<Token![async]>,
46+
pub unsafety: Option<Token![unsafe]>,
47+
pub abi: Option<Abi>,
48+
pub fn_token: Token![fn],
49+
pub crate_path: Option<(Token![crate], Token![::])>,
50+
pub fn_path: Punctuated<Ident, Token![::]>,
51+
pub fn_name: Ident,
52+
pub generics: Generics,
53+
pub paren_token: token::Paren,
54+
pub args: Args,
55+
pub ret: ReturnType,
56+
pub body: Block,
57+
}
58+
59+
impl Parse for DefaultArgs {
60+
/// Parse function for `DefaultArgs`
61+
///
62+
/// ## Errors
63+
///
64+
/// - when path don't start with `crate`: `path should start with crate`
65+
fn parse(input: ParseStream) -> syn::Result<Self> {
66+
let attrs = input.call(Attribute::parse_outer)?;
67+
let export = input.parse()?;
68+
let vis = input.parse()?;
69+
let constness = input.parse()?;
70+
let asyncness = input.parse()?;
71+
let unsafety = input.parse()?;
72+
let abi = input.parse()?;
73+
let fn_token = input.parse()?;
74+
75+
let mut fn_path: Punctuated<Ident, Token![::]> = Punctuated::new();
76+
let crate_token = input.parse::<Option<Token![crate]>>()?;
77+
let crate_path = if let Some(token) = crate_token {
78+
let crate_colon_token = input.parse::<Token![::]>()?;
79+
Some((token, crate_colon_token))
80+
} else {
81+
None
82+
};
83+
84+
loop {
85+
fn_path.push_value(input.parse()?);
86+
if input.peek(Token![::]) {
87+
fn_path.push_punct(input.parse()?);
88+
} else {
89+
break;
90+
}
91+
}
92+
93+
if crate_path.is_none() && fn_path.len() > 1 {
94+
return Err(syn::Error::new(
95+
fn_path.first().unwrap().span(),
96+
"path should start with crate",
97+
));
98+
}
99+
let fn_name = fn_path.pop().unwrap().into_value();
100+
101+
let mut generics: Generics = input.parse()?;
102+
let content;
103+
let paren_token = parenthesized!(content in input);
104+
105+
let mut args = Punctuated::new();
106+
let mut has_optional = false;
107+
let mut required = Vec::new();
108+
let mut optional = Vec::new();
109+
110+
while !content.is_empty() {
111+
let mut fn_arg = content.parse::<FnArg>()?;
112+
113+
let pat = match &fn_arg {
114+
FnArg::Receiver(r) => {
115+
return Err(syn::Error::new(
116+
r.span(),
117+
"self in default_args! is not support in this version",
118+
));
119+
}
120+
FnArg::Typed(pat) => pat.clone(),
121+
};
122+
123+
if content.parse::<Option<Token![=]>>()?.is_some() {
124+
has_optional = true;
125+
optional.push((pat, content.parse()?));
126+
} else {
127+
let mut default = None;
128+
129+
if let FnArg::Typed(pat_mut) = &mut fn_arg {
130+
let mut attr_to_remove = None;
131+
for (i, attr) in pat_mut.attrs.iter().enumerate() {
132+
if attr.meta.path().is_ident("default") {
133+
if let syn::Meta::List(meta_list) = &attr.meta {
134+
default = Some(syn::parse2(meta_list.tokens.clone())?);
135+
attr_to_remove = Some(i);
136+
}
137+
break;
138+
}
139+
}
140+
if let Some(i) = attr_to_remove {
141+
pat_mut.attrs.remove(i);
142+
}
143+
}
144+
145+
if let Some(d) = default {
146+
has_optional = true;
147+
optional.push((pat, d));
148+
} else if has_optional {
149+
return Err(syn::Error::new(
150+
pat.span(),
151+
"required argument cannot come after optional argument",
152+
));
153+
} else {
154+
required.push(pat);
155+
}
156+
}
157+
158+
args.push_value(fn_arg);
159+
160+
if content.is_empty() {
161+
break;
162+
}
163+
164+
args.push_punct(content.parse()?);
165+
}
166+
167+
let args = Args {
168+
all: args,
169+
required,
170+
optional,
171+
};
172+
173+
let ret = input.parse()?;
174+
generics.where_clause = input.parse()?;
175+
let body = input.parse()?;
176+
177+
Ok(DefaultArgs {
178+
attrs,
179+
export,
180+
vis,
181+
constness,
182+
asyncness,
183+
unsafety,
184+
abi,
185+
fn_token,
186+
crate_path,
187+
fn_path,
188+
fn_name,
189+
generics,
190+
paren_token,
191+
args,
192+
ret,
193+
body,
194+
})
195+
}
196+
}
197+
198+
impl ToTokens for DefaultArgs {
199+
/// This function changes to normal signature of function
200+
/// It would not print `export` and change the name with under bar attached
201+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
202+
for i in &self.attrs {
203+
i.to_tokens(tokens);
204+
}
205+
self.vis.to_tokens(tokens);
206+
self.constness.to_tokens(tokens);
207+
self.asyncness.to_tokens(tokens);
208+
self.unsafety.to_tokens(tokens);
209+
self.abi.to_tokens(tokens);
210+
self.fn_token.to_tokens(tokens);
211+
format_ident!("{}_", &self.fn_name).to_tokens(tokens);
212+
self.generics.lt_token.to_tokens(tokens);
213+
self.generics.params.to_tokens(tokens);
214+
self.generics.gt_token.to_tokens(tokens);
215+
self.paren_token.surround(tokens, |tokens| {
216+
self.args.to_tokens(tokens);
217+
});
218+
self.ret.to_tokens(tokens);
219+
self.generics.where_clause.to_tokens(tokens);
220+
self.body.to_tokens(tokens);
221+
}
222+
}
223+
224+
/// Make unnamed arguments in macro
225+
/// - `count`: how many arguments
226+
/// - `def`: if it would be used in macro definition (will add `expr`)
227+
fn unnamed_args(count: usize, def: bool) -> proc_macro2::TokenStream {
228+
(0..count)
229+
.map(|i| {
230+
let item = format_ident!("u{}", i);
231+
if def {
232+
if i == 0 {
233+
quote! { $#item:expr }
234+
} else {
235+
quote! { , $#item:expr }
236+
}
237+
} else if i == 0 {
238+
quote! { $#item }
239+
} else {
240+
quote! { , $#item }
241+
}
242+
})
243+
.collect()
244+
}
245+
246+
/// Make named arguments in definition of macro
247+
/// - `front_comma`: if it needs a front comma
248+
/// - `input`: default args
249+
/// - `macro_index`: mapped index of argument in function from macro
250+
fn named_args_def(
251+
front_comma: bool,
252+
input: &DefaultArgs,
253+
macro_index: &[usize],
254+
) -> proc_macro2::TokenStream {
255+
macro_index
256+
.iter()
257+
.enumerate()
258+
.map(|(j, i)| {
259+
let item = format_ident!("n{}", i);
260+
let pat = &input.args.optional[*i].0.pat;
261+
if !front_comma && j == 0 {
262+
quote! { #pat = $#item:expr }
263+
} else {
264+
quote! { , #pat = $#item:expr }
265+
}
266+
})
267+
.collect()
268+
}
269+
270+
/// Make names arguments in macro
271+
/// - `front_comma`: if it needs a front comma
272+
/// - `input`: default args
273+
/// - `offset`: offset of named argument
274+
/// - `func_index`: whether if the function argument is provided
275+
fn named_args(
276+
front_comma: bool,
277+
input: &DefaultArgs,
278+
offset: usize,
279+
func_index: &[bool],
280+
) -> proc_macro2::TokenStream {
281+
func_index
282+
.iter()
283+
.enumerate()
284+
.map(|(i, provided)| {
285+
let inner = if *provided {
286+
let item = format_ident!("n{}", i + offset);
287+
quote! { $#item }
288+
} else {
289+
let item = &input.args.optional[i + offset].1;
290+
quote! { ( #item ) }
291+
};
292+
293+
if !front_comma && i == 0 {
294+
quote! { #inner }
295+
} else {
296+
quote! { , #inner }
297+
}
298+
})
299+
.collect()
300+
}
301+
302+
/// Generate one arm of macro
303+
/// - `input`: default args
304+
/// - `unnamed_cnt`: unnamed argument count
305+
/// - `offset`: offset of named argument
306+
/// - `macro_index`: mapped index of argument in function from macro
307+
/// - `func_index`: whether if the function argument is provided
308+
fn generate(
309+
input: &DefaultArgs,
310+
unnamed_cnt: usize,
311+
offset: usize,
312+
macro_index: &[usize],
313+
func_index: &[bool],
314+
) -> proc_macro2::TokenStream {
315+
let fn_name = format_ident!("{}_", input.fn_name);
316+
317+
let unnamed_def = unnamed_args(unnamed_cnt, true);
318+
let unnamed = unnamed_args(unnamed_cnt, false);
319+
320+
let named_def = named_args_def(unnamed_cnt != 0, input, macro_index);
321+
let named = named_args(unnamed_cnt != 0, input, offset, func_index);
322+
323+
if input.crate_path.is_some() {
324+
let fn_path = &input.fn_path;
325+
quote! {
326+
(#unnamed_def#named_def) => {
327+
$crate::#fn_path#fn_name(#unnamed#named)
328+
};
329+
}
330+
} else {
331+
quote! {
332+
(#unnamed_def#named_def) => {
333+
#fn_name(#unnamed#named)
334+
};
335+
}
336+
}
337+
}
338+
339+
/// Generate macro arms recursively
340+
/// - `input`: default args
341+
/// - `unnamed_cnt`: unnamed argument count
342+
/// - `offset`: offset of named argument
343+
/// - `macro_index`: mapped index of argument in function from macro
344+
/// - `func_index`: whether if the function argument is provided
345+
/// - `stream`: token stream to append faster
346+
fn generate_recursive(
347+
input: &DefaultArgs,
348+
unnamed_cnt: usize,
349+
offset: usize,
350+
macro_index: &mut Vec<usize>,
351+
func_index: &mut Vec<bool>,
352+
stream: &mut proc_macro2::TokenStream,
353+
) {
354+
stream.append_all(generate(
355+
input,
356+
unnamed_cnt,
357+
offset,
358+
macro_index,
359+
func_index,
360+
));
361+
362+
for i in 0..func_index.len() {
363+
if func_index[i] {
364+
continue;
365+
}
366+
367+
func_index[i] = true;
368+
macro_index.push(i + offset);
369+
generate_recursive(input, unnamed_cnt, offset, macro_index, func_index, stream);
370+
macro_index.pop();
371+
func_index[i] = false;
372+
}
373+
}
374+
375+
/// Generates all macro arms
376+
/// - `input`: default args
377+
pub fn generate_macro(input: &DefaultArgs) -> proc_macro2::TokenStream {
378+
let mut stream = proc_macro2::TokenStream::new();
379+
380+
for i in 0..=input.args.optional.len() {
381+
let mut macro_index = Vec::new();
382+
let mut func_index = vec![false; input.args.optional.len() - i];
383+
generate_recursive(
384+
input,
385+
input.args.required.len() + i,
386+
i,
387+
&mut macro_index,
388+
&mut func_index,
389+
&mut stream,
390+
);
391+
}
392+
393+
stream
394+
}

0 commit comments

Comments
 (0)