bddap_aoc_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    parse::{Parse, ParseStream},
5    Token,
6};
7
8struct Args {
9    year: syn::LitInt,
10    day: syn::LitInt,
11    part: syn::LitInt,
12}
13
14impl Parse for Args {
15    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
16        let year = input.parse()?;
17        input.parse::<Token![,]>()?;
18        let day = input.parse()?;
19        input.parse::<Token![,]>()?;
20        let part: syn::LitInt = input.parse()?;
21
22        let part_val = part.to_string();
23        if part_val != "1" && part_val != "2" {
24            return Err(syn::Error::new(part.span(), "part must be 1 or 2"));
25        }
26
27        // optional comma
28        input.parse::<Token![,]>().ok();
29        Ok(Args { year, day, part })
30    }
31}
32
33fn as_challege_struct(args: Args, func: syn::ItemFn) -> syn::ExprStruct {
34    let year = args.year;
35    let day = args.day;
36    let part = args.part;
37    let funcname = &func.sig.ident;
38
39    syn::parse_quote! {
40        ::bddap_aoc::Challenge {
41            name: stringify!(#funcname),
42            year: #year,
43            day: #day,
44            part: #part,
45            run: {
46                #func
47                fn run(input: &str) -> String {
48                    ::std::string::ToString::to_string(& #funcname ( input ))
49                }
50                run
51            },
52        }
53    }
54}
55
56/// Marks a function as a solution to an Advent of Code challenge.
57///
58/// The function must have the signature `fn() -> String`.
59///
60/// # Example
61///
62/// ```rust
63/// #[bddap_aoc::register(2020, 1, 1)]
64/// fn part1() -> String {
65///     "Hello, world!".to_string()
66/// }
67/// ```
68#[proc_macro_attribute]
69pub fn register(attr: TokenStream, item: TokenStream) -> TokenStream {
70    let args = syn::parse_macro_input!(attr as Args);
71    let fn_def = syn::parse_macro_input!(item as syn::ItemFn);
72    let challenge = as_challenge(args, fn_def);
73    quote! {
74        #[::bddap_aoc::linkme::distributed_slice(::bddap_aoc::CHALLENGES)]
75        #challenge
76    }
77    .into()
78}
79
80/// Declare a challenge function without registering it.
81///
82/// ```rust
83/// #[bddap_aoc::unregistered_challenge(2020, 1, 1)]
84/// fn part1() -> String {
85///     "Hello, world!".to_string()
86/// }
87/// ```
88#[proc_macro_attribute]
89pub fn unregistered_challenge(attr: TokenStream, item: TokenStream) -> TokenStream {
90    let args = syn::parse_macro_input!(attr as Args);
91    let fn_def = syn::parse_macro_input!(item as syn::ItemFn);
92    let declaration = as_challenge(args, fn_def);
93    quote!(#declaration).into()
94}
95
96fn as_challenge(args: Args, fn_def: syn::ItemFn) -> syn::ItemStatic {
97    let fn_name = fn_def.sig.ident.clone();
98    let visibility = fn_def.vis.clone();
99    let struct_literal = as_challege_struct(args, fn_def);
100    syn::parse_quote! {
101        #[allow(non_upper_case_globals)]
102        #visibility static #fn_name: ::bddap_aoc::Challenge = #struct_literal;
103    }
104}