summaryrefslogtreecommitdiff
path: root/dhall_proc_macros/src/make_parser.rs
blob: 268a63960bd2d765f0bec47ec9fedbd7951970dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use quote::quote;
use syn::parse::{ParseStream, Result};
use syn::spanned::Spanned;
use syn::{
    parse_quote, Error, Expr, FnArg, Ident, ImplItem, ImplItemMethod, ItemImpl,
    Pat, Token,
};

fn apply_special_attrs(function: &mut ImplItemMethod) -> Result<()> {
    *function = parse_quote!(
        #[allow(non_snake_case, dead_code)]
        #function
    );

    let recognized_attrs: Vec<_> = function
        .attrs
        .drain_filter(|attr| attr.path.is_ident("prec_climb"))
        .collect();

    let name = function.sig.ident.clone();

    if recognized_attrs.is_empty() {
        // do nothing
    } else if recognized_attrs.len() > 1 {
        return Err(Error::new(
            recognized_attrs[1].span(),
            "expected a single prec_climb attribute",
        ));
    } else {
        let attr = recognized_attrs.into_iter().next().unwrap();
        let (child_rule, climber) =
            attr.parse_args_with(|input: ParseStream| {
                let child_rule: Ident = input.parse()?;
                let _: Token![,] = input.parse()?;
                let climber: Expr = input.parse()?;
                Ok((child_rule, climber))
            })?;

        // Get the name of the first (`input`) function argument
        let first_arg = function.sig.inputs.first().ok_or_else(|| {
            Error::new(
                function.sig.inputs.span(),
                "a prec_climb function needs 4 arguments",
            )
        })?;
        let first_arg = match &first_arg {
            FnArg::Receiver(_) => return Err(Error::new(
                first_arg.span(),
                "a prec_climb function should not have a `self` argument",
            )),
            FnArg::Typed(first_arg) =>   match &*first_arg.pat{
                Pat::Ident(ident) => &ident.ident,
                _ => return Err(Error::new(
                    first_arg.span(),
                    "this argument should be a plain identifier instead of a pattern",
                )),
            }
        };

        function.block = parse_quote!({
            #function

            #climber.climb(
                #first_arg.pair.clone().into_inner(),
                |p| Self::#child_rule(#first_arg.with_pair(p)),
                |l, op, r| {
                    #name(#first_arg.clone(), l?, op, r?)
                },
            )
        });
        // Remove the 3 last arguments to keep only the `input` one
        function.sig.inputs.pop();
        function.sig.inputs.pop();
        function.sig.inputs.pop();
        // Check that an argument remains
        function.sig.inputs.first().ok_or_else(|| {
            Error::new(
                function.sig.inputs.span(),
                "a prec_climb function needs 4 arguments",
            )
        })?;
    }

    Ok(())
}

pub fn make_parser(
    attrs: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> Result<proc_macro2::TokenStream> {
    let rule_enum: Ident = syn::parse(attrs)?;

    let mut imp: ItemImpl = syn::parse(input)?;
    imp.items
        .iter_mut()
        .map(|item| match item {
            ImplItem::Method(m) => apply_special_attrs(m),
            _ => Ok(()),
        })
        .collect::<Result<()>>()?;

    let ty = &imp.self_ty;
    let (impl_generics, _, where_clause) = imp.generics.split_for_impl();
    Ok(quote!(
        impl #impl_generics PestConsumer for #ty #where_clause {
            type RuleEnum = #rule_enum;
        }

        #imp
    ))
}