elvish_macros/
lib.rs

1//! Macros for elvish. 
2
3#![warn(missing_docs)]
4
5use proc_macro::TokenStream;
6use syn::parse_macro_input;
7
8mod declare;
9mod example;
10mod solution;
11
12/// A solution of an advent of code problem. 
13///
14/// You need to pass in `day = X` for the macro to work. If the function is named `part1` or 
15/// `part2`, the part gets set accordingly; otherwise you need to specify it as `part = Y`.
16///
17/// You can also specify the expected result of the example given in the puzzle using `example = Z`
18/// or `example = [A, B, C, ...]` if there are multiple. The examples need to be defined somewhere
19/// using [`elvish::example!()`](example!()) for them to work. 
20///
21/// At the end of the day, this macro is mostly to reduce boilerplate but it's easily expandable by
22/// hand. 
23///
24/// # Example usage
25///
26/// Solution for day 1 of 2023:
27///
28/// ```rust
29/// # struct Solutions;
30/// #[elvish::solution(day = 1, example = 142)]
31/// fn part1(input: &str) -> u32 {
32///     input
33///         .lines()
34///         .filter(|line| !line.is_empty())
35///         .map(|line| {
36///             let mut iter = line.chars().filter_map(|c| c.to_digit(10));
37/// 
38///             let a = iter.next().unwrap();
39///             let b = iter.last().unwrap_or(a);
40/// 
41///             a * 10 + b
42///         })
43///         .sum()
44/// }
45/// ```
46///
47/// which generates:
48/// 
49/// ```rust
50/// # struct Solutions;
51/// # const EXAMPLE_PART1: &str = "yo";
52/// impl elvish::solution::Part<1, 1> for crate::Solutions {
53///     fn solve(input: &str) -> impl std::fmt::Display {
54///         part1(input)
55///     }
56/// }
57/// 
58/// #[test]
59/// fn part1_example() {
60///     assert_eq!(part1(EXAMPLE_PART1), 142)
61/// }
62///
63/// fn part1(input: &str) -> u32 {
64/// // --snip--
65/// }
66/// ```
67#[proc_macro_attribute]
68pub fn solution(attr: TokenStream, item: TokenStream) -> TokenStream {
69    solution::expand(attr, item)
70}
71
72/// Defines examples given in advent of code puzzles. The strings in the example are unindented
73/// using [`indoc`](https://docs.rs/indoc). 
74///
75/// There are three cases that make up 90% of examples in advent of code, which this macro
76/// addresses. Namely:
77///
78/// - One example is given for both part 1 and part 2:
79///
80/// ```rust
81/// elvish::example!("
82///     YOUR
83///     EXAMPLE
84///     HERE
85/// ");
86/// ```
87/// - Part 1 and part 2 have each one example:
88///
89/// ```rust
90/// elvish::example!(
91///     part1: "
92///         YOUR
93///         PART 1
94///         EXAMPLE
95///         HERE
96///     ",
97///
98///     part2: "
99///         YOUR
100///         PART 2
101///         EXAMPLE
102///         HERE
103///     ",
104/// );
105/// ```
106///
107/// - Part 1 and part 2 have more than one example:
108///
109/// ```rust
110/// elvish::example!(
111///     part1: "
112///         YOUR
113///         FIRST PART 1
114///         EXAMPLE
115///         HERE
116///     ",
117///
118///     part1: "
119///         YOUR
120///         SECOND PART 1
121///         EXAMPLE
122///         HERE
123///     ",
124///
125///     part2: "
126///         YOUR
127///         FIRST PART 2
128///         EXAMPLE
129///         HERE
130///     ",
131///
132///     part2: "
133///         YOUR
134///         SECOND PART 2
135///         EXAMPLE
136///         HERE
137///     ",
138/// );
139/// ```
140#[proc_macro]
141pub fn example(input: TokenStream) -> TokenStream {
142    parse_macro_input!(input as example::Example).expand()
143}
144
145/// Declare modules for each day of advent of code.
146///
147/// Expands to 
148/// 
149/// ```rust
150/// #[cfg(feature="day01")]
151/// mod day01;
152/// #[cfg(feature="day02")]
153/// mod day02;
154/// #[cfg(feature="day03")]
155/// mod day03;
156///
157/// // etc...
158/// ```
159#[proc_macro]
160pub fn declare_modules(_input: TokenStream) -> TokenStream {
161    declare::modules()
162}
163
164/// Declare a function that can run advent of code solutions dynamically based on the
165/// aviable (think, solved) days.
166///
167/// Expands to 
168/// 
169/// ```rust
170/// fn run_day_part(day: u8, part: u8, input: &str) -> eyre::Result<String> {
171///     #[cfg(feature = "day01")]
172///     if day == 01 {
173///         #[cfg(feature = "part1")]
174///         if part == 0 {
175///             return Ok(elvish::solution::run_day_part::<Solutions, 17u8, 1>(input));
176///         }
177///         #[cfg(feature = "part2")]
178///         if part == 1 {
179///             return Ok(elvish::solution::run_day_part::<Solutions, 17u8, 2>(input));
180///         }
181///     }
182///
183///     #[cfg(feature = "day02")]
184///     if day == 02 {
185///         #[cfg(feature = "part1")]
186///         if part == 0 {
187///             return Ok(elvish::solution::run_day_part::<Solutions, 17u8, 1>(input));
188///         }
189///         #[cfg(feature = "part2")]
190///         if part == 1 {
191///             return Ok(elvish::solution::run_day_part::<Solutions, 17u8, 2>(input));
192///         }
193///     }
194/// }
195/// 
196/// // etc...
197/// ```
198#[proc_macro]
199pub fn declare_run_fn(_input: TokenStream) -> TokenStream {
200    declare::run_fn()
201}
202
203/// Declares an array of available days, based on feature flags.
204///
205/// Expands to 
206/// 
207/// ```rust
208/// [
209///
210///     #[cfg(feature="day01")]
211///     1,
212///     #[cfg(feature="day02")]
213///     2,
214///     #[cfg(feature="day03")]
215///     3,
216///
217///     // etc...
218/// ]
219///
220/// ```
221#[proc_macro]
222pub fn available_days(_input: TokenStream) -> TokenStream {
223    declare::available_days()
224}