commit 5b84356e5ee9d06f7887ea934fb03fc943658e4c Author: LordMZTE Date: Sat Dec 4 23:24:26 2021 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7033fdc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "libaoc", + "libaoc-macros", +] diff --git a/libaoc-macros/Cargo.toml b/libaoc-macros/Cargo.toml new file mode 100644 index 0000000..590faf5 --- /dev/null +++ b/libaoc-macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libaoc-macros" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +syn = { version = "1.0", features = ["full"] } + +[features] diff --git a/libaoc-macros/src/lib.rs b/libaoc-macros/src/lib.rs new file mode 100644 index 0000000..6abce3d --- /dev/null +++ b/libaoc-macros/src/lib.rs @@ -0,0 +1,108 @@ +#![feature(proc_macro_diagnostic)] + +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token::Comma, + Expr, Item, ItemMod, +}; + +#[proc_macro_attribute] +pub fn day(args: TokenStream, input: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(input as ItemMod); + let args = parse_macro_input!(args as Args); + + let content = if let Some(c) = &mut input.content { + &mut c.1 + } else { + input.span().unwrap().error("Module must have body!").emit(); + return quote!(#input).into(); + }; + + let fns = content + .iter() + .filter_map(|i| match i { + Item::Fn(fun) => Some(fun), + _ => None, + }) + .collect::>(); + + let part1 = fns + .iter() + .find(|i| &i.sig.ident.to_string() == "part1") + .copied(); + let part2 = fns + .iter() + .find(|i| &i.sig.ident.to_string() == "part2") + .copied(); + + if part1.is_none() { + input + .span() + .unwrap() + .error("Need part1 function, which is not found within module.") + .emit(); + return quote!(#input).into(); + } + + let part2 = if part2.is_some() { + quote!(Some(self::part2)) + } else { + quote!(None) + }; + + let name = args.name; + content.push(parse_quote! { + pub fn aoc_day() -> libaoc::Day { + libaoc::Day { + name: format!("{}", #name), + part1: self::part1, + part2: #part2, + } + } + }); + + if let Some(infile) = args.input { + content.push(parse_quote!{ + const INPUT: &str = include_str!(#infile); + }); + } + + quote!(#input).into() +} + +struct Args { + name: Expr, + input: Option, +} + +impl Parse for Args { + fn parse(input: ParseStream) -> syn::Result { + let mut puncted = Punctuated::::parse_separated_nonempty(input)?.into_iter(); + let name = puncted.next(); + if name.is_none() { + return Err(syn::Error::new( + input.span(), + "Must provide at least one argument for the day name!", + )); + } + + let input = puncted.next(); + + if let Some(excess) = puncted.next() { + return Err(syn::Error::new( + excess.span(), + "No more than 2 arguments allowed!", + )); + } + + Ok(Self { + name: name.unwrap(), + input, + }) + } +} diff --git a/libaoc/Cargo.toml b/libaoc/Cargo.toml new file mode 100644 index 0000000..14b2048 --- /dev/null +++ b/libaoc/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "libaoc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libaoc-macros = { path = "../libaoc-macros" } +miette = { version = "3.2", features = ["fancy"] } +owo-colors = "2.1" diff --git a/libaoc/src/lib.rs b/libaoc/src/lib.rs new file mode 100644 index 0000000..65ebefe --- /dev/null +++ b/libaoc/src/lib.rs @@ -0,0 +1,52 @@ +pub use libaoc_macros::*; +pub use miette; +use miette::Context; +use owo_colors::OwoColorize; + +#[macro_export] +macro_rules! run_days { + ($($day:ident),+) => { + libaoc::AocRunner::new() + $(.day($day::aoc_day()))* + .run() + }; +} + +pub struct Day { + pub name: String, + pub part1: fn() -> miette::Result<()>, + pub part2: Option miette::Result<()>>, +} + +pub struct AocRunner { + days: Vec, +} + +impl AocRunner { + pub fn new() -> Self { + Self { days: vec![] } + } + + pub fn day(&mut self, day: Day) -> &mut Self { + self.days.push(day); + self + } + + pub fn run(&self) -> miette::Result<()> { + for day in &self.days { + println!("Running day {}...", day.name.blue()); + + println!("{}", "--- Part 1 ---".red()); + (day.part1)().wrap_err_with(|| format!("Running part 1 of day {}", &day.name))?; + println!(); + + if let Some(p2) = day.part2 { + println!("{}", "--- Part 2 ---".red()); + p2().wrap_err_with(|| format!("Running part 2 of day {}", &day.name))?; + println!(); + } + } + + Ok(()) + } +}