summaryrefslogtreecommitdiff
path: root/dhall_proc_macros/src/make_parser.rs
blob: 63ce7791a75819f8f52b33095296cac9f2772707 (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
use quote::quote;
use syn::parse::{ParseStream, Result};
use syn::spanned::Spanned;
use syn::{
    parse_quote, Error, Expr, Ident, ImplItem, ImplItemMethod, ItemImpl,
    ReturnType, Token,
};

fn apply_special_attrs(
    rule_enum: &Ident,
    function: &mut ImplItemMethod,
) -> Result<()> {
    let recognized_attrs: Vec<_> = function
        .attrs
        .drain_filter(|attr| attr.path.is_ident("prec_climb"))
        .collect();

    let name = function.sig.ident.clone();
    let output_type = match &function.sig.output {
        ReturnType::Default => parse_quote!(()),
        ReturnType::Type(_, t) => (**t).clone(),
    };

    if recognized_attrs.is_empty() {
    } 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))
            })?;

        *function = parse_quote!(
            fn #name<'a>(
                input: ParseInput<'a, #rule_enum>,
            ) -> #output_type {
                #[allow(non_snake_case, dead_code)]
                #function

                #climber.climb(
                    input.pair.clone().into_inner(),
                    |p| Self::#child_rule(input.with_pair(p)),
                    |l, op, r| {
                        #name(input.clone(), l?, op, r?)
                    },
                )
            }
        );
    }

    *function = parse_quote!(
        #[allow(non_snake_case, dead_code)]
        #function
    );

    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(&rule_enum, 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
    ))
}