switch to nom

This commit is contained in:
Rüdiger Ludwig 2023-07-30 16:42:17 +02:00
parent a5f19ecae1
commit b00835c25e
7 changed files with 288 additions and 181 deletions

View file

@ -6,9 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
const_format = "0.2.31"
itertools = "0.11" itertools = "0.11"
num-traits = "0.2" num-traits = "0.2"
once_cell = "1.18.0"
regex = "1.7"
thiserror = "1.0" thiserror = "1.0"
nom = "7"

View file

@ -3,5 +3,6 @@ pub mod direction;
pub mod file; pub mod file;
pub mod helper; pub mod helper;
pub mod math; pub mod math;
pub mod parser;
pub mod pos; pub mod pos;
pub mod turn; pub mod turn;

70
src/common/parser.rs Normal file
View file

@ -0,0 +1,70 @@
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{line_ending, space0, space1},
combinator::{eof, opt, value},
error::ParseError,
multi::many0,
sequence::{delimited, preceded, terminated, tuple},
Err, IResult, Parser,
};
pub fn extract_result<I, F, O, E: ParseError<I>>(
mut parser: F,
) -> impl FnMut(I) -> Result<O, Err<E>>
where
F: FnMut(I) -> IResult<I, O, E>,
{
move |input: I| parser(input).map(|(_, value)| value)
}
pub fn ignore<I, F, O, E: ParseError<I>>(mut parser: F) -> impl FnMut(I) -> Result<I, Err<E>>
where
F: FnMut(I) -> IResult<I, O, E>,
{
move |input: I| parser(input).map(|(i, _)| i)
}
pub fn eol_terminated<'a, F, O, E: ParseError<&'a str>>(
line: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
terminated(line, alt((line_ending, eof)))
}
pub fn true_false<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, bool, E> {
alt((value(true, tag("true")), value(false, tag("false"))))(input)
}
pub fn empty_lines<'a, E: ParseError<&'a str>>(input: &'a str) -> Result<&'a str, Err<E>> {
ignore(tuple((many0(line_ending), opt(eof))))(input)
}
pub fn trim_left1<'a, F, O, E: ParseError<&'a str>>(
inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
preceded(space1, inner)
}
pub fn trim0<'a, F, O, E: ParseError<&'a str>>(
inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
delimited(space0, inner, space0)
}
pub fn trim1<'a, F, O, E: ParseError<&'a str>>(
inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
delimited(space1, inner, space1)
}

View file

@ -1,3 +1,5 @@
use std::str::FromStr;
use crate::common::file::split_lines; use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType}; use super::template::{DayTrait, ResultType};
@ -14,13 +16,13 @@ impl DayTrait for Day {
} }
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> { fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let forest = Forest::try_from(lines)?; let forest: Forest = lines.parse()?;
let result = forest.count_visible(); let result = forest.count_visible();
Ok(ResultType::Integer(result)) Ok(ResultType::Integer(result))
} }
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> { fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let forest = Forest::try_from(lines)?; let forest: Forest = lines.parse()?;
let result = forest.best_score(); let result = forest.best_score();
Ok(ResultType::Integer(result)) Ok(ResultType::Integer(result))
} }
@ -181,10 +183,10 @@ impl Forest {
} }
} }
impl TryFrom<&str> for Forest { impl FromStr for Forest {
type Error = ForestError; type Err = ForestError;
fn try_from(lines: &str) -> Result<Self, Self::Error> { fn from_str(lines: &str) -> Result<Self, Self::Err> {
split_lines(lines) split_lines(lines)
.map(|line| { .map(|line| {
line.chars() line.chars()
@ -206,7 +208,7 @@ mod test {
fn test_parse() -> Result<()> { fn test_parse() -> Result<()> {
let day = Day {}; let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?; let lines = read_string(day.get_day_number(), "example01.txt")?;
let forest = Forest::try_from(lines.as_str())?; let forest: Forest = lines.parse()?;
assert_eq!(forest.width, 5); assert_eq!(forest.width, 5);
assert_eq!(forest.height, 5); assert_eq!(forest.height, 5);

View file

@ -1,9 +1,19 @@
use crate::common::file::split_lines; use crate::common::parser::{
empty_lines, eol_terminated, extract_result, ignore, trim0, trim_left1, true_false,
};
use super::template::{DayTrait, ResultType}; use super::template::{DayTrait, ResultType};
use once_cell::sync::Lazy; use nom::{
use regex::Regex; branch::alt,
use std::{iter::zip, num::ParseIntError}; bytes::complete::tag,
character::complete::{char, i64, u32},
combinator::{map, value, verify},
error::Error,
multi::{many1, separated_list1},
sequence::{preceded, tuple},
Err, IResult, Parser,
};
use std::{iter::zip, str::FromStr};
use thiserror::Error; use thiserror::Error;
const DAY_NUMBER: usize = 11; const DAY_NUMBER: usize = 11;
@ -16,50 +26,50 @@ impl DayTrait for Day {
} }
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> { fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let troop = Troop::try_from(lines)?; let troop: Troop = lines.parse()?;
Ok(ResultType::Integer(troop.play(20))) Ok(ResultType::Integer(troop.play(20)))
} }
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> { fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let troop = Troop::try_from(lines)?; let troop: Troop = lines.parse()?;
Ok(ResultType::Integer(troop.play_again(10_000))) Ok(ResultType::Integer(troop.play_again(10_000)))
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum MonkeyError { enum MonkeyError {
#[error("Not an Integer")] #[error("Error while parsing: {0}")]
NotAnInteger(#[from] ParseIntError), ParsingError(String),
#[error("Can't parse line: {0}")]
UnknownLine(String),
#[error("Did not expect end of Input")]
PrematureEndOfInput,
} }
#[derive(Debug, PartialEq)] impl From<Err<Error<&str>>> for MonkeyError {
fn from(value: Err<Error<&str>>) -> Self {
MonkeyError::ParsingError(value.to_string())
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
enum Operation { enum Operation {
Plus(i64),
Times(i64),
Squared, Squared,
Times(i64),
Plus(i64),
} }
impl Operation { impl Operation {
pub fn parse(line: &str) -> Operation { fn parse(input: &str) -> IResult<&str, Self> {
match line.split_whitespace().collect::<Vec<_>>()[..] { let (input, _) = tag("new = old ")(input)?;
["*", "old"] => Operation::Squared, alt((
["*", value] => Operation::Times(value.parse().unwrap()), value(Operation::Squared, tag("* old")),
["+", value] => Operation::Plus(value.parse().unwrap()), preceded(char('*'), trim0(i64.map(|a| Operation::Times(a)))),
_ => unreachable!(), preceded(char('+'), trim0(i64.map(|a| Operation::Plus(a)))),
} ))(input)
} }
pub fn calc(&self, old: i64) -> i64 { pub fn calc(&self, old: i64) -> i64 {
match self { match self {
Operation::Plus(value) => old + *value,
Operation::Times(value) => old * value,
Operation::Squared => old.pow(2), Operation::Squared => old.pow(2),
Operation::Times(value) => old * value,
Operation::Plus(value) => old + value,
} }
} }
} }
@ -74,90 +84,96 @@ struct Monkey {
bad_monkey: usize, bad_monkey: usize,
} }
static MONKEY: Lazy<Regex> = Lazy::new(|| Regex::new(r"Monkey (\d+)").unwrap());
static STARTING: Lazy<Regex> =
Lazy::new(|| Regex::new(r"Starting items: (\d+(?:, \d+)*)").unwrap());
static OP: Lazy<Regex> =
Lazy::new(|| Regex::new(r"Operation: new = old ([+*] \d+|\* old)").unwrap());
static TEST: Lazy<Regex> = Lazy::new(|| Regex::new(r"Test: divisible by (\d+)").unwrap());
static NEXT: Lazy<Regex> =
Lazy::new(|| Regex::new(r"If (?:true|false): throw to monkey (\d+)").unwrap());
impl Monkey { impl Monkey {
fn parse_line<'a>(re: &Regex, line: &'a str) -> Result<&'a str, MonkeyError> { fn number_parse(input: &str) -> IResult<&str, usize> {
let caps = re let input = ignore(tag("Monkey "))(input)?;
.captures(line) let (input, number) = trim0(u32.map(|val| val as usize))(input)?;
.ok_or(MonkeyError::UnknownLine(line.to_owned()))?; let input = ignore(char(':'))(input)?;
Ok(caps.get(1).unwrap().as_str())
Ok((input, number))
} }
pub fn parse_one( fn starting_items_parse(input: &str) -> IResult<&str, Vec<i64>> {
iterator: &mut dyn Iterator<Item = &str>, let input = ignore(tag("Starting items:"))(input)?;
) -> Result<Option<Monkey>, MonkeyError> { separated_list1(char(','), trim0(i64))(input)
if let Some(line) = iterator.next() { }
let number: usize = Monkey::parse_line(&MONKEY, line)?.parse()?;
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; fn operation_parse(input: &str) -> IResult<&str, Operation> {
let start = Monkey::parse_line(&STARTING, line)?; let input = ignore(tag("Operation:"))(input)?;
let items = start trim0(Operation::parse)(input)
.split(", ") }
.map(|item| item.parse::<i64>())
.collect::<Result<Vec<i64>, _>>()?;
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; fn divisor_parse(input: &str) -> IResult<&str, i64> {
let operation = Operation::parse(Monkey::parse_line(&OP, line)?); let input = ignore(tag("Test: divisible by"))(input)?;
trim0(i64)(input)
}
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; fn target_parse(want_good: bool) -> impl FnMut(&str) -> IResult<&str, usize> {
let divisor: i64 = Monkey::parse_line(&TEST, line)?.parse()?; move |input: &str| {
let input = ignore(tuple((
tag("If"),
trim_left1(verify(true_false, |is_good| *is_good == want_good)),
tag(": throw to monkey"),
)))(input)?;
trim0(u32.map(|val| val as usize))(input)
}
}
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; fn parse(input: &str) -> IResult<&str, Monkey> {
let good_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?; let input = empty_lines(input)?;
let (input, number) = eol_terminated(trim0(Monkey::number_parse))(input)?;
let (input, items) = eol_terminated(trim0(Monkey::starting_items_parse))(input)?;
let (input, operation) = eol_terminated(trim0(Monkey::operation_parse))(input)?;
let (input, divisor) = eol_terminated(trim0(Monkey::divisor_parse))(input)?;
let (input, good_monkey) = eol_terminated(trim0(Monkey::target_parse(true)))(input)?;
let (input, bad_monkey) = eol_terminated(trim0(Monkey::target_parse(false)))(input)?;
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?; Ok((
let bad_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?; input,
Monkey {
iterator.next();
Ok(Some(Monkey {
number, number,
items, items,
operation, operation,
divisor, divisor,
good_monkey, good_monkey,
bad_monkey, bad_monkey,
})) },
} else { ))
Ok(None)
}
} }
#[inline]
pub fn process(&self, value: i64) -> i64 { pub fn process(&self, value: i64) -> i64 {
self.operation.calc(value) self.operation.calc(value)
} }
#[inline]
pub fn check(&self, value: i64) -> bool { pub fn check(&self, value: i64) -> bool {
value % self.divisor == 0 value % self.divisor == 0
} }
#[allow(dead_code)]
fn parse_one(lines: &str) -> Result<Monkey, MonkeyError> {
Ok(extract_result(Monkey::parse)(lines)?)
}
} }
struct Troop { struct Troop {
monkeys: Vec<Monkey>, monkeys: Vec<Monkey>,
} }
impl TryFrom<&str> for Troop { impl FromStr for Troop {
type Error = MonkeyError; type Err = MonkeyError;
fn try_from(value: &str) -> Result<Self, Self::Error> { fn from_str(lines: &str) -> Result<Self, Self::Err> {
let mut iter = split_lines(value); Ok(extract_result(Troop::parse)(lines)?)
let mut monkeys = Vec::new();
while let Some(monkey) = Monkey::parse_one(&mut iter)? {
monkeys.push(monkey);
}
Ok(Troop { monkeys })
} }
} }
impl Troop { impl Troop {
fn parse(input: &str) -> IResult<&str, Troop> {
map(many1(Monkey::parse), |monkeys| Troop { monkeys })(input)
}
fn do_play<F>(&self, rounds: usize, alter: F) -> i64 fn do_play<F>(&self, rounds: usize, alter: F) -> i64
where where
F: Fn(i64) -> i64, F: Fn(i64) -> i64,
@ -204,7 +220,6 @@ mod test {
fn test_parse() -> Result<()> { fn test_parse() -> Result<()> {
let day = Day {}; let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?; let lines = read_string(day.get_day_number(), "example01.txt")?;
let mut lines = split_lines(&lines);
let expected = Monkey { let expected = Monkey {
number: 0, number: 0,
items: vec![79, 98], items: vec![79, 98],
@ -213,7 +228,7 @@ mod test {
good_monkey: 2, good_monkey: 2,
bad_monkey: 3, bad_monkey: 3,
}; };
let result = Monkey::parse_one(&mut lines)?.unwrap(); let result = Monkey::parse_one(&lines)?;
assert_eq!(result, expected); assert_eq!(result, expected);
Ok(()) Ok(())
@ -224,7 +239,7 @@ mod test {
let day = Day {}; let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?; let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = 4; let expected = 4;
let result = Troop::try_from(lines.as_str())?; let result: Troop = lines.parse()?;
assert_eq!(result.monkeys.len(), expected); assert_eq!(result.monkeys.len(), expected);
Ok(()) Ok(())

View file

@ -1,37 +1,44 @@
use super::template::{DayTrait, ResultType}; use super::template::{DayTrait, ResultType};
use crate::common::{file::split_lines, pos::Pos}; use crate::common::{
parser::{eol_terminated, extract_result, ignore, trim0},
pos::Pos,
};
use itertools::Itertools; use itertools::Itertools;
use once_cell::sync::Lazy; use nom::{
use regex::Regex; bytes::complete::tag,
use std::{collections::HashSet, num::ParseIntError}; character::complete::{char, i64},
error::Error,
multi::many0,
sequence::{separated_pair, tuple},
Err, IResult,
};
use std::collections::HashSet;
use thiserror::Error; use thiserror::Error;
const DAY_NUMBER: usize = 15; const DAY_NUMBER: usize = 15;
pub struct Day; pub struct Day;
fn parse_row(input: &str) -> IResult<&str, i64> {
eol_terminated(i64)(input)
}
impl DayTrait for Day { impl DayTrait for Day {
fn get_day_number(&self) -> usize { fn get_day_number(&self) -> usize {
DAY_NUMBER DAY_NUMBER
} }
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> { fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let lines = split_lines(lines).collect_vec(); let (lines, row) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?;
if lines.len() < 2 { let (sensors, beacons) = Day::parse_all(lines)?;
Err(SensorError::ToFewLines)?;
}
let row = lines[0].parse()?;
let (sensors, beacons) = Day::parse_all(&lines[1..])?;
let result = Day::count_coverage_at(&sensors, &beacons, row); let result = Day::count_coverage_at(&sensors, &beacons, row);
Ok(ResultType::Integer(result)) Ok(ResultType::Integer(result))
} }
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> { fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let lines = split_lines(lines).collect_vec(); let (lines, _) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?;
if lines.len() < 2 { let (sensors, _) = Day::parse_all(lines)?;
Err(SensorError::ToFewLines)?;
}
let (sensors, _) = Day::parse_all(&lines[1..])?;
let mid_lines = Day::dividing_lines(&sensors); let mid_lines = Day::dividing_lines(&sensors);
let points = mid_lines let points = mid_lines
.iter() .iter()
@ -51,11 +58,12 @@ impl DayTrait for Day {
} }
impl Day { impl Day {
fn parse_all(lines: &[&str]) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> { fn parse_all(lines: &str) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> {
let data = extract_result(many0(eol_terminated(Sensor::parse)))(lines)?;
let mut sensors = HashSet::new(); let mut sensors = HashSet::new();
let mut beacons = HashSet::new(); let mut beacons = HashSet::new();
for line in lines { for (sensor, beacon) in data {
let (sensor, beacon) = Sensor::parse(line)?;
sensors.insert(sensor); sensors.insert(sensor);
beacons.insert(beacon); beacons.insert(beacon);
} }
@ -91,17 +99,20 @@ impl Day {
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum SensorError { enum SensorError {
#[error("Not an Integer")] #[error("Error Parsing Input: {0}")]
NotAnInt(#[from] ParseIntError), ParsingError(String),
#[error("Unknown line: {0}")]
UnknownLine(String),
#[error("Did not find exactly one point for the sensor: {0}")] #[error("Did not find exactly one point for the sensor: {0}")]
NotExactlyOneResult(usize), NotExactlyOneResult(usize),
#[error("Too few lines in input.txt")] #[error("Row line not found at start of Input")]
ToFewLines, RowLineNotFound,
}
impl From<Err<Error<&str>>> for SensorError {
fn from(error: Err<Error<&str>>) -> Self {
SensorError::ParsingError(error.to_string())
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -220,28 +231,30 @@ struct Sensor {
radius: i64, radius: i64,
} }
static SENSOR: Lazy<Regex> =
Lazy::new(|| Regex::new(r"x=(-?\d+), y=(-?\d+).*x=(-?\d+), y=(-?\d+)").unwrap());
impl Sensor { impl Sensor {
pub fn parse(line: &str) -> Result<(Sensor, Pos<i64>), SensorError> { fn component<'a>(name: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, i64> {
let caps = SENSOR move |input: &str| {
.captures(line) let input = ignore(tuple((tag(name), char('='))))(input)?;
.ok_or(SensorError::UnknownLine(line.to_owned()))?; trim0(i64)(input)
let sensor_x = caps.get(1).unwrap().as_str().parse()?; }
let sensor_y = caps.get(2).unwrap().as_str().parse()?; }
let beacon_x = caps.get(3).unwrap().as_str().parse()?;
let beacon_y = caps.get(4).unwrap().as_str().parse()?; fn parse_pos(input: &str) -> IResult<&str, Pos<i64>> {
let sensor = Pos::new(sensor_x, sensor_y); let (input, (x, y)) = separated_pair(
let beacon = Pos::new(beacon_x, beacon_y); Sensor::component("x"),
let radius = sensor.taxicab(&beacon); trim0(char(',')),
Ok(( Sensor::component("y"),
Sensor { )(input)?;
pos: sensor, Ok((input, Pos::new(x, y)))
radius, }
},
beacon, pub fn parse(input: &str) -> IResult<&str, (Sensor, Pos<i64>)> {
)) let input = ignore(tag("Sensor at"))(input)?;
let (input, pos) = trim0(Sensor::parse_pos)(input)?;
let input = ignore(tag(": closest beacon is at"))(input)?;
let (input, beacon) = trim0(Sensor::parse_pos)(input)?;
let radius = pos.taxicab(&beacon);
Ok((input, (Sensor { pos, radius }, beacon)))
} }
pub fn range_at(&self, y: i64) -> Option<Range> { pub fn range_at(&self, y: i64) -> Option<Range> {
@ -279,7 +292,7 @@ mod test {
}, },
Pos::new(-2, 15), Pos::new(-2, 15),
); );
let result = Sensor::parse(input)?; let result = extract_result(Sensor::parse)(input)?;
assert_eq!(result, expected); assert_eq!(result, expected);
Ok(()) Ok(())

View file

@ -1,17 +1,20 @@
use super::template::{DayTrait, ResultType};
use crate::common::parser::{eol_terminated, extract_result, ignore, trim0, trim1, trim_left1};
use itertools::Itertools;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{alpha1, i64},
error::Error,
multi::{many1, separated_list1},
Err, IResult,
};
use std::collections::BinaryHeap;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::hash::Hash; use std::hash::Hash;
use std::iter::Sum; use std::iter::Sum;
use std::ops::{Add, Deref, Mul, Sub}; use std::ops::{Add, Deref, Mul, Sub};
use std::{collections::BinaryHeap, num::ParseIntError};
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use const_format::concatcp;
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use thiserror::Error; use thiserror::Error;
const DAY_NUMBER: usize = 16; const DAY_NUMBER: usize = 16;
@ -129,11 +132,8 @@ impl Deref for Index {
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum ValveError { enum ValveError {
#[error("Not an Integer")]
NotAnInt(#[from] ParseIntError),
#[error("Not a valid description: {0}")] #[error("Not a valid description: {0}")]
CouldNotParse(String), ParsingError(String),
#[error("Not a valid valve: {0}")] #[error("Not a valid valve: {0}")]
ValveNotFound(String), ValveNotFound(String),
@ -142,17 +142,11 @@ enum ValveError {
NoOptimumFound, NoOptimumFound,
} }
const ID: &str = "[[:alpha:]]+"; impl From<Err<Error<&str>>> for ValveError {
const COMMON: &str = concatcp!("^Valve (?<id>", ID, r") has flow rate=(?<rate>\d+); "); fn from(error: Err<Error<&str>>) -> Self {
const PLURAL_STR: &str = concatcp!( ValveError::ParsingError(error.to_string())
COMMON, }
"tunnels lead to valves (?<exits>", }
ID,
"(?:, ",
ID,
")+)$"
);
const SINGULAR_STR: &str = concatcp!(COMMON, "tunnel leads to valve (?<exits>", ID, ")$");
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct RawValve<'a> { struct RawValve<'a> {
@ -170,29 +164,45 @@ impl<'a> RawValve<'a> {
} }
} }
fn from_regex(regex: &Regex, line: &'a str) -> Option<Result<RawValve<'a>, ValveError>> { fn parse_start(input: &str) -> IResult<&str, (&str, Flow)> {
regex.captures(line).map(|caps| { let input = ignore(tag("Valve"))(input)?;
match (caps.name("id"), caps.name("rate"), caps.name("exits")) { let (input, valve) = trim1(alpha1)(input)?;
(Some(id), Some(rate), Some(exits)) => { let input = ignore(tag("has flow rate="))(input)?;
let rate = rate.as_str().parse()?; let (input, flow) = trim0(i64)(input)?;
let neighbors = exits.as_str().split(",").map(|s| s.trim_start()).collect(); let input = ignore(tag(";"))(input)?;
Ok(RawValve::create(id.as_str(), Flow(rate), neighbors)) Ok((input, (valve, Flow(flow))))
} }
_ => Err(ValveError::CouldNotParse(line.to_string())),
fn parse_singular(input: &str) -> IResult<&str, Vec<&str>> {
let input = ignore(tag("tunnel leads to valve "))(input)?;
let (input, valve) = alpha1(input)?;
Ok((input, vec![valve]))
} }
})
fn parse_plural(input: &str) -> IResult<&str, Vec<&str>> {
let input = ignore(tag("tunnels lead to valves "))(input)?;
let (input, valve) = separated_list1(tag(","), trim0(alpha1))(input)?;
Ok((input, valve))
}
fn parse(input: &str) -> IResult<&str, RawValve> {
let (input, (id, flow)) = RawValve::parse_start(input)?;
let (input, neighbors) = trim_left1(eol_terminated(alt((
RawValve::parse_plural,
RawValve::parse_singular,
))))(input)?;
Ok((input, RawValve::create(id, flow, neighbors)))
} }
} }
impl<'a> TryFrom<&'a str> for RawValve<'a> { impl<'a> TryFrom<&'a str> for RawValve<'a> {
type Error = ValveError; type Error = ValveError;
fn try_from(value: &'a str) -> Result<RawValve<'a>, Self::Error> { fn try_from(value: &'a str) -> Result<Self, Self::Error> {
static PLURAL: Lazy<Regex> = Lazy::new(|| Regex::new(PLURAL_STR).unwrap()); Ok(extract_result(RawValve::parse)(value)?)
static SINGULAR: Lazy<Regex> = Lazy::new(|| Regex::new(SINGULAR_STR).unwrap());
RawValve::from_regex(&PLURAL, value)
.or_else(|| RawValve::from_regex(&SINGULAR, value))
.unwrap_or_else(|| Err(ValveError::CouldNotParse(value.to_string())))
} }
} }
@ -267,9 +277,7 @@ struct ValveSystem {
impl ValveSystem { impl ValveSystem {
fn build(lines: &str, start: &str) -> Result<ValveSystem, ValveError> { fn build(lines: &str, start: &str) -> Result<ValveSystem, ValveError> {
let mut raw = split_lines(lines) let mut raw = extract_result(many1(RawValve::parse))(lines)?;
.map(|line| RawValve::try_from(line))
.collect::<Result<Vec<_>, _>>()?;
raw.sort_unstable_by_key(|valve| valve.id); raw.sort_unstable_by_key(|valve| valve.id);
let start_idx = raw let start_idx = raw
@ -931,7 +939,7 @@ mod test {
fn parse_plural() -> Result<()> { fn parse_plural() -> Result<()> {
let line = "Valve BB has flow rate=13; tunnels lead to valves CC, AA"; let line = "Valve BB has flow rate=13; tunnels lead to valves CC, AA";
let expected = RawValve::create("BB", Flow(13), vec!["CC", "AA"]); let expected = RawValve::create("BB", Flow(13), vec!["CC", "AA"]);
let result = RawValve::try_from(line)?; let result = line.try_into()?;
assert_eq!(expected, result); assert_eq!(expected, result);
Ok(()) Ok(())
@ -941,7 +949,7 @@ mod test {
fn parse_singular() -> Result<()> { fn parse_singular() -> Result<()> {
let line = "Valve HH has flow rate=22; tunnel leads to valve GG"; let line = "Valve HH has flow rate=22; tunnel leads to valve GG";
let expected = RawValve::create("HH", Flow(22), vec!["GG"]); let expected = RawValve::create("HH", Flow(22), vec!["GG"]);
let result = RawValve::try_from(line)?; let result = line.try_into()?;
assert_eq!(expected, result); assert_eq!(expected, result);
Ok(()) Ok(())