aoc_toolbox_derive/
lib.rsuse std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::collections::HashSet;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::{parse_macro_input, AttributeArgs, ItemFn, Lit, NestedMeta};
use inflector::Inflector;
thread_local! {
static AOC_SOLVERS: RefCell<HashMap<String, HashSet<String>>> = RefCell::new(HashMap::new());
}
fn format_trait(day: &String, part: &String) -> Ident {
format_ident!("{}{}", day.to_title_case(), part.to_title_case())
}
fn format_module(day: &String, part: &String) -> Ident {
format_ident!("Mod{}{}", day.to_title_case(), part.to_title_case())
}
#[proc_macro_attribute]
pub fn aoc_solver(
input: proc_macro::TokenStream,
annotated_item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attributes = parse_macro_input!(input as AttributeArgs);
let attributes: Vec<String> = attributes
.iter()
.map(|a| match a {
NestedMeta::Lit(Lit::Str(s)) => s.value(),
_ => panic!("Attribute is not a string"),
})
.collect();
if attributes.len() != 2 {
panic!("Number of attributes must be two");
}
let day = attributes[0].clone();
let part = attributes[1].clone();
let trait_name = format_trait(&day, &part);
let module_name = format_module(&day, &part);
let func = parse_macro_input!(annotated_item as ItemFn);
let function = format_ident!("{}", func.sig.ident.to_string());
let solve_impl = quote! {
mod #module_name {
use super::*;
use crate::#trait_name;
impl<'a> #trait_name for aoc_toolbox::Aoc<'a> {
fn solve() -> String {
#function(aoc_toolbox::utils::load_input(#day))
}
}
}
};
AOC_SOLVERS.with(|solvers| {
match solvers.borrow_mut().entry(day) {
Entry::Occupied(mut e) => {
if e.get().contains(&part) {
panic!("Part \"{}\" for day \"{}\" exists already", part, e.key());
}
e.get_mut().insert(part);
}
Entry::Vacant(e) => {
e.insert(HashSet::new()).insert(part);
}
};
});
quote! {
#func
#solve_impl
}
.into_token_stream()
.into()
}
#[proc_macro]
pub fn aoc_main(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let year = parse_macro_input!(input as Lit);
AOC_SOLVERS.with(|solvers| {
let traits: Vec<TokenStream> = solvers
.borrow_mut()
.iter()
.flat_map(|(day, parts)| {
let ret: Vec<TokenStream> = parts
.iter()
.map(|part| {
let trait_name = format_trait(day, part);
quote! {
trait #trait_name {
fn solve() -> String;
}
}
})
.collect();
ret
})
.collect();
let adders: Vec<TokenStream> = solvers
.borrow_mut()
.iter()
.flat_map(|(day, parts)| {
let ret: Vec<TokenStream> = parts
.iter()
.map(|part| {
let trait_name = format_trait(day, part);
quote! {
aoc.add_solver(#day, #part, <aoc_toolbox::Aoc as #trait_name>::solve);
}
})
.collect();
ret
})
.collect();
quote! {
#( #traits )*
fn main() -> Result<(), Box<dyn std::error::Error>> {
use aoc_toolbox::{clap, Parser};
#[derive(aoc_toolbox::Parser, Debug)]
#[clap(about, long_about = None)]
struct Args {
#[clap(index = 1, value_parser, default_value = "all")]
solver: String,
#[clap(short, long, value_parser, exclusive = true)]
list: bool,
#[clap(short = 'd', long, value_parser)]
with_duration: bool,
}
let args = Args::parse();
let mut aoc = aoc_toolbox::Aoc::new( #year , args.with_duration);
#( #adders )*
if args.list {
aoc.list();
return Ok(());
}
aoc.run(args.solver)?;
Ok(())
}
}
.into()
})
}