aoc-runner-derive 0.2.0

Codegen for aoc-runner & others
Documentation
use aoc_runner_internal::{DayParts, DayPartsBuilder};
use crate::map::InnerMap;
use crate::utils::{to_camelcase, to_input, to_snakecase};
use crate::AOC_RUNNER;
use proc_macro as pm;
use proc_macro2 as pm2;
use quote::quote;
use std::error;

#[derive(Debug)]
struct LibInfos {
    year: u32,
}

#[derive(Debug)]
enum MainInfos {
    Ref { lib: pm2::Ident },
    Standalone { year: u32 },
}

pub fn lib_impl(input: pm::TokenStream) -> pm::TokenStream {
    let infos = parse_lib_infos(input).expect("failed to parse lib infos");

    AOC_RUNNER.with(|map| {
        let map = map.consume().expect("failed to consume map from lib");

        let year = infos.year;

        write_infos(&map, year).expect("failed to write infos from lib");

        pm::TokenStream::from(headers(&map, year))
    })
}

pub fn main_impl(input: pm::TokenStream) -> pm::TokenStream {
    let infos = parse_main_infos(input).expect("failed to parse main infos");

    AOC_RUNNER.with(|map| {
        let map = map.consume().expect("failed to consume map from main");

        let expanded = match infos {
            MainInfos::Ref { lib } => {
                let infos = read_infos().expect("failed to read infos from ref main");
                body(&infos, Some(lib))
            }
            MainInfos::Standalone { year } => {
                let infos =
                    write_infos(&map, year).expect("failed to write infos from standalone main");
                let headers = headers(&map, year);
                let body = body(&infos, None);

                quote! {
                    #headers

                    #body
                }
            }
        };

        pm::TokenStream::from(expanded)
    })
}

fn headers(map: &InnerMap, year: u32) -> pm2::TokenStream {
    let traits_impl: pm2::TokenStream = map
        .keys()
        .map(|dp| {
            let snake = to_snakecase(&dp);
            let camel = to_camelcase(&dp);

            quote! {
                #[doc(hidden)]
                pub trait #camel {
                    fn #snake(input: ArcStr) -> Result<Box<dyn Runner>, Box<dyn Error>>;
                }
            }
        }).collect();

    quote! {
        pub use self::aoc_factory::*;

        #[allow(unused)]
        mod aoc_factory {
            use aoc_runner::{Runner, ArcStr};
            use std::error::Error;

            #[doc(hidden)]
            pub static YEAR : u32 = #year;

            #[doc(hidden)]
            pub struct Factory();

            #traits_impl
        }
    }
}

fn body(infos: &DayParts, lib: Option<pm2::Ident>) -> pm2::TokenStream {
    let mut days: Vec<_> = infos.iter().map(|dp| dp.day).collect();
    days.sort();
    days.dedup();

    let inputs: pm2::TokenStream = days
        .into_iter()
        .map(|d| {
            let name = to_input(d);
            let input = format!("../input/{}/day{}.txt", infos.year, d.0);

            quote! { let #name = ArcStr::from(include_str!(#input)); }
        }).collect();

    let body : pm2::TokenStream = infos.iter().map(|dp| {
        let identifier = to_snakecase(dp);
        let (pattern, err) = if let Some(n) = &dp.name {
            (
                format!(
                    "Day {} - Part {} - {}: {{}}\n\tgenerator: {{:?}},\n\trunner: {{:?}}\n",
                    dp.day.0, dp.part.0, n
                ),
                format! (
                    "Day {} - Part {} - {}: FAILED while {{}}:\n{{:#?}}\n",
                    dp.day.0, dp.part.0, n
                )
            )
        } else {
            (
                format!(
                    "Day {} - Part {}: {{}}\n\tgenerator: {{:?}},\n\trunner: {{:?}}\n",
                    dp.day.0, dp.part.0
                ),
                format! (
                    "Day {} - Part {}: FAILED while {{}}:\n{{:#?}}\n",
                    dp.day.0, dp.part.0
                )
            )
        };

        let input = to_input(dp.day);

        quote! {
            {
                let start_time = Instant::now();

                match Factory::#identifier(#input.clone()) {
                    Ok(runner) => {
                        let inter_time = Instant::now();

                        match runner.try_run() {
                            Ok(result) => {
                                let final_time = Instant::now();
                                println!(#pattern, result, (inter_time - start_time), (final_time - inter_time));
                            },
                            Err(e) => eprintln!(#err, "running", e)
                        }
                    },
                    Err(e) => eprintln!(#err, "generating", e)
                }
            }
        }
    }).collect();

    if let Some(lib) = lib {
        quote! {
            use #lib::*;

            fn main() {
                use aoc_runner::ArcStr;
                use std::time::{Duration, Instant};

                #inputs

                println!("Advent of code {}", YEAR);

                #body
            }
        }
    } else {
        quote! {
            fn main() {
                use aoc_runner::ArcStr;
                use std::time::{Duration, Instant};


                #inputs

                println!("Advent of code {}", YEAR);

                #body
            }
        }
    }
}

fn write_infos(map: &InnerMap, year: u32) -> Result<DayParts, Box<dyn error::Error>> {
    let mut day_parts = map
        .iter()
        .filter_map(|(dp, runner)| {
            if runner.solver.is_some() {
                Some(dp.clone())
            } else {
                None
            }
        }).collect::<DayPartsBuilder>()
        .with_year(year);

    day_parts.sort();

    day_parts.save()?;

    Ok(day_parts)
}

fn read_infos() -> Result<DayParts, Box<dyn error::Error>> {
    DayParts::load()
}

fn parse_lib_infos(infos: pm::TokenStream) -> Result<LibInfos, ()> {
    let tokens: Vec<_> = infos.into_iter().collect();

    if let pm::TokenTree::Ident(i) = tokens.get(0).ok_or(())? {
        if i.to_string() != "year" {
            return Err(());
        }
    } else {
        return Err(());
    }

    if let pm::TokenTree::Punct(p) = tokens.get(1).ok_or(())? {
        if p.as_char() != '=' {
            return Err(());
        }
    } else {
        return Err(());
    }

    if let pm::TokenTree::Literal(l) = tokens.get(2).ok_or(())? {
        let repr = l.to_string();

        let year = repr.parse().map_err(|_| ())?;

        Ok(LibInfos { year })
    } else {
        Err(())
    }
}

fn parse_main_infos(infos: pm::TokenStream) -> Result<MainInfos, ()> {
    let tokens: Vec<_> = infos.into_iter().collect();

    if let pm::TokenTree::Punct(p) = tokens.get(1).ok_or(())? {
        if p.as_char() != '=' {
            return Err(());
        }
    } else {
        return Err(());
    }

    if let pm::TokenTree::Ident(i) = tokens.get(0).ok_or(())? {
        let ty = i.to_string();

        Ok(match ty.as_ref() {
            "year" => {
                let repr = if let pm::TokenTree::Literal(l) = tokens.get(2).ok_or(())? {
                    l.to_string()
                } else {
                    return Err(());
                };

                let year = repr.parse().map_err(|_| ())?;
                MainInfos::Standalone { year }
            }
            "lib" => {
                let lib = if let pm::TokenTree::Ident(i) = tokens.get(2).ok_or(())? {
                    pm2::Ident::new(&i.to_string(), pm2::Span::from(i.span()))
                } else {
                    return Err(());
                };

                MainInfos::Ref { lib }
            }
            _ => return Err(()),
        })
    } else {
        return Err(());
    }
}