molt_argparse_procmacro/
lib.rs

1//! This proc-macro crate is intended to be re-exported by `molt_argparse`. Please do NOT use it alone.
2
3use proc_macro::TokenStream;
4use quote::{ quote, ToTokens };
5use indexmap::IndexMap;
6
7struct ArgInfo {
8    funcarg_ident: syn::Ident,
9    funcarg_def: syn::PatType,
10    /// if keyed, this is specified.
11    keyarg_name: Option<String>,
12    is_optional: bool,
13    is_flag: bool,
14    raw_type: proc_macro2::TokenStream,
15    value_converter: proc_macro2::TokenStream
16}
17
18fn tokens_equal(
19    a: &(impl Clone + IntoIterator<Item = proc_macro2::TokenTree>),
20    b: impl IntoIterator<Item = proc_macro2::TokenTree>
21) -> bool
22{
23    use proc_macro2::*;
24    let a = a.clone().into_iter().collect::<Vec<_>>();
25    let b = b.into_iter().collect::<Vec<_>>();
26
27    if a.len() != b.len() { return false; }
28    for (ai, bi) in a.iter().zip(b.iter()) {
29        match (ai, bi) {
30            (TokenTree::Ident(ia), TokenTree::Ident(ib)) if ia == ib => { },
31            (TokenTree::Punct(pa), TokenTree::Punct(pb))
32                if pa.as_char() == pb.as_char() => { },
33            _ => return false
34        }
35    }
36    true
37}
38
39fn tokens_match(
40    a: &(impl Clone + IntoIterator<Item = proc_macro2::TokenTree>),
41    l: impl IntoIterator<Item = proc_macro2::TokenTree>,
42    r: impl IntoIterator<Item = proc_macro2::TokenTree>
43) -> Option<proc_macro2::TokenStream>
44{
45    use proc_macro2::*;
46    let mut a = a.clone().into_iter().collect::<Vec<_>>();
47    let l = l.into_iter().collect::<Vec<_>>();
48    let r = r.into_iter().collect::<Vec<_>>();
49    
50    if a.len() < l.len() + r.len() { return None; }
51    for i in 0..l.len() {
52        match (&a[i], &l[i]) {
53            (TokenTree::Ident(ia), TokenTree::Ident(ib)) if ia == ib => { },
54            (TokenTree::Punct(pa), TokenTree::Punct(pb))
55                if pa.as_char() == pb.as_char() => { },
56            _ => return None,
57        }
58    }
59    for i in 0..r.len() {
60        match (&a[a.len() - r.len() + i], &r[i]) {
61            (TokenTree::Ident(ia), TokenTree::Ident(ib)) if ia == ib => { },
62            (TokenTree::Punct(pa), TokenTree::Punct(pb))
63                if pa.as_char() == pb.as_char() => { },
64            _ => return None,
65        }
66    }
67
68    let ret = a.splice(l.len()..(a.len() - r.len()), []);
69    Some(ret.into_iter().collect())
70}
71
72#[proc_macro_attribute]
73pub fn molt_argparse(args: TokenStream, decl: TokenStream) -> TokenStream {
74    // println!("orig decl: {:#?}", decl);
75    let mut args = syn::parse_macro_input!(args as syn::LitStr).value()
76        .split_whitespace()
77        .map(|s| s.to_string())
78        .collect::<Vec<String>>();
79    let decl = syn::parse_macro_input!(decl as syn::ItemFn);
80    // println!("{:?}", args);
81    // println!("{:#?}", decl);
82
83    let mut funcargs: IndexMap<String, (syn::PatType, syn::PatIdent)> =
84        decl.sig.inputs.iter().map(|fnarg| match fnarg {
85            syn::FnArg::Typed(typ) => match &*typ.pat {
86                syn::Pat::Ident(patident) => {
87                    (patident.ident.to_string(),
88                     (typ.clone(), patident.clone()))
89                }
90                _ => panic!("unexpected arg def")
91            },
92            syn::FnArg::Receiver(_) =>
93                panic!("self argument is unsupported by molt_argparse")
94        })
95        .collect();
96
97    let contextdecl = {
98        if args[0].starts_with("{") {
99            let s = args.remove(0);
100            let p = &s[1..s.len() - 1];
101            let (pattype, _) = funcargs.remove(p).expect("context arg not found");
102            quote! {
103                let #pattype = _interp.context(_ctxid);
104            }
105        }
106        else { quote! { } }
107    };
108
109    let args = args.into_iter().map(|arg| {
110        let (funcarg_name, keyarg_name) = match arg.starts_with("-") {
111            true => match arg.find(':') {
112                Some(i) => (arg[i + 1..].to_string(),
113                            Some(arg[0..i].to_string())),
114                None => (arg[1..].to_string(), Some(arg))
115            }
116            false => (arg, None)
117        };
118        let (pt, pi) = funcargs.remove(&funcarg_name)
119            .expect("arg not found");
120
121        let type_tokens = pt.ty.to_token_stream();
122        let (raw_type, is_optional) = match tokens_match(
123            &type_tokens,
124            quote!{ Option< }, quote!{ > }
125        ) {
126            Some(ts) => (ts, true),
127            None => (type_tokens, false)
128        };
129        
130        let raw_type_vec = raw_type.clone().into_iter().collect::<Vec<_>>();
131
132        let is_flag = keyarg_name.is_some() &&
133            !is_optional &&
134            tokens_equal(&raw_type, quote!{ bool });
135
136        let value_converter = {
137            if tokens_equal(&raw_type_vec, quote!{ bool }) {
138                quote!{ _molt_v.as_bool().ok() }
139            }
140            else if tokens_equal(&raw_type_vec, quote!{ &str }) {
141                quote!{ Some(_molt_v.as_str()) }
142            }
143            else if let Some(_) = tokens_match(
144                &raw_type_vec,
145                quote!{ &mut }, quote!{ }
146            ) {
147                panic!("mutable reference is not allowed in molt_argparse");
148            }
149            else if let Some(t) = tokens_match(
150                &raw_type_vec,
151                quote!{ &Vec< }, quote!{ > }
152            ) {
153                quote!{ _molt_v.as_other::<molt_argparse::WrapVec<#t>>()
154                         .map(|v| &v.0) }
155            }
156            else if let Some(t) = tokens_match(
157                &raw_type_vec,
158                quote!{ Vec< }, quote!{ > }
159            ) {
160                quote!{ _molt_v.as_other::<molt_argparse::WrapVec<#t>>()
161                         .map(|v| v.0.clone()) }
162            }
163            else if let Some(v) = tokens_match(
164                &raw_type_vec,
165                quote!{ & }, quote!{ }
166            ) {
167                quote!{ _molt_v.as_other::<#v>().map(|r| r.as_ref()) }
168            }
169            else if let Some(v) = tokens_match(
170                &raw_type_vec,
171                quote!{ Rc< }, quote!{ > }
172            ) {
173                quote!{ _molt_v.as_other::<#v>() }
174            }
175            else {
176                quote!{ _molt_v.as_other::<#(#raw_type_vec)*>()
177                         .map(|r| r.as_ref().clone()) }
178            }
179        };
180
181        ArgInfo {
182            funcarg_ident: pi.ident,
183            funcarg_def: pt,
184            keyarg_name,
185            is_optional,
186            is_flag,
187            raw_type,
188            value_converter
189        }
190    }).collect::<Vec<_>>();
191
192    assert_eq!(funcargs.len(), 0, "excess funcargs");
193
194    let argdecls = args.iter().map(|ai| {
195        let ident = &ai.funcarg_ident;
196        let typ = &ai.raw_type;
197        match ai.is_flag {
198            true => quote! { let mut #ident: bool = false; },
199            false => quote! { let mut #ident: Option<#typ> = None; }
200        }
201    });
202
203    let argpostprocess = args.iter().filter(|ai| !ai.is_optional && !ai.is_flag).map(|ai| {
204        let ident = &ai.funcarg_ident;
205        let error_info = format!("argparse: {} is required.", ident);
206        quote! {
207            let #ident = match #ident {
208                Some(#ident) => #ident,
209                None => { return molt_ng::molt_err!(#error_info); }
210            };
211        }
212    });
213
214    let argfixmatch = args.iter().filter(|ai| ai.keyarg_name.is_none()).enumerate().map(|(i, ai)| {
215        let ident = &ai.funcarg_ident;
216        let value_converter = &ai.value_converter;
217        quote! {
218            #i => {
219                if let Some(_v) = #value_converter {
220                    #ident = Some(_v);
221                    _molt_argparse_i += 1;
222                    continue;
223                }
224            }
225        }
226    });
227
228    let argkeymatch = args.iter().filter(|ai| ai.keyarg_name.is_some()).map(|ai| {
229        let keyarg_name = ai.keyarg_name.as_ref().unwrap().as_str();
230        let ident = &ai.funcarg_ident;
231        let value_converter = &ai.value_converter;
232        match ai.is_flag {
233            true => quote! {
234                #keyarg_name => {
235                    #ident = true;
236                    Ok(true)
237                }
238            },
239            false => quote! {
240                #keyarg_name => {
241                    if let Some(_molt_v) = _molt_argparse_vals.next() {
242                        if let Some(_v) = #value_converter {
243                            #ident = Some(_v);
244                            Ok(true)
245                        }
246                        else {
247                            // although not MoltResult, we can still
248                            // use this macro as it expands to Err(..).
249                            return molt_ng::molt_err!(
250                                "argparse: {} does not accept {}",
251                                #keyarg_name, _molt_v.as_str());
252                        }
253                    }
254                    else {
255                        return molt_ng::molt_err!(
256                            "argparse: expected value after {}",
257                            #keyarg_name);
258                    }
259                }
260            }
261        }
262    });
263
264    let orig_args = args.iter().map(|ai| ai.funcarg_ident.clone());
265    let orig_arg_defs = args.iter().map(|ai| ai.funcarg_def.clone());
266    let orig_stmts = &decl.block.stmts;
267    let (orig_ret_type, is_ret_empty) = match &decl.sig.output {
268        syn::ReturnType::Default => (quote!{ () }, true),
269        syn::ReturnType::Type(_, typ) => (typ.to_token_stream().into(), false)
270    };
271    let (orig_raw_rettype, orig_ret_is_option) = {
272        if let Some(v) = tokens_match(
273            &orig_ret_type, quote!{ Option< }, quote!{ > }
274        ) {
275            (v, true)
276        }
277        else { (orig_ret_type.clone(), false) }
278    };
279
280    let retstmt = {
281        if is_ret_empty {
282            quote!{ molt_ng::Value::empty() }
283        }
284        else if tokens_equal(&orig_raw_rettype, quote!{ &str }) {
285            quote!{ molt_ng::Value::from(_ret) }
286        }
287        else if tokens_equal(&orig_raw_rettype, quote!{ String }) {
288            quote!{ molt_ng::Value::from(_ret) }
289        }
290        else if let Some(t) = tokens_match(
291            &orig_raw_rettype, quote!{ Vec< }, quote!{ > }
292        ) {
293            quote!{ molt_ng::Value::from_other(
294                molt_argparse::WrapVec<#t>(_ret)) }
295        }
296        else {
297            quote!{ molt_ng::Value::from_other(_ret) }
298        }
299    };
300    let retstmt = match orig_ret_is_option {
301        true => quote!{
302            match _ret {
303                Some(_ret) => Ok(#retstmt),
304                None => Err(molt_ng::Exception::molt_err(molt_ng::Value::empty()))
305            }
306        },
307        false => quote!{ Ok(#retstmt) }
308    };
309
310    let parser = quote!{
311        fn parser_function(_interp: &mut molt_ng::Interp, _ctxid: molt_ng::ContextID, _argv: &[molt_ng::Value]) -> molt_ng::MoltResult {
312            #contextdecl
313            #(#argdecls)*
314            
315            let mut _molt_argparse_i: usize = 0;
316            let mut _molt_argparse_vals = _argv.iter();
317            let _ = _molt_argparse_vals.next().unwrap();  // skip command name
318            let mut _molt_f_parse_key =
319                |_molt_key: &str, _molt_argparse_vals: &mut std::slice::Iter<molt_ng::Value>| ->
320                std::result::Result<bool, molt_ng::Exception>
321            {
322                match _molt_key {
323                    #(#argkeymatch,)*
324                    _ => Ok(false)
325                }
326            };
327            while let Some(_molt_v) = _molt_argparse_vals.next() {
328                if let Some(_molt_v_str) = _molt_v.try_as_str() {
329                    if _molt_f_parse_key(_molt_v_str, &mut _molt_argparse_vals)? {
330                        continue;
331                    }
332                }
333                match _molt_argparse_i {
334                    #(#argfixmatch,)*
335                    _ => { }
336                }
337                let s = _molt_v.as_str();
338                if !_molt_f_parse_key(s, &mut _molt_argparse_vals)? {
339                    return molt_ng::molt_err!("argparse: stray {:?}", s);
340                }
341            }
342            #(#argpostprocess)*
343
344            let _ret = (|#(#orig_arg_defs),*| -> #orig_ret_type {
345                #(#orig_stmts)*
346            })(#(#orig_args),*);
347            #retstmt
348        }
349    };
350    // println!("orig parser: {}", parser);
351
352    let mut decl = decl;
353    let mut parser = syn::parse2::<syn::ItemFn>(parser).unwrap();
354
355    std::mem::swap(&mut decl.sig.inputs, &mut parser.sig.inputs);
356    std::mem::swap(&mut decl.sig.output, &mut parser.sig.output);
357    std::mem::swap(&mut decl.block, &mut parser.block);
358
359    let decl = decl.to_token_stream();
360    // println!("rebuilt code: {}", decl);
361    decl.into()
362}