Skip to main content

bin_setup

Attribute Macro bin_setup 

Source
#[bin_setup]
Available on crate feature binary only.
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);
}