Skip to main content

bin_setup

Attribute Macro bin_setup 

Source
#[bin_setup]
Expand description

This macro can be used to generate my standard approach to Advent of Code binaries.

It can only be attached to fn main().
Required arguments:

  • puzzles_count, number of puzzles in the binary
  • resources_directory, path to inputs and answers files, relative to src
  • input_extension, input files extension
  • answers_file, file containing the puzzle answers for checking execution

A simple pretty_solution_2 macro is provided to:

  • embed input strings
  • execute solutions
  • measure execution time
  • verify output against provided answers

The generated main function provides args handling to execute only specific days, e.g. $ executable 1 3 5

Example usage:

use aoc_core_macros::bin_setup;

#[bin_setup(5, "../resources", ".in", "Answers.out")]
fn main() {
    pretty_solution_2!(5, "PuzzleX", solution1, solution2);
}

fn solution1(input: &str) -> u8 {
    input.lines().map(|n| n.parse::<u8>().unwrap()).sum()
}

fn solution2(input: &str) -> u8 {
    input.lines().map(|n| n.parse::<u8>().unwrap()).product()
}

// ../resources/PuzzleX.in
// 2
// 3

// ../resources/Answers.out
// PuzzleX 5 6

Generated code looks like this:

#[allow(clippy::items_after_statements)]
fn main() {
    let puzzle_answers: rustc_hash::FxHashMap<&'static str, [&'static str; 2]> =
        include_str!(concat!("../resources", "/", "Answers.out"))
            .lines()
            .map(|line| {
                let parts: Vec<_> = line.split_ascii_whitespace().collect();

                (parts[0], [parts[1], parts[2]])
            })
            .collect();

    let selected_puzzles: [bool; 5] = {
        let args: Vec<_> = std::env::args().collect();

        if args.len() == 1 {
            [true; 5]
        } else {
            std::array::from_fn(|day| args.contains(&(day + 1).to_string()))
        }
    };

    #[inline]
    fn pretty_solution<R>(
        puzzle: &str,
        part: usize,
        solution: fn(&str) -> R,
        input: &str,
        answer: &str,
    ) where
        R: std::fmt::Display + PartialEq,
    {
        let now = std::time::Instant::now();
        let solution = solution(input);
        let microseconds = now.elapsed().as_micros();

        assert!(
            solution.to_string() == answer,
            "Wrong solution for {puzzle} part {part}: expected {answer}, but got {solution}"
        );

        println!("{part} -> {answer} ({microseconds}μs)");
    }

    macro_rules! pretty_solution_2 {
        ($day:literal, $puzzle: literal, $solution1:ident $(,$solution2:ident)?) => {
            if selected_puzzles[$day - 1] {
                println!("Day {}: {}", $day, $puzzle);

                const INPUT: &str =
                    include_str!(concat!("../resources", "/", $puzzle, ".in"));
                let answers = puzzle_answers.get($puzzle).expect("Puzzle answer not found");

                pretty_solution($puzzle, 1, $solution1, INPUT, answers[0]);

                $(pretty_solution($puzzle, 2, $solution2, INPUT, answers[1]);)?

                println!();
            }
        };
    }

    pretty_solution_2!(5, "PuzzleX", solution1, solution2);
}