aott_derive 0.2.0

Procedural macros for AOTT.
Documentation
//! do not read till the end if you want to keep your sanity.
//! please.
//! i beg you.
//! WARNING: unformatted shitcode ahead.
//! fix PRs accepted.

use proc_macro::TokenStream as TS;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{
        parse::{Parse, Parser},
        punctuated::Punctuated,
        token::Comma,
        AngleBracketedGenericArguments, Expr, ExprPath, FnArg, GenericArgument, GenericParam,
        ItemFn, Lifetime, LifetimeParam, Meta, MetaNameValue, Path, PathArguments, PathSegment,
        ReturnType, Type, TypePath,
};

// example usage:
// ```
// #[parser(extras = Extras)]
// fn case(input: Stream<Token>) -> Case {}
// ```
// into
// ```
// fn case<'input>(input: Input<'input, Stream<Token>, Extras>) -> IResult<'input, Stream<Token>, Extras, Case> {}
// ```

#[proc_macro_attribute]
pub fn parser(args: TS, ts: TS) -> TS {
        let ts: TokenStream = ts.into();

        parser_impl(args.into(), ts)
                .map_or_else(|e| e.to_compile_error(), Into::<TokenStream>::into)
                .into()
}

fn parser_impl(args: TokenStream, ts: TokenStream) -> Result<TokenStream, syn::Error> {
        let meta: Punctuated<Meta, Comma> = Punctuated::parse_terminated.parse2(args)?;
        let mut f = ItemFn::parse.parse2(ts)?;
        let extras = meta.iter().find_map(|meth| match meth {
                Meta::NameValue(MetaNameValue {
                        path,
                        value: Expr::Path(ExprPath { path: ext, .. }),
                        ..
                }) if path.is_ident(&Ident::new("extras", Span::call_site())) => Some(ext.clone()),
                _ => None,
        });
        let parse_lifetime = Lifetime::new("'parse", Span::call_site());
        let mut lifetimes = vec![parse_lifetime.clone()];
        let mut inputs = vec![];
        for inp in f.sig.inputs {
                match inp {
                        FnArg::Receiver(_) => {}
                        FnArg::Typed(mut pat) => {
                                let ty = match *pat.ty {
                                        Type::Reference(mut r) => {
                                                if r.lifetime.is_none() {
                                                        let lifetime = Lifetime::new(
                                                                "'a",
                                                                Span::call_site(),
                                                        );
                                                        r.lifetime = Some(lifetime.clone());

                                                        lifetimes.push(lifetime);
                                                }
                                                Type::Reference(r)
                                        }
                                        t => t,
                                };
                                let extras = extras.unwrap_or_else(|| {
                                        Path::parse.parse2(quote!(extra::Err<#ty>)).unwrap()
                                });
                                let ty_for_ret = ty.clone();
                                let parse_lifetime_for_ret = parse_lifetime.clone();
                                let extras_for_ret = extras.clone();
                                let output = f.sig.output;
                                f.sig.output = ReturnType::Type(Default::default(), Box::new(Type::Path(TypePath { qself: None, path: Path {leading_colon: None, segments: core::iter::once(PathSegment {ident: Ident::new("IResult", Span::call_site()), arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { colon2_token: None, gt_token: Default::default(), lt_token: Default::default(), args: Punctuated::from_iter([GenericArgument::Lifetime(parse_lifetime_for_ret), GenericArgument::Type(ty_for_ret), GenericArgument::Type(Type::Path(TypePath { qself: None, path: extras_for_ret })), GenericArgument::Type(match output { ReturnType::Default => Type::Tuple(syn::TypeTuple { paren_token: Default::default(), elems: Punctuated::new() }), ReturnType::Type(_, t) => *t })]) })}).collect()} })));

                                pat.ty = Box::new(Type::Path(TypePath {
                                        qself: None,
                                        path: Path {
                                                leading_colon: None,
                                                segments: core::iter::once(PathSegment {
                                                        ident: Ident::new("Input", Span::call_site()),
                                                        arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
                                                                args: Punctuated::from_iter([
                                                                        GenericArgument::Lifetime(parse_lifetime),
                                                                        GenericArgument::Type(ty),
                                                                        GenericArgument::Type(Type::Path(TypePath {
                                                                                path: extras,
                                                                                qself: None
                                                                        }))
                                                                ]),
                                                                colon2_token: None,
                                                                lt_token: Default::default(),
                                                                gt_token: Default::default(),
                                                        })
                                                }).collect(),
                                        },
                                }));
                                inputs.push(FnArg::Typed(pat));
                                break;
                        }
                }
        }
        f.sig.inputs = inputs.into_iter().collect();
        f.sig.generics
                .params
                .extend(lifetimes.into_iter().map(|lifetime| {
                        GenericParam::Lifetime(LifetimeParam {
                                attrs: vec![],
                                lifetime,
                                colon_token: None,
                                bounds: Punctuated::new(),
                        })
                }));
        Ok(quote! {
            #f
        })
}