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}