adventage_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use quote::ToTokens;
4use quote::format_ident;
5use syn::{Lit, Expr, Token, Result};
6use syn::punctuated::Punctuated;
7use syn::parse::Parser;
8use syn::parse::Parse;
9use syn::parse::ParseStream;
10
11static mut DEMOS: u32 = 0;
12
13#[proc_macro]
14pub fn day(attrs: TokenStream) -> TokenStream {
15    let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
16	let args = parser.parse(attrs).unwrap().iter()
17		.filter_map(|x| match x {
18			Expr::Lit(el) => Some(el.lit.clone()),
19			_ => None
20		})
21		.filter_map(|lit| match lit {
22			Lit::Int(li) => Some(li.base10_parse::<u32>().unwrap()),
23			_ => None,
24		}).collect::<Vec<u32>>();
25
26	let year = args.iter().max().unwrap();
27	let day = args.iter().min().unwrap(); 
28
29    let main: syn::ItemFn = syn::parse(quote! {
30        fn main() -> Result<(), ()> {
31            let fetch_start = std::time::Instant::now();
32            let input = adventage::fetch_day(#year, #day);
33            let fetch_runtime = adventage::format_runtime(fetch_start.elapsed());
34            println!("Fetching the input took {fetch_runtime}");
35
36            let parsed_start = std::time::Instant::now();
37            let parsed = parse(&input);
38            let parse_runtime = adventage::format_runtime(parsed_start.elapsed());
39            println!("Parsing the input took {parse_runtime}");
40
41            let part1_start = std::time::Instant::now();
42            let answer1 = part1(&parsed);
43            let part1_runtime = adventage::format_runtime(part1_start.elapsed());
44            println!("Answer 1: {answer1}, took {part1_runtime}");
45
46            let part2_start = std::time::Instant::now();
47            let answer2 = part2(&parsed);
48            let part2_runtime = adventage::format_runtime(part2_start.elapsed());
49            println!("Answer 2: {answer2}, took {part2_runtime}");
50            Ok(())
51        }
52    }.into()).unwrap();
53
54    main.to_token_stream().into()
55}
56
57#[derive(Debug)]
58enum Answer {
59    StringAnswer(String),
60    NumberAnswer(String),
61}
62
63impl Parse for Answer {
64    fn parse(input: ParseStream) -> Result<Self> {
65        let literal: Lit = input.parse()?;
66
67        match literal {
68            Lit::Str(ls) => Ok(Answer::StringAnswer(ls.value())),
69            Lit::Int(li) => Ok(Answer::NumberAnswer(li.base10_digits().to_string())),
70            _ => panic!(),
71        }
72    }
73}
74
75#[proc_macro]
76pub fn part1demo(attrs: TokenStream) -> TokenStream {
77    let parser = Punctuated::<Answer, Token![,]>::parse_terminated;
78    let result = parser.parse(attrs).unwrap();
79    let Answer::StringAnswer(input) = &result[0] else { panic!() };
80    let num = unsafe { DEMOS += 1; DEMOS };
81    let name = format_ident!("test_part1_{}", num);
82
83    let assertion = match &result[1] {
84        Answer::StringAnswer(answer) => quote! { assert_eq!(answer, #answer); },
85        Answer::NumberAnswer(answer) => quote! { assert_eq!(answer, #answer.parse().unwrap()); }
86    };
87
88    let test: syn::ItemFn = syn::parse(quote! {
89        #[test]
90        fn #name() {
91            let parsed_input = parse(#input);
92            let answer = part1(&parsed_input);
93
94            println!("Part 1 example:");
95            println!("{}", #input);
96            println!("Part 1 answer: {}", answer);
97            println!("--");
98
99            #assertion
100        }
101    }.into()).unwrap();
102
103    test.to_token_stream().into()
104}
105
106#[proc_macro]
107pub fn part2demo(attrs: TokenStream) -> TokenStream {
108    let parser = Punctuated::<Answer, Token![,]>::parse_terminated;
109    let result = parser.parse(attrs).unwrap();
110    let Answer::StringAnswer(input) = &result[0] else { panic!() };
111
112    let assertion = match &result[1] {
113        Answer::StringAnswer(answer) => quote! { assert_eq!(answer, #answer); },
114        Answer::NumberAnswer(answer) => quote! { assert_eq!(answer, #answer.parse().unwrap()); }
115    };
116 
117    let num = unsafe { DEMOS += 1; DEMOS };
118    let name = format_ident!("test_part2_{}", num);
119
120    let test: syn::ItemFn = syn::parse(quote! {
121        #[test]
122        fn #name() {
123            let parsed_input = parse(#input);
124            let answer = part2(&parsed_input);
125
126            println!("Part 2 example:");
127            println!("{}", #input);
128            println!("Part 2 answer: {}", answer);
129            println!("--");
130
131            #assertion
132        }
133    }.into()).unwrap();
134
135    test.to_token_stream().into()
136}