From 219da9f64cba749ef6605d8bf83074a38c287d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 10 Nov 2023 23:49:57 +0000 Subject: [PATCH 1/5] extremely naive initial derive macro --- 2022/day01/Cargo.toml | 1 + 2022/day01/src/lib.rs | 30 ++-- aoc-solution-derive/Cargo.toml | 6 +- aoc-solution-derive/src/aoc.rs | 243 +++++++++++++++++++++++++++++++++ aoc-solution-derive/src/lib.rs | 59 ++++++-- aoc-solution/Cargo.toml | 3 +- aoc-solution/src/lib.rs | 32 ++--- common/src/lib.rs | 2 +- common/src/parsing.rs | 34 +++++ 9 files changed, 358 insertions(+), 52 deletions(-) create mode 100644 aoc-solution-derive/src/aoc.rs diff --git a/2022/day01/Cargo.toml b/2022/day01/Cargo.toml index 8280567..c06b0b3 100644 --- a/2022/day01/Cargo.toml +++ b/2022/day01/Cargo.toml @@ -11,6 +11,7 @@ path = "src/lib.rs" [dependencies] common = { path = "../../common" } +aoc-solution = { path = "../../aoc-solution" } anyhow = { workspace = true } [dev-dependencies] diff --git a/2022/day01/src/lib.rs b/2022/day01/src/lib.rs index d6e089f..ed3c8b8 100644 --- a/2022/day01/src/lib.rs +++ b/2022/day01/src/lib.rs @@ -16,31 +16,21 @@ #![warn(clippy::expect_used)] use crate::types::Elf; -use common::parsing::parse_groups; -use common::AocSolution; +use common::parsing::AocInputParser; +use common::parsing::GroupsParser; +use common::Aoc; mod types; +#[derive(Aoc)] +#[aoc(input = Vec)] +#[aoc(parser = GroupsParser)] +#[aoc(part1_output = usize)] +#[aoc(part2_output = usize)] +#[aoc(part1 = part1)] +#[aoc(part2 = part2)] pub struct Day01; -impl AocSolution for Day01 { - type Input = Vec; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - parse_groups(raw.as_ref()) - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - pub fn part1(input: Vec) -> usize { input .iter() diff --git a/aoc-solution-derive/Cargo.toml b/aoc-solution-derive/Cargo.toml index ff25e27..02326c1 100644 --- a/aoc-solution-derive/Cargo.toml +++ b/aoc-solution-derive/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" proc-macro = true [dependencies] -common = { path = "../common" } -syn = "2.0" +#syn = "2.0" +proc-macro-error = "1.0.4" +proc-macro2 = "1.0.67" +syn = { version = "2.0", features = ["extra-traits"] } quote = "1.0" diff --git a/aoc-solution-derive/src/aoc.rs b/aoc-solution-derive/src/aoc.rs new file mode 100644 index 0000000..c2a53ba --- /dev/null +++ b/aoc-solution-derive/src/aoc.rs @@ -0,0 +1,243 @@ +// Copyright 2023 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::ResultExt; +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, ToTokens}; +use syn::parse::Parse; +use syn::{Attribute, Error, Token}; + +pub struct AocContainer { + attributes: AocAttr, + ident: Ident, +} + +impl AocContainer { + pub fn new(attributes: AocAttr, ident: Ident) -> Self { + AocContainer { attributes, ident } + } +} + +impl AocContainer { + fn unimplemented_inner(&self, name: &str) -> TokenStream { + // we're going to default to anyhow + if self.attributes.error_ty.is_none() { + quote! { + let name = #name + bail!("{name} hasn't been implemented") + } + } else { + quote! { + let name = #name + panic!("{name} hasn't been implemented") + } + } + } +} + +impl ToTokens for AocContainer { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let AocContainer { attributes, ident } = self; + let input_ty = if let Some(input_type) = &attributes.input_type { + input_type.to_token_stream() + } else { + quote! {()} + }; + + let part1_ty = if let Some(part1_ty) = &attributes.part1_ty { + part1_ty.to_token_stream() + } else { + quote! { String } + }; + + let part2_ty = if let Some(part2_ty) = &attributes.part2_ty { + part2_ty.to_token_stream() + } else { + quote! { String } + }; + + // TODO: that technically requires anyhow import, but for the time being that's fine + let error_ty = if let Some(error_ty) = &attributes.error_ty { + error_ty.to_token_stream() + } else { + quote! { anyhow::Error } + }; + + // TODO: we need to ensure import of `AocInputParser` + let parser_impl_inner = if let Some(parser) = &attributes.parser { + quote! { #parser::parse_input(raw) } + } else { + self.unimplemented_inner("input parser") + }; + + let part1_impl_inner = if let Some(part1) = &attributes.part1 { + if attributes.error_ty.is_none() { + quote! { Ok(#part1(input)) } + } else { + quote! { #part1(input) } + } + } else { + self.unimplemented_inner("part1") + }; + + let part2_impl_inner = if let Some(part2) = &attributes.part2 { + if attributes.error_ty.is_none() { + quote! { Ok(#part2(input)) } + } else { + quote! { #part2(input) } + } + } else { + self.unimplemented_inner("part2") + }; + + tokens.extend(quote! { + impl aoc_solution::AocSolution for #ident { + type Input = #input_ty; + type Error = #error_ty; + type Part1Output = #part1_ty; + type Part2Output = #part2_ty; + + fn parse_input(raw: &str) -> Result { + #parser_impl_inner + } + + fn part1(input: Self::Input) -> Result { + #part1_impl_inner + } + + fn part2(input: Self::Input) -> Result { + #part2_impl_inner + } + } + }) + } +} + +impl Parse for AocAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + const EXPECTED_ATTRIBUTE: &str = + "unexpected attribute, expected any of: parser, part1, part2"; + + let mut aocttr = AocAttr::default(); + + while !input.is_empty() { + let ident = input.parse::().map_err(|error| { + Error::new(error.span(), format!("{EXPECTED_ATTRIBUTE}, {error}")) + })?; + let attribute = &*ident.to_string(); + + match attribute { + "input" => { + input.parse::()?; + let ident: syn::Type = input.parse()?; + aocttr.input_type = Some(ident); + } + "parser" => { + input.parse::()?; + let ident: syn::Ident = input.parse()?; + aocttr.parser = Some(ident); + } + "part1" => { + input.parse::()?; + let ident: syn::Ident = input.parse()?; + aocttr.part1 = Some(ident); + } + "part2" => { + input.parse::()?; + let ident: syn::Ident = input.parse()?; + aocttr.part2 = Some(ident); + } + "part1_output" => { + input.parse::()?; + let ident: syn::Type = input.parse()?; + aocttr.part1_ty = Some(ident); + } + "part2_output" => { + input.parse::()?; + let ident: syn::Type = input.parse()?; + aocttr.part2_ty = Some(ident); + } + "error" => { + input.parse::()?; + let ident: syn::Type = input.parse()?; + aocttr.error_ty = Some(ident); + } + _ => { + return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE)); + } + } + + if !input.is_empty() { + input.parse::()?; + } + } + + Ok(aocttr) + } +} + +#[derive(Default, Debug)] +pub struct AocAttr { + // TODO: more concrete types? + input_type: Option, + parser: Option, + + part1_ty: Option, + part2_ty: Option, + error_ty: Option, + + part1: Option, + part2: Option, +} + +// struct AocPart { +// output_ty: syn::Type, +// exec: syn::Ident +// } + +impl AocAttr { + fn merge(mut self, other: AocAttr) -> Self { + if other.input_type.is_some() { + self.input_type = other.input_type + } + if other.parser.is_some() { + self.parser = other.parser + } + if other.part1.is_some() { + self.part1 = other.part1 + } + if other.part2.is_some() { + self.part2 = other.part2 + } + if other.part1_ty.is_some() { + self.part1_ty = other.part1_ty + } + if other.part2_ty.is_some() { + self.part2_ty = other.part2_ty + } + if other.error_ty.is_some() { + self.error_ty = other.error_ty + } + + self + } +} + +pub fn parse_aoc_attrs(attrs: &[Attribute]) -> Option { + attrs + .iter() + .filter(|attribute| attribute.path().is_ident("aoc")) + .map(|attribute| attribute.parse_args::().unwrap_or_abort()) + .reduce(|acc, item| acc.merge(item)) +} diff --git a/aoc-solution-derive/src/lib.rs b/aoc-solution-derive/src/lib.rs index 4225688..a2304f7 100644 --- a/aoc-solution-derive/src/lib.rs +++ b/aoc-solution-derive/src/lib.rs @@ -1,16 +1,55 @@ +// Copyright 2023 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::aoc::AocContainer; use proc_macro::TokenStream; -// use quote::quote; -// use syn; +use proc_macro_error::{abort, OptionExt}; +use quote::ToTokens; +use syn::DeriveInput; -// TODO: -// allow something like: -// #[derive(Aoc)] -// #[aoc(parser = xxx)] -> infer Input type from parser -// #[aoc(part1 = xxx)] -> if not defined, return Result<(), Error>; -// #[aoc(part2 = xxx)] -> ibid -// struct DayXX; +mod aoc; #[proc_macro_derive(Aoc, attributes(aoc))] pub fn derive_aoc_solution(input: TokenStream) -> TokenStream { - input + let DeriveInput { attrs, ident, .. } = syn::parse_macro_input!(input); + + let aoc_attributes = aoc::parse_aoc_attrs(&attrs).expect_or_abort( + "expected #[aoc(...)] attribute to be present when used with Aoc derive trait", + ); + + AocContainer::new(aoc_attributes, ident) + .to_token_stream() + .into() +} + +trait ResultExt { + fn unwrap_or_abort(self) -> T; + fn expect_or_abort(self, message: &str) -> T; +} + +impl ResultExt for Result { + fn unwrap_or_abort(self) -> T { + match self { + Ok(value) => value, + Err(error) => abort!(error.span(), format!("{error}")), + } + } + + fn expect_or_abort(self, message: &str) -> T { + match self { + Ok(value) => value, + Err(error) => abort!(error.span(), format!("{error}: {message}")), + } + } } diff --git a/aoc-solution/Cargo.toml b/aoc-solution/Cargo.toml index 3224f14..18e60c9 100644 --- a/aoc-solution/Cargo.toml +++ b/aoc-solution/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = { workspace = true } \ No newline at end of file +anyhow = { workspace = true } +aoc-derive = { path = "../aoc-solution-derive" } \ No newline at end of file diff --git a/aoc-solution/src/lib.rs b/aoc-solution/src/lib.rs index 67a835f..e3f1bca 100644 --- a/aoc-solution/src/lib.rs +++ b/aoc-solution/src/lib.rs @@ -12,33 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -use anyhow::bail; use std::any::type_name; use std::fmt::{Display, Formatter}; use std::path::Path; use std::time::{Duration, Instant}; +extern crate aoc_derive; + +pub use aoc_derive::Aoc; + pub trait AocSolution { type Input: Clone; + type Error: Display; type Part1Output: Display; type Part2Output: Display; - fn parse_input>(_raw: M) -> Result { - bail!("unimplemented") - } - - fn part1(_input: Self::Input) -> Result { - bail!("unimplemented") - } - - fn part2(_input: Self::Input) -> Result { - bail!("unimplemented") - } + fn parse_input(_raw: &str) -> Result; + fn part1(_input: Self::Input) -> Result; + fn part2(_input: Self::Input) -> Result; } pub trait AocSolutionSolver: AocSolution { - fn try_solve>(raw_input: M) { - match run::(raw_input) { + fn try_solve(raw_input: &str) { + match run::(raw_input) { Ok(result) => println!("{result}"), Err(err) => eprintln!("failed to solve aoc for '{}': {err}", type_name::()), } @@ -103,18 +99,18 @@ fn timed U>(f: F, input: T) -> TimedResult { } } -pub fn run_from_file(path: P) -> Result, anyhow::Error> +pub fn run_from_file(path: P) -> Result, T::Error> where P: AsRef, T: AocSolution + ?Sized, { - run(std::fs::read_to_string(path)?) + let read_input = std::fs::read_to_string(path).expect("failed to read the file input"); + run(&read_input) } -pub fn run(input: M) -> Result, anyhow::Error> +pub fn run(input: &str) -> Result, T::Error> where T: AocSolution + ?Sized, - M: AsRef, { let parsed_input = timed(T::parse_input, input).transpose()?; diff --git a/common/src/lib.rs b/common/src/lib.rs index 180e053..c1776a7 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -17,4 +17,4 @@ pub mod execution; pub mod input_read; pub mod parsing; -pub use aoc_solution::{AocSolution, AocSolutionSolver}; +pub use aoc_solution::{Aoc, AocSolution, AocSolutionSolver}; diff --git a/common/src/parsing.rs b/common/src/parsing.rs index b060ff8..cd88ace 100644 --- a/common/src/parsing.rs +++ b/common/src/parsing.rs @@ -14,9 +14,43 @@ use anyhow::{Error, Result}; use std::fmt::Debug; +use std::marker::PhantomData; use std::ops::RangeInclusive; use std::str::FromStr; +// we need separate trait, i.e. we can't just use `FromStr`, +// because of all the custom rules for say `Vec` +// TODO: or maybe we should just create wrapper containers instead? +pub trait AocInputParser { + type Output; + + fn parse_input(raw: &str) -> Result; +} + +pub trait AocParseExt { + fn parse_aoc_input(&self) -> Result; +} + +impl AocParseExt for str { + fn parse_aoc_input(&self) -> Result { + ::parse_input(self) + } +} + +pub struct GroupsParser(*const PhantomData); + +impl AocInputParser for GroupsParser +where + T: FromStr, + ::Err: Debug, +{ + type Output = Vec; + + fn parse_input(raw: &str) -> Result { + parse_groups(raw) + } +} + /// Parse input in the form of x=.. to `RangeInclusive` pub fn parse_raw_range(raw: &str) -> Result> { let mut bounds = raw.split('='); From 9ecb14f54f35a12aa10ab2548585edc94baf914e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Sat, 11 Nov 2023 20:41:03 +0000 Subject: [PATCH 2/5] combined part output type and impl under single attribute --- 2022/day01/src/lib.rs | 64 ++++----- aoc-solution-derive/src/aoc.rs | 231 ++++++++++++++++++++------------- 2 files changed, 176 insertions(+), 119 deletions(-) diff --git a/2022/day01/src/lib.rs b/2022/day01/src/lib.rs index ed3c8b8..e3e95ef 100644 --- a/2022/day01/src/lib.rs +++ b/2022/day01/src/lib.rs @@ -25,10 +25,12 @@ mod types; #[derive(Aoc)] #[aoc(input = Vec)] #[aoc(parser = GroupsParser)] -#[aoc(part1_output = usize)] -#[aoc(part2_output = usize)] -#[aoc(part1 = part1)] -#[aoc(part2 = part2)] +// #[aoc(part1_output = usize)] +// #[aoc(part2_output = usize)] +// #[aoc(part1 = part1)] +// #[aoc(part2 = part2)] +#[aoc(part1(output = usize, runner = part1))] +// #[aoc(part2(output = usize, runner = part2))] pub struct Day01; pub fn part1(input: Vec) -> usize { @@ -62,30 +64,30 @@ pub fn part2(input: Vec) -> usize { max + max2 + max3 } -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod tests { - use super::*; - - fn sample_input() -> Vec { - vec![ - Elf::new(vec![1000, 2000, 3000]), - Elf::new(vec![4000]), - Elf::new(vec![5000, 6000]), - Elf::new(vec![7000, 8000, 9000]), - Elf::new(vec![10000]), - ] - } - - #[test] - fn part1_sample_input() { - let expected = 24000; - assert_eq!(expected, part1(sample_input())) - } - - #[test] - fn part2_sample_input() { - let expected = 45000; - assert_eq!(expected, part2(sample_input())) - } -} +// #[cfg(test)] +// #[allow(clippy::unwrap_used)] +// mod tests { +// use super::*; +// +// fn sample_input() -> Vec { +// vec![ +// Elf::new(vec![1000, 2000, 3000]), +// Elf::new(vec![4000]), +// Elf::new(vec![5000, 6000]), +// Elf::new(vec![7000, 8000, 9000]), +// Elf::new(vec![10000]), +// ] +// } +// +// #[test] +// fn part1_sample_input() { +// let expected = 24000; +// assert_eq!(expected, part1(sample_input())) +// } +// +// #[test] +// fn part2_sample_input() { +// let expected = 45000; +// assert_eq!(expected, part2(sample_input())) +// } +// } diff --git a/aoc-solution-derive/src/aoc.rs b/aoc-solution-derive/src/aoc.rs index c2a53ba..ee900e9 100644 --- a/aoc-solution-derive/src/aoc.rs +++ b/aoc-solution-derive/src/aoc.rs @@ -13,10 +13,19 @@ // limitations under the License. use crate::ResultExt; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; -use syn::parse::Parse; -use syn::{Attribute, Error, Token}; +use syn::parse::{Parse, ParseStream}; +use syn::{parenthesized, Attribute, Error, Token}; + +fn unimplemented_inner(custom_err: bool, name: &str) -> TokenStream { + // we're going to default to anyhow + if !custom_err { + quote! { anyhow::bail!("{0} hasn't been implemented", #name) } + } else { + quote! { panic!("{0} hasn't been implemented", #name) } + } +} pub struct AocContainer { attributes: AocAttr, @@ -30,94 +39,114 @@ impl AocContainer { } impl AocContainer { - fn unimplemented_inner(&self, name: &str) -> TokenStream { - // we're going to default to anyhow + fn wrap_runner(&self, runner: &Ident) -> TokenStream { if self.attributes.error_ty.is_none() { - quote! { - let name = #name - bail!("{name} hasn't been implemented") - } + quote! { Ok(#runner(input)) } } else { - quote! { - let name = #name - panic!("{name} hasn't been implemented") + quote! { #runner(input) } + } + } + + fn part1_impl(&self) -> TokenStream { + if let Some(p1) = &self.attributes.part1 { + if let Some(runner) = &p1.runner { + return self.wrap_runner(runner); } } + + self.unimplemented_inner("part1") } -} -impl ToTokens for AocContainer { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let AocContainer { attributes, ident } = self; - let input_ty = if let Some(input_type) = &attributes.input_type { - input_type.to_token_stream() - } else { - quote! {()} - }; + fn part1_output(&self) -> TokenStream { + if let Some(p1) = &self.attributes.part1 { + if let Some(ty) = &p1.output_ty { + return quote! { #ty }; + } + } - let part1_ty = if let Some(part1_ty) = &attributes.part1_ty { - part1_ty.to_token_stream() - } else { - quote! { String } - }; + quote! { String } + } - let part2_ty = if let Some(part2_ty) = &attributes.part2_ty { - part2_ty.to_token_stream() + fn part2_impl(&self) -> TokenStream { + if let Some(p2) = &self.attributes.part2 { + if let Some(runner) = &p2.runner { + return self.wrap_runner(runner); + } + } + + self.unimplemented_inner("part2") + } + + fn part2_output(&self) -> TokenStream { + if let Some(p2) = &self.attributes.part2 { + if let Some(ty) = &p2.output_ty { + return quote! { #ty }; + } + } + + quote! { String } + } + + fn input_ty(&self) -> TokenStream { + if let Some(input_type) = &self.attributes.input_type { + input_type.to_token_stream() } else { - quote! { String } - }; + quote! {()} + } + } - // TODO: that technically requires anyhow import, but for the time being that's fine - let error_ty = if let Some(error_ty) = &attributes.error_ty { + // TODO: that technically requires anyhow import, but for the time being that's fine + fn error_ty(&self) -> TokenStream { + if let Some(error_ty) = &self.attributes.error_ty { error_ty.to_token_stream() } else { quote! { anyhow::Error } - }; + } + } - // TODO: we need to ensure import of `AocInputParser` - let parser_impl_inner = if let Some(parser) = &attributes.parser { + // TODO: we need to ensure import of `AocInputParser` + fn parser_impl(&self) -> TokenStream { + if let Some(parser) = &self.attributes.parser { quote! { #parser::parse_input(raw) } } else { self.unimplemented_inner("input parser") - }; + } + } - let part1_impl_inner = if let Some(part1) = &attributes.part1 { - if attributes.error_ty.is_none() { - quote! { Ok(#part1(input)) } - } else { - quote! { #part1(input) } - } - } else { - self.unimplemented_inner("part1") - }; - - let part2_impl_inner = if let Some(part2) = &attributes.part2 { - if attributes.error_ty.is_none() { - quote! { Ok(#part2(input)) } - } else { - quote! { #part2(input) } - } - } else { - self.unimplemented_inner("part2") - }; + fn unimplemented_inner(&self, name: &str) -> TokenStream { + unimplemented_inner(self.attributes.error_ty.is_some(), name) + } +} + +impl ToTokens for AocContainer { + fn to_tokens(&self, tokens: &mut TokenStream) { + let ident = &self.ident; + + let input_ty = self.input_ty(); + let error_ty = self.error_ty(); + let parser_impl = self.parser_impl(); + let p1_ty = self.part1_output(); + let p2_ty = self.part2_output(); + let p1_impl = self.part1_impl(); + let p2_impl = self.part2_impl(); tokens.extend(quote! { impl aoc_solution::AocSolution for #ident { type Input = #input_ty; type Error = #error_ty; - type Part1Output = #part1_ty; - type Part2Output = #part2_ty; + type Part1Output = #p1_ty; + type Part2Output = #p2_ty; fn parse_input(raw: &str) -> Result { - #parser_impl_inner + #parser_impl } fn part1(input: Self::Input) -> Result { - #part1_impl_inner + #p1_impl } fn part2(input: Self::Input) -> Result { - #part2_impl_inner + #p2_impl } } }) @@ -149,25 +178,14 @@ impl Parse for AocAttr { aocttr.parser = Some(ident); } "part1" => { - input.parse::()?; - let ident: syn::Ident = input.parse()?; + let ident: AocPart = input.parse()?; aocttr.part1 = Some(ident); } "part2" => { input.parse::()?; - let ident: syn::Ident = input.parse()?; + let ident: AocPart = input.parse()?; aocttr.part2 = Some(ident); } - "part1_output" => { - input.parse::()?; - let ident: syn::Type = input.parse()?; - aocttr.part1_ty = Some(ident); - } - "part2_output" => { - input.parse::()?; - let ident: syn::Type = input.parse()?; - aocttr.part2_ty = Some(ident); - } "error" => { input.parse::()?; let ident: syn::Type = input.parse()?; @@ -192,19 +210,62 @@ pub struct AocAttr { // TODO: more concrete types? input_type: Option, parser: Option, - - part1_ty: Option, - part2_ty: Option, error_ty: Option, - part1: Option, - part2: Option, + part1: Option, + part2: Option, } -// struct AocPart { -// output_ty: syn::Type, -// exec: syn::Ident -// } +#[derive(Debug, Clone, Default)] +struct AocPart { + output_ty: Option, + runner: Option, +} + +impl Parse for AocPart { + fn parse(input: ParseStream) -> syn::Result { + let content; + parenthesized!(content in input); + const EXPECTED_ATTRIBUTE: &str = "unexpected attribute. expected one of: output, runner"; + + let mut aoc_part = AocPart::default(); + + while !content.is_empty() { + let ident = content.parse::().map_err(|error| { + Error::new(error.span(), format!("{EXPECTED_ATTRIBUTE}, {error}")) + })?; + let attribute = &*ident.to_string(); + + // every single attribute is in the form of `name = value`, + // thus we should be able to parse out the Eq token + content.parse::()?; + + match attribute { + "output" => { + let output_ty2: syn::Type = content.parse()?; + aoc_part.output_ty = Some(output_ty2); + } + "runner" => { + let runner2: Ident = content.parse()?; + aoc_part.runner = Some(runner2); + } + _ => return Err(syn::Error::new(ident.span(), EXPECTED_ATTRIBUTE)), + } + if !content.is_empty() { + content.parse::()?; + } + } + + if aoc_part.runner.is_some() && aoc_part.output_ty.is_none() { + return Err(Error::new( + Span::call_site(), + "could not use part runner without specifying return type", + )); + } + + Ok(aoc_part) + } +} impl AocAttr { fn merge(mut self, other: AocAttr) -> Self { @@ -220,12 +281,6 @@ impl AocAttr { if other.part2.is_some() { self.part2 = other.part2 } - if other.part1_ty.is_some() { - self.part1_ty = other.part1_ty - } - if other.part2_ty.is_some() { - self.part2_ty = other.part2_ty - } if other.error_ty.is_some() { self.error_ty = other.error_ty } From afa231ab41915be1c4c8905dcd25af4a846e6fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Sat, 11 Nov 2023 20:57:04 +0000 Subject: [PATCH 3/5] macro cleanup --- 2022/day01/src/lib.rs | 7 +------ aoc-solution-derive/src/aoc.rs | 38 ++++++++++++---------------------- aoc-solution/src/lib.rs | 2 ++ aoc-solution/src/parser.rs | 34 ++++++++++++++++++++++++++++++ common/src/parsing.rs | 20 +----------------- 5 files changed, 51 insertions(+), 50 deletions(-) create mode 100644 aoc-solution/src/parser.rs diff --git a/2022/day01/src/lib.rs b/2022/day01/src/lib.rs index e3e95ef..b878a59 100644 --- a/2022/day01/src/lib.rs +++ b/2022/day01/src/lib.rs @@ -16,7 +16,6 @@ #![warn(clippy::expect_used)] use crate::types::Elf; -use common::parsing::AocInputParser; use common::parsing::GroupsParser; use common::Aoc; @@ -25,12 +24,8 @@ mod types; #[derive(Aoc)] #[aoc(input = Vec)] #[aoc(parser = GroupsParser)] -// #[aoc(part1_output = usize)] -// #[aoc(part2_output = usize)] -// #[aoc(part1 = part1)] -// #[aoc(part2 = part2)] #[aoc(part1(output = usize, runner = part1))] -// #[aoc(part2(output = usize, runner = part2))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day01; pub fn part1(input: Vec) -> usize { diff --git a/aoc-solution-derive/src/aoc.rs b/aoc-solution-derive/src/aoc.rs index ee900e9..692f9a5 100644 --- a/aoc-solution-derive/src/aoc.rs +++ b/aoc-solution-derive/src/aoc.rs @@ -107,7 +107,11 @@ impl AocContainer { // TODO: we need to ensure import of `AocInputParser` fn parser_impl(&self) -> TokenStream { if let Some(parser) = &self.attributes.parser { - quote! { #parser::parse_input(raw) } + quote! { + #[allow(unused_imports)] + use ::aoc_solution::parser::AocInputParser; + #parser::parse_input(raw) + } } else { self.unimplemented_inner("input parser") } @@ -131,7 +135,7 @@ impl ToTokens for AocContainer { let p2_impl = self.part2_impl(); tokens.extend(quote! { - impl aoc_solution::AocSolution for #ident { + impl ::aoc_solution::AocSolution for #ident { type Input = #input_ty; type Error = #error_ty; type Part1Output = #p1_ty; @@ -169,27 +173,17 @@ impl Parse for AocAttr { match attribute { "input" => { input.parse::()?; - let ident: syn::Type = input.parse()?; - aocttr.input_type = Some(ident); + aocttr.input_type = Some(input.parse()?); } "parser" => { input.parse::()?; - let ident: syn::Ident = input.parse()?; - aocttr.parser = Some(ident); - } - "part1" => { - let ident: AocPart = input.parse()?; - aocttr.part1 = Some(ident); - } - "part2" => { - input.parse::()?; - let ident: AocPart = input.parse()?; - aocttr.part2 = Some(ident); + aocttr.parser = Some(input.parse()?); } + "part1" => aocttr.part1 = Some(input.parse()?), + "part2" => aocttr.part2 = Some(input.parse()?), "error" => { input.parse::()?; - let ident: syn::Type = input.parse()?; - aocttr.error_ty = Some(ident); + aocttr.error_ty = Some(input.parse()?); } _ => { return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE)); @@ -241,14 +235,8 @@ impl Parse for AocPart { content.parse::()?; match attribute { - "output" => { - let output_ty2: syn::Type = content.parse()?; - aoc_part.output_ty = Some(output_ty2); - } - "runner" => { - let runner2: Ident = content.parse()?; - aoc_part.runner = Some(runner2); - } + "output" => aoc_part.output_ty = Some(content.parse()?), + "runner" => aoc_part.runner = Some(content.parse()?), _ => return Err(syn::Error::new(ident.span(), EXPECTED_ATTRIBUTE)), } if !content.is_empty() { diff --git a/aoc-solution/src/lib.rs b/aoc-solution/src/lib.rs index e3f1bca..cae74ae 100644 --- a/aoc-solution/src/lib.rs +++ b/aoc-solution/src/lib.rs @@ -17,6 +17,8 @@ use std::fmt::{Display, Formatter}; use std::path::Path; use std::time::{Duration, Instant}; +pub mod parser; + extern crate aoc_derive; pub use aoc_derive::Aoc; diff --git a/aoc-solution/src/parser.rs b/aoc-solution/src/parser.rs new file mode 100644 index 0000000..2060895 --- /dev/null +++ b/aoc-solution/src/parser.rs @@ -0,0 +1,34 @@ +// Copyright 2023 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::Result; + +// we need separate trait, i.e. we can't just use `FromStr`, +// because of all the custom rules for say `Vec` +// TODO: or maybe we should just create wrapper containers instead? +pub trait AocInputParser { + type Output; + + fn parse_input(raw: &str) -> Result; +} + +pub trait AocParseExt { + fn parse_aoc_input(&self) -> Result; +} + +impl AocParseExt for str { + fn parse_aoc_input(&self) -> Result { + ::parse_input(self) + } +} diff --git a/common/src/parsing.rs b/common/src/parsing.rs index cd88ace..cf2764e 100644 --- a/common/src/parsing.rs +++ b/common/src/parsing.rs @@ -13,30 +13,12 @@ // limitations under the License. use anyhow::{Error, Result}; +use aoc_solution::parser::AocInputParser; use std::fmt::Debug; use std::marker::PhantomData; use std::ops::RangeInclusive; use std::str::FromStr; -// we need separate trait, i.e. we can't just use `FromStr`, -// because of all the custom rules for say `Vec` -// TODO: or maybe we should just create wrapper containers instead? -pub trait AocInputParser { - type Output; - - fn parse_input(raw: &str) -> Result; -} - -pub trait AocParseExt { - fn parse_aoc_input(&self) -> Result; -} - -impl AocParseExt for str { - fn parse_aoc_input(&self) -> Result { - ::parse_input(self) - } -} - pub struct GroupsParser(*const PhantomData); impl AocInputParser for GroupsParser From e2ef84f994531f15234e3c39e03353d2bb62082f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Sat, 11 Nov 2023 21:13:06 +0000 Subject: [PATCH 4/5] common parsers impls --- 2022/day01/src/lib.rs | 56 ++-- aoc-solution/src/lib.rs | 22 +- common/src/{parsing.rs => parsing/impls.rs} | 288 +++++++++----------- common/src/parsing/mod.rs | 131 +++++++++ 4 files changed, 311 insertions(+), 186 deletions(-) rename common/src/{parsing.rs => parsing/impls.rs} (86%) create mode 100644 common/src/parsing/mod.rs diff --git a/2022/day01/src/lib.rs b/2022/day01/src/lib.rs index b878a59..86115c3 100644 --- a/2022/day01/src/lib.rs +++ b/2022/day01/src/lib.rs @@ -25,7 +25,7 @@ mod types; #[aoc(input = Vec)] #[aoc(parser = GroupsParser)] #[aoc(part1(output = usize, runner = part1))] -#[aoc(part2(output = usize, runner = part2))] +// #[aoc(part2(output = usize, runner = part2))] pub struct Day01; pub fn part1(input: Vec) -> usize { @@ -59,30 +59,30 @@ pub fn part2(input: Vec) -> usize { max + max2 + max3 } -// #[cfg(test)] -// #[allow(clippy::unwrap_used)] -// mod tests { -// use super::*; -// -// fn sample_input() -> Vec { -// vec![ -// Elf::new(vec![1000, 2000, 3000]), -// Elf::new(vec![4000]), -// Elf::new(vec![5000, 6000]), -// Elf::new(vec![7000, 8000, 9000]), -// Elf::new(vec![10000]), -// ] -// } -// -// #[test] -// fn part1_sample_input() { -// let expected = 24000; -// assert_eq!(expected, part1(sample_input())) -// } -// -// #[test] -// fn part2_sample_input() { -// let expected = 45000; -// assert_eq!(expected, part2(sample_input())) -// } -// } +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::*; + + fn sample_input() -> Vec { + vec![ + Elf::new(vec![1000, 2000, 3000]), + Elf::new(vec![4000]), + Elf::new(vec![5000, 6000]), + Elf::new(vec![7000, 8000, 9000]), + Elf::new(vec![10000]), + ] + } + + #[test] + fn part1_sample_input() { + let expected = 24000; + assert_eq!(expected, part1(sample_input())) + } + + #[test] + fn part2_sample_input() { + let expected = 45000; + assert_eq!(expected, part2(sample_input())) + } +} diff --git a/aoc-solution/src/lib.rs b/aoc-solution/src/lib.rs index cae74ae..7a93f0d 100644 --- a/aoc-solution/src/lib.rs +++ b/aoc-solution/src/lib.rs @@ -57,8 +57,8 @@ impl AocSolutionSolver for T where T: AocSolution {} pub struct DayResult { parsing: Duration, - part1: TimedResult, - part2: TimedResult, + part1: TimedResult>, + part2: TimedResult>, } impl Display for DayResult { @@ -69,8 +69,18 @@ impl Display for DayResult { writeln!(f, "PART 2:\t\t{:?}", self.part2.taken)?; writeln!(f)?; writeln!(f, "# RESULTS #")?; - writeln!(f, "PART 1:\t\t{}", self.part1.value)?; - writeln!(f, "PART 2:\t\t{}", self.part2.value) + let display_p1 = match &self.part1.value { + Ok(res) => res.to_string(), + Err(err) => format!("failed to solve: {err}"), + }; + + let display_p2 = match &self.part2.value { + Ok(res) => res.to_string(), + Err(err) => format!("failed to solve: {err}"), + }; + + writeln!(f, "PART 1:\t\t{display_p1}")?; + writeln!(f, "PART 2:\t\t{display_p2}") } } @@ -116,8 +126,8 @@ where { let parsed_input = timed(T::parse_input, input).transpose()?; - let part1 = timed(T::part1, parsed_input.value.clone()).transpose()?; - let part2 = timed(T::part2, parsed_input.value).transpose()?; + let part1 = timed(T::part1, parsed_input.value.clone()); + let part2 = timed(T::part2, parsed_input.value); Ok(DayResult { parsing: parsed_input.taken, diff --git a/common/src/parsing.rs b/common/src/parsing/impls.rs similarity index 86% rename from common/src/parsing.rs rename to common/src/parsing/impls.rs index cf2764e..b899d18 100644 --- a/common/src/parsing.rs +++ b/common/src/parsing/impls.rs @@ -1,152 +1,136 @@ -// Copyright 2022-2023 Jedrzej Stuczynski -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use anyhow::{Error, Result}; -use aoc_solution::parser::AocInputParser; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::ops::RangeInclusive; -use std::str::FromStr; - -pub struct GroupsParser(*const PhantomData); - -impl AocInputParser for GroupsParser -where - T: FromStr, - ::Err: Debug, -{ - type Output = Vec; - - fn parse_input(raw: &str) -> Result { - parse_groups(raw) - } -} - -/// Parse input in the form of x=.. to `RangeInclusive` -pub fn parse_raw_range(raw: &str) -> Result> { - let mut bounds = raw.split('='); - let _axis = bounds - .next() - .ok_or_else(|| Error::msg("incomplete range"))?; - let mut values = bounds - .next() - .ok_or_else(|| Error::msg("incomplete range"))? - .split(".."); - - let lower_bound = values - .next() - .ok_or_else(|| Error::msg("incomplete range"))? - .parse()?; - let upper_bound = values - .next() - .ok_or_else(|| Error::msg("incomplete range"))? - .parse()?; - - Ok(RangeInclusive::new(lower_bound, upper_bound)) -} - -/// Parses input in the form of: -/// -/// value1 -/// value2 -/// ... -/// -/// to Vec -pub fn parse_input_lines(raw: &str) -> Result> -where - T: FromStr, - ::Err: Debug, -{ - raw.lines() - .map(|line| line.parse::()) - .collect::, _>>() - .map_err(|err| { - Error::msg(format!( - "input could not be parsed into desired type - {err:?}" - )) - }) -} - -/// Parses input in the form of: -/// -/// value1,value2,... -/// -/// to Vec -pub fn parse_comma_separated_values(raw: &str) -> Result> -where - T: FromStr, - ::Err: Debug, -{ - raw.split(',') - .map(|split| split.parse()) - .collect::, _>>() - .map_err(|err| { - Error::msg(format!( - "input could not be parsed into desired type - {err:?}" - )) - }) -} - -/// Splits input in the form of: -/// -/// group1_value1 -/// group1_value2 -/// ... -/// -/// group2_value1 -/// group2_value2 -/// ... -/// -/// to Vec -pub fn split_to_string_groups(raw: &str) -> Vec { - let split = raw - .replace("\r\n", "\n") // Windows fix - .split("\n\n") - .map(|split| split.to_owned()) - .collect(); - - split -} - -/// Parses input in the form of: -/// -/// group1_value1 -/// group1_value2 -/// ... -/// -/// group2_value1 -/// group2_value2 -/// ... -/// -/// to Vec -pub fn parse_groups(raw: &str) -> Result> -where - T: FromStr, - ::Err: Debug, -{ - split_to_string_groups(raw) - .into_iter() - .map(|line| line.parse::()) - .collect::, _>>() - .map_err(|err| { - Error::msg(format!( - "input could not be parsed into desired type - {err:?}" - )) - }) -} - -/// Transforms the raw string input into a Vec -pub fn as_char_vec(raw: &str) -> Result> { - Ok(raw.chars().collect()) -} +// Copyright 2023 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::{Error, Result}; +use std::fmt::Debug; +use std::ops::RangeInclusive; +use std::str::FromStr; + +/// Parse input in the form of x=.. to `RangeInclusive` +pub fn parse_raw_range(raw: &str) -> Result> { + let mut bounds = raw.split('='); + let _axis = bounds + .next() + .ok_or_else(|| Error::msg("incomplete range"))?; + let mut values = bounds + .next() + .ok_or_else(|| Error::msg("incomplete range"))? + .split(".."); + + let lower_bound = values + .next() + .ok_or_else(|| Error::msg("incomplete range"))? + .parse()?; + let upper_bound = values + .next() + .ok_or_else(|| Error::msg("incomplete range"))? + .parse()?; + + Ok(RangeInclusive::new(lower_bound, upper_bound)) +} + +/// Parses input in the form of: +/// +/// value1 +/// value2 +/// ... +/// +/// to Vec +pub fn parse_input_lines(raw: &str) -> Result> +where + T: FromStr, + ::Err: Debug, +{ + raw.lines() + .map(|line| line.parse::()) + .collect::, _>>() + .map_err(|err| { + Error::msg(format!( + "input could not be parsed into desired type - {err:?}" + )) + }) +} + +/// Parses input in the form of: +/// +/// value1,value2,... +/// +/// to Vec +pub fn parse_comma_separated_values(raw: &str) -> Result> +where + T: FromStr, + ::Err: Debug, +{ + raw.split(',') + .map(|split| split.parse()) + .collect::, _>>() + .map_err(|err| { + Error::msg(format!( + "input could not be parsed into desired type - {err:?}" + )) + }) +} + +/// Splits input in the form of: +/// +/// group1_value1 +/// group1_value2 +/// ... +/// +/// group2_value1 +/// group2_value2 +/// ... +/// +/// to Vec +pub fn split_to_string_groups(raw: &str) -> Vec { + let split = raw + .replace("\r\n", "\n") // Windows fix + .split("\n\n") + .map(|split| split.to_owned()) + .collect(); + + split +} + +/// Parses input in the form of: +/// +/// group1_value1 +/// group1_value2 +/// ... +/// +/// group2_value1 +/// group2_value2 +/// ... +/// +/// to Vec +pub fn parse_groups(raw: &str) -> Result> +where + T: FromStr, + ::Err: Debug, +{ + split_to_string_groups(raw) + .into_iter() + .map(|line| line.parse::()) + .collect::, _>>() + .map_err(|err| { + Error::msg(format!( + "input could not be parsed into desired type - {err:?}" + )) + }) +} + +/// Transforms the raw string input into a Vec +pub fn as_char_vec(raw: &str) -> Result> { + Ok(raw.chars().collect()) +} diff --git a/common/src/parsing/mod.rs b/common/src/parsing/mod.rs new file mode 100644 index 0000000..8b2b13c --- /dev/null +++ b/common/src/parsing/mod.rs @@ -0,0 +1,131 @@ +// Copyright 2022-2023 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::Result; +use aoc_solution::parser::AocInputParser; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::ops::RangeInclusive; +use std::str::FromStr; + +pub use impls::*; +pub mod impls; + +/// Parse input in the form of x=.. to `RangeInclusive` +pub struct RangeParser; + +/// Parses input in the form of: +/// +/// value1 +/// value2 +/// ... +/// +/// to Vec +pub struct LineParser(*const PhantomData); + +/// Parses input in the form of: +/// +/// value1,value2,... +/// +/// to Vec +pub struct CommaSeparatedParser(*const PhantomData); + +/// Splits input in the form of: +/// +/// group1_value1 +/// group1_value2 +/// ... +/// +/// group2_value1 +/// group2_value2 +/// ... +/// +/// to Vec +pub struct StringGroupsParser; + +/// Parses input in the form of: +/// +/// group1_value1 +/// group1_value2 +/// ... +/// +/// group2_value1 +/// group2_value2 +/// ... +/// +/// to Vec +pub struct GroupsParser(*const PhantomData); + +/// Transforms the raw string input into a Vec +pub struct CharVecParser; + +impl AocInputParser for RangeParser { + type Output = RangeInclusive; + + fn parse_input(raw: &str) -> Result { + parse_raw_range(raw) + } +} + +impl AocInputParser for LineParser +where + T: FromStr, + ::Err: Debug, +{ + type Output = Vec; + + fn parse_input(raw: &str) -> Result { + parse_input_lines(raw) + } +} + +impl AocInputParser for CommaSeparatedParser +where + T: FromStr, + ::Err: Debug, +{ + type Output = Vec; + + fn parse_input(raw: &str) -> Result { + parse_comma_separated_values(raw) + } +} + +impl AocInputParser for StringGroupsParser { + type Output = Vec; + + fn parse_input(raw: &str) -> Result { + Ok(split_to_string_groups(raw)) + } +} + +impl AocInputParser for GroupsParser +where + T: FromStr, + ::Err: Debug, +{ + type Output = Vec; + + fn parse_input(raw: &str) -> Result { + parse_groups(raw) + } +} + +impl AocInputParser for CharVecParser { + type Output = Vec; + + fn parse_input(raw: &str) -> Result { + as_char_vec(raw) + } +} From eb1ba2c63555f15636c28ae309ca57dd3de9d680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Sat, 11 Nov 2023 21:40:41 +0000 Subject: [PATCH 5/5] using derive macro for existing solution --- 2022/day01/Cargo.toml | 4 ++-- 2022/day01/src/lib.rs | 4 ++-- 2022/day01/src/main.rs | 2 +- 2022/day02/Cargo.toml | 3 ++- 2022/day02/src/lib.rs | 33 +++++++-------------------------- 2022/day02/src/main.rs | 2 +- 2022/day03/Cargo.toml | 3 ++- 2022/day03/src/lib.rs | 27 +++++++-------------------- 2022/day03/src/main.rs | 2 +- 2022/day04/Cargo.toml | 3 ++- 2022/day04/src/lib.rs | 26 +++++++------------------- 2022/day04/src/main.rs | 2 +- 2022/day05/Cargo.toml | 3 ++- 2022/day05/src/lib.rs | 26 +++++++------------------- 2022/day05/src/main.rs | 2 +- 2022/day06/Cargo.toml | 3 ++- 2022/day06/src/lib.rs | 27 +++++++-------------------- 2022/day06/src/main.rs | 2 +- 2022/day07/Cargo.toml | 3 ++- 2022/day07/src/lib.rs | 26 +++++++------------------- 2022/day07/src/main.rs | 2 +- 2022/day08/Cargo.toml | 3 ++- 2022/day08/src/lib.rs | 33 +++++++-------------------------- 2022/day08/src/main.rs | 2 +- 2022/day11/Cargo.toml | 3 ++- 2022/day11/src/lib.rs | 33 +++++++-------------------------- 2022/day11/src/main.rs | 2 +- common/src/lib.rs | 2 -- common/src/parsing/mod.rs | 15 +++++++++++++++ solution-runner/Cargo.toml | 2 ++ solution-runner/src/main.rs | 2 +- 31 files changed, 103 insertions(+), 199 deletions(-) diff --git a/2022/day01/Cargo.toml b/2022/day01/Cargo.toml index c06b0b3..d782f5b 100644 --- a/2022/day01/Cargo.toml +++ b/2022/day01/Cargo.toml @@ -10,12 +10,12 @@ name = "day01_2022" path = "src/lib.rs" [dependencies] -common = { path = "../../common" } aoc-solution = { path = "../../aoc-solution" } +common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day01/src/lib.rs b/2022/day01/src/lib.rs index 86115c3..013c22f 100644 --- a/2022/day01/src/lib.rs +++ b/2022/day01/src/lib.rs @@ -16,8 +16,8 @@ #![warn(clippy::expect_used)] use crate::types::Elf; +use aoc_solution::Aoc; use common::parsing::GroupsParser; -use common::Aoc; mod types; @@ -25,7 +25,7 @@ mod types; #[aoc(input = Vec)] #[aoc(parser = GroupsParser)] #[aoc(part1(output = usize, runner = part1))] -// #[aoc(part2(output = usize, runner = part2))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day01; pub fn part1(input: Vec) -> usize { diff --git a/2022/day01/src/main.rs b/2022/day01/src/main.rs index 01460e6..71d4701 100644 --- a/2022/day01/src/main.rs +++ b/2022/day01/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day01_2022::Day01; #[cfg(not(tarpaulin))] diff --git a/2022/day02/Cargo.toml b/2022/day02/Cargo.toml index de34588..d1af197 100644 --- a/2022/day02/Cargo.toml +++ b/2022/day02/Cargo.toml @@ -10,11 +10,12 @@ name = "day02_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day02/src/lib.rs b/2022/day02/src/lib.rs index 78d9749..a4939a9 100644 --- a/2022/day02/src/lib.rs +++ b/2022/day02/src/lib.rs @@ -16,33 +16,18 @@ #![warn(clippy::expect_used)] use crate::types::RPSGame; -use common::execution::execute; -use common::parsing::parse_input_lines; -use common::AocSolution; -use std::path::Path; +use aoc_solution::Aoc; +use common::parsing::LineParser; mod types; +#[derive(Aoc)] +#[aoc(input = Vec)] +#[aoc(parser = LineParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day02; -impl AocSolution for Day02 { - type Input = Vec; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - parse_input_lines(raw.as_ref()) - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - pub fn part1(input: Vec) -> usize { input.into_iter().map(|p| p.play_as_shape()).sum() } @@ -51,10 +36,6 @@ pub fn part2(input: Vec) -> usize { input.into_iter().map(|p| p.play_as_result()).sum() } -pub fn solve>(input_file: P) { - execute(input_file, parse_input_lines, part1, part2) -} - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { diff --git a/2022/day02/src/main.rs b/2022/day02/src/main.rs index 9431a72..d3f7f98 100644 --- a/2022/day02/src/main.rs +++ b/2022/day02/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day02_2022::Day02; #[cfg(not(tarpaulin))] diff --git a/2022/day03/Cargo.toml b/2022/day03/Cargo.toml index 436cbb3..c9cc74f 100644 --- a/2022/day03/Cargo.toml +++ b/2022/day03/Cargo.toml @@ -10,11 +10,12 @@ name = "day03_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day03/src/lib.rs b/2022/day03/src/lib.rs index 2b65d80..6879533 100644 --- a/2022/day03/src/lib.rs +++ b/2022/day03/src/lib.rs @@ -16,31 +16,18 @@ #![warn(clippy::expect_used)] use crate::types::Rucksack; -use common::parsing::parse_input_lines; -use common::AocSolution; +use aoc_solution::Aoc; +use common::parsing::LineParser; mod types; +#[derive(Aoc)] +#[aoc(input = Vec)] +#[aoc(parser = LineParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day03; -impl AocSolution for Day03 { - type Input = Vec; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - parse_input_lines(raw.as_ref()) - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - pub fn part1(input: Vec) -> usize { input .into_iter() diff --git a/2022/day03/src/main.rs b/2022/day03/src/main.rs index c1afaa8..8d8ba69 100644 --- a/2022/day03/src/main.rs +++ b/2022/day03/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day03_2022::Day03; #[cfg(not(tarpaulin))] diff --git a/2022/day04/Cargo.toml b/2022/day04/Cargo.toml index 587f567..ed059cb 100644 --- a/2022/day04/Cargo.toml +++ b/2022/day04/Cargo.toml @@ -10,11 +10,12 @@ name = "day04_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day04/src/lib.rs b/2022/day04/src/lib.rs index 89689cc..4789a88 100644 --- a/2022/day04/src/lib.rs +++ b/2022/day04/src/lib.rs @@ -16,30 +16,18 @@ #![warn(clippy::expect_used)] use crate::types::AssignmentPair; -use common::parsing::parse_input_lines; -use common::AocSolution; +use aoc_solution::Aoc; +use common::parsing::LineParser; mod types; +#[derive(Aoc)] +#[aoc(input = Vec)] +#[aoc(parser = LineParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day04; -impl AocSolution for Day04 { - type Input = Vec; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - parse_input_lines(raw.as_ref()) - } - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - pub fn part1(input: Vec) -> usize { input .into_iter() diff --git a/2022/day04/src/main.rs b/2022/day04/src/main.rs index 42c575d..5534a22 100644 --- a/2022/day04/src/main.rs +++ b/2022/day04/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day04_2022::Day04; #[cfg(not(tarpaulin))] diff --git a/2022/day05/Cargo.toml b/2022/day05/Cargo.toml index b8202f9..65e9b79 100644 --- a/2022/day05/Cargo.toml +++ b/2022/day05/Cargo.toml @@ -10,11 +10,12 @@ name = "day05_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day05/src/lib.rs b/2022/day05/src/lib.rs index 7fd690c..ac50274 100644 --- a/2022/day05/src/lib.rs +++ b/2022/day05/src/lib.rs @@ -16,30 +16,18 @@ #![warn(clippy::expect_used)] use crate::types::Supplies; -use common::AocSolution; +use aoc_solution::Aoc; +use common::parsing::FromStrParser; mod types; +#[derive(Aoc)] +#[aoc(input = Supplies)] +#[aoc(parser = FromStrParser)] +#[aoc(part1(output = String, runner = part1))] +#[aoc(part2(output = String, runner = part2))] pub struct Day05; -impl AocSolution for Day05 { - type Input = Supplies; - type Part1Output = String; - type Part2Output = String; - - fn parse_input>(raw: M) -> Result { - raw.as_ref().parse() - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - pub fn part1(mut input: Supplies) -> String { input.complete_rearrangement_procedure(false); input.top_message() diff --git a/2022/day05/src/main.rs b/2022/day05/src/main.rs index 5eb27c5..20329a6 100644 --- a/2022/day05/src/main.rs +++ b/2022/day05/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day05_2022::Day05; #[cfg(not(tarpaulin))] diff --git a/2022/day06/Cargo.toml b/2022/day06/Cargo.toml index ca45021..55a9f3a 100644 --- a/2022/day06/Cargo.toml +++ b/2022/day06/Cargo.toml @@ -10,11 +10,12 @@ name = "day06_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day06/src/lib.rs b/2022/day06/src/lib.rs index bdbf2b1..acc882a 100644 --- a/2022/day06/src/lib.rs +++ b/2022/day06/src/lib.rs @@ -15,29 +15,16 @@ #![warn(clippy::unwrap_used)] #![warn(clippy::expect_used)] -use common::parsing::as_char_vec; -use common::AocSolution; +use aoc_solution::Aoc; +use common::parsing::CharVecParser; +#[derive(Aoc)] +#[aoc(input = Vec)] +#[aoc(parser = CharVecParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day06; -impl AocSolution for Day06 { - type Input = Vec; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - as_char_vec(raw.as_ref()) - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - // since our window size is 4 and 14 for part1 and part2 respectively, // it's more efficient to do full slice lookup as opposed to paying for the instantiation cost of a HashSet fn solve(input: Vec, window_size: usize) -> usize { diff --git a/2022/day06/src/main.rs b/2022/day06/src/main.rs index b888b80..855905e 100644 --- a/2022/day06/src/main.rs +++ b/2022/day06/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day06_2022::Day06; #[cfg(not(tarpaulin))] diff --git a/2022/day07/Cargo.toml b/2022/day07/Cargo.toml index 880d543..fca32e0 100644 --- a/2022/day07/Cargo.toml +++ b/2022/day07/Cargo.toml @@ -10,11 +10,12 @@ name = "day07_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day07/src/lib.rs b/2022/day07/src/lib.rs index dc8f772..7f2736f 100644 --- a/2022/day07/src/lib.rs +++ b/2022/day07/src/lib.rs @@ -16,30 +16,18 @@ #![warn(clippy::expect_used)] use crate::types::FileSystem; -use common::AocSolution; +use aoc_solution::Aoc; +use common::parsing::FromStrParser; mod types; +#[derive(Aoc)] +#[aoc(input = FileSystem)] +#[aoc(parser = FromStrParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day07; -impl AocSolution for Day07 { - type Input = FileSystem; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - raw.as_ref().parse() - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - pub fn part1(input: FileSystem) -> usize { input.sum_dirs_with_max_size(100000) } diff --git a/2022/day07/src/main.rs b/2022/day07/src/main.rs index 1fcbb8b..5d97029 100644 --- a/2022/day07/src/main.rs +++ b/2022/day07/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day07_2022::Day07; #[cfg(not(tarpaulin))] diff --git a/2022/day08/Cargo.toml b/2022/day08/Cargo.toml index 7614106..874fcaa 100644 --- a/2022/day08/Cargo.toml +++ b/2022/day08/Cargo.toml @@ -10,11 +10,12 @@ name = "day08_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day08/src/lib.rs b/2022/day08/src/lib.rs index 8347814..41e113f 100644 --- a/2022/day08/src/lib.rs +++ b/2022/day08/src/lib.rs @@ -16,37 +16,18 @@ #![warn(clippy::expect_used)] use crate::types::Forest; -use common::execution::execute; -use common::AocSolution; -use std::path::Path; -use std::str::FromStr; +use aoc_solution::Aoc; +use common::parsing::FromStrParser; mod types; +#[derive(Aoc)] +#[aoc(input = Forest)] +#[aoc(parser = FromStrParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day08; -impl AocSolution for Day08 { - type Input = Forest; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - raw.as_ref().parse() - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - -pub fn solve>(input_file: P) { - execute(input_file, FromStr::from_str, part1, part2) -} - pub fn part1(input: Forest) -> usize { input.count_visible_trees() } diff --git a/2022/day08/src/main.rs b/2022/day08/src/main.rs index 40c2555..e2ee148 100644 --- a/2022/day08/src/main.rs +++ b/2022/day08/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day08_2022::Day08; #[cfg(not(tarpaulin))] diff --git a/2022/day11/Cargo.toml b/2022/day11/Cargo.toml index 944cfb6..a9e6508 100644 --- a/2022/day11/Cargo.toml +++ b/2022/day11/Cargo.toml @@ -10,12 +10,13 @@ name = "day11_2022" path = "src/lib.rs" [dependencies] +aoc-solution = { path = "../../aoc-solution" } common = { path = "../../common" } anyhow = { workspace = true } num = "0.4.0" [dev-dependencies] -criterion = "0.4" +criterion = "0.5" [[bench]] name = "benchmarks" diff --git a/2022/day11/src/lib.rs b/2022/day11/src/lib.rs index 5307f7e..73b3e07 100644 --- a/2022/day11/src/lib.rs +++ b/2022/day11/src/lib.rs @@ -16,38 +16,19 @@ #![warn(clippy::expect_used)] use crate::types::{Monkey, State, WorryDecrease}; -use common::execution::execute; -use common::parsing::parse_groups; -use common::AocSolution; +use aoc_solution::Aoc; +use common::parsing::GroupsParser; use num::integer::lcm; -use std::path::Path; mod types; +#[derive(Aoc)] +#[aoc(input = Vec)] +#[aoc(parser = GroupsParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] pub struct Day11; -impl AocSolution for Day11 { - type Input = Vec; - type Part1Output = usize; - type Part2Output = usize; - - fn parse_input>(raw: M) -> Result { - parse_groups(raw.as_ref()) - } - - fn part1(input: Self::Input) -> Result { - Ok(part1(input)) - } - - fn part2(input: Self::Input) -> Result { - Ok(part2(input)) - } -} - -pub fn solve>(input_file: P) { - execute(input_file, parse_groups, part1, part2) -} - pub fn part1(input: Vec) -> usize { let mut state = State::new(input, WorryDecrease::DivByThree); state.inspection_rounds(20) diff --git a/2022/day11/src/main.rs b/2022/day11/src/main.rs index 91ce9e1..2a89683 100644 --- a/2022/day11/src/main.rs +++ b/2022/day11/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::AocSolutionSolver; +use aoc_solution::AocSolutionSolver; use day11_2022::Day11; #[cfg(not(tarpaulin))] diff --git a/common/src/lib.rs b/common/src/lib.rs index c1776a7..362fd81 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -16,5 +16,3 @@ pub mod benchmark; pub mod execution; pub mod input_read; pub mod parsing; - -pub use aoc_solution::{Aoc, AocSolution, AocSolutionSolver}; diff --git a/common/src/parsing/mod.rs b/common/src/parsing/mod.rs index 8b2b13c..f591f66 100644 --- a/common/src/parsing/mod.rs +++ b/common/src/parsing/mod.rs @@ -22,6 +22,9 @@ use std::str::FromStr; pub use impls::*; pub mod impls; +/// Uses the underlying FromStr impl of T +pub struct FromStrParser(*const PhantomData); + /// Parse input in the form of x=.. to `RangeInclusive` pub struct RangeParser; @@ -70,6 +73,18 @@ pub struct GroupsParser(*const PhantomData); /// Transforms the raw string input into a Vec pub struct CharVecParser; +impl AocInputParser for FromStrParser +where + T: FromStr, + anyhow::Error: From<::Err>, +{ + type Output = T; + + fn parse_input(raw: &str) -> Result { + raw.parse().map_err(anyhow::Error::from) + } +} + impl AocInputParser for RangeParser { type Output = RangeInclusive; diff --git a/solution-runner/Cargo.toml b/solution-runner/Cargo.toml index 095ed7a..f793232 100644 --- a/solution-runner/Cargo.toml +++ b/solution-runner/Cargo.toml @@ -8,6 +8,8 @@ edition = "2021" [dependencies] clap = { version = "4.0.29", features = ["derive"] } + +aoc-solution = { path = "../aoc-solution" } common = { path = "../common" } day01_2022 = { path = "../2022/day01" } diff --git a/solution-runner/src/main.rs b/solution-runner/src/main.rs index 81a88f8..d128093 100644 --- a/solution-runner/src/main.rs +++ b/solution-runner/src/main.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use aoc_solution::AocSolutionSolver; use clap::Parser; -use common::AocSolutionSolver; /// Simple solution runner for Advent of Code puzzles. #[derive(Parser, Debug)]