1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse::{Parse, ParseStream, Result};
4use syn::{parse_macro_input, Expr, LitInt, Token};
5
6struct ClientArgs {
7 year: u16,
8 day: u8,
9 test_input_file: Option<String>,
10}
11
12impl Parse for ClientArgs {
13 fn parse(input: ParseStream) -> Result<Self> {
14 let help_text = format!(
15 "Provide a year and a day, e.g. #[aocd({})]",
16 chrono::Utc::now().format("%Y, %d")
17 );
18
19 let year = input
20 .parse::<LitInt>()
21 .unwrap_or_else(|_| panic!("Expected a literal year. {help_text}"))
22 .base10_parse::<u16>()?;
23 input
24 .parse::<Token![,]>()
25 .unwrap_or_else(|_| panic!("Expected 2 arguments. {help_text}"));
26 let day = input
27 .parse::<LitInt>()
28 .unwrap_or_else(|_| panic!("Expected a literal day. {help_text}"))
29 .base10_parse::<u8>()?;
30
31 let mut test_input_file = None;
32 if input.parse::<Token![,]>().is_ok() {
33 if let Ok(file_name) = input.parse::<syn::LitStr>() {
34 assert!(
35 std::fs::metadata(file_name.value()).is_ok(),
36 "Test file {} does not exist",
37 file_name.value()
38 );
39 test_input_file = Some(file_name.value());
40 }
41 }
42
43 Ok(ClientArgs {
44 year,
45 day,
46 test_input_file,
47 })
48 }
49}
50
51struct SubmitArgs {
52 part: Expr,
53 answer: Expr,
54}
55
56impl Parse for SubmitArgs {
57 fn parse(input: ParseStream) -> Result<Self> {
58 let part = input.parse::<Expr>()?;
59
60 if let Expr::Lit(part_lit) = &part {
62 if let syn::Lit::Int(part_int) = &part_lit.lit {
63 if let Ok(part) = part_int.base10_parse::<i64>() {
64 assert!(part == 1 || part == 2, "Part should be 1 or 2, not {part}",);
65 }
66 }
67 }
68
69 input.parse::<Token![,]>()?;
70 let answer: Expr = input.parse()?;
71 Ok(SubmitArgs { part, answer })
72 }
73}
74
75#[proc_macro_attribute]
109pub fn aocd(attr: TokenStream, input: TokenStream) -> TokenStream {
110 let args = parse_macro_input!(attr as ClientArgs);
111 let year = args.year;
112 let day = args.day;
113 let test_input_file = args.test_input_file;
114
115 assert!(
117 year >= 2015,
118 "The first Advent of Code was in 2015, not {year}.",
119 );
120 assert!(
121 (1..=25).contains(&day),
122 "Chose a day from 1 to 25, not {day}.",
123 );
124
125 let mut fn_item: syn::ItemFn = syn::parse(input).unwrap();
126 if let Some(test_input_file) = test_input_file {
127 fn_item.block.stmts.insert(
128 0,
129 syn::parse(
130 quote!( let __aocd_client = aocd::Aocd::new(#year, #day, Some(#test_input_file));)
131 .into(),
132 )
133 .unwrap(),
134 );
135 } else {
136 fn_item.block.stmts.insert(
137 0,
138 syn::parse(quote!( let __aocd_client = aocd::Aocd::new(#year, #day, None);).into())
139 .unwrap(),
140 );
141 }
142
143 TokenStream::from(quote!(#fn_item))
144}
145
146#[proc_macro]
153pub fn input(_: TokenStream) -> TokenStream {
154 TokenStream::from(quote!(__aocd_client.get_input()))
155}
156
157#[proc_macro]
164pub fn submit(args: TokenStream) -> TokenStream {
165 let args = parse_macro_input!(args as SubmitArgs);
166 let part = args.part;
167 let answer = args.answer;
168 TokenStream::from(quote!(__aocd_client.submit(#part, #answer)))
169}