mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 21:00:31 +01:00
Make parsers more robust
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
//! Common helper utilities to all days
|
//! Common helper utilities to all days
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use nom::error::ErrorKind;
|
||||||
|
use nom::error::ParseError;
|
||||||
use nom::Finish;
|
use nom::Finish;
|
||||||
|
use nom::IResult;
|
||||||
|
use nom::InputLength;
|
||||||
use nom::Parser;
|
use nom::Parser;
|
||||||
|
|
||||||
/// Parse input from some nom parser and return as an anyhow result
|
/// Parse input from some nom parser and return as an anyhow result
|
||||||
@@ -12,8 +16,79 @@ pub fn parse_input<'a, O>(
|
|||||||
input: &'a [u8],
|
input: &'a [u8],
|
||||||
mut parser: impl Parser<&'a [u8], O, nom::error::Error<&'a [u8]>>,
|
mut parser: impl Parser<&'a [u8], O, nom::error::Error<&'a [u8]>>,
|
||||||
) -> Result<O> {
|
) -> Result<O> {
|
||||||
match parser.parse(input).finish() {
|
let (unparsed, output) = parser.parse(input).finish().map_err(|e| {
|
||||||
Ok((_, value)) => Ok(value),
|
anyhow::anyhow!(
|
||||||
Err(err) => anyhow::bail!("Failed to parse at: {err:?}"),
|
"Parser error {:?} to parse at {}",
|
||||||
|
e.code,
|
||||||
|
String::from_utf8_lossy(e.input)
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !unparsed.is_empty() {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Not all input consumed: {}",
|
||||||
|
String::from_utf8_lossy(unparsed)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a parser iteratively and reduces the results using the given function. Fails if the
|
||||||
|
/// embedded parser doesn't return at least one result.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// - `f`: the function to apply
|
||||||
|
/// - `g`: the function that combines the result o `f` with previous results
|
||||||
|
///
|
||||||
|
/// This implementation is based on [`nom::multi::fold_many1`] with minor differences. If
|
||||||
|
/// successful, this should probably be upstreamed.
|
||||||
|
pub fn reduce_many1<I, O, E, F>(
|
||||||
|
mut f: F,
|
||||||
|
mut g: impl FnMut(O, O) -> O,
|
||||||
|
) -> impl FnMut(I) -> IResult<I, O, E>
|
||||||
|
where
|
||||||
|
I: Clone + InputLength,
|
||||||
|
E: ParseError<I>,
|
||||||
|
F: Parser<I, O, E>,
|
||||||
|
{
|
||||||
|
// Cannot delegate to fold_many0 because that would make the function FnOnce rather than FnMut,
|
||||||
|
// since it would transfer ownership of the embedded parser to fold_many0.
|
||||||
|
move |i: I| {
|
||||||
|
let _i = i.clone();
|
||||||
|
match f.parse(_i) {
|
||||||
|
Err(nom::Err::Error(_)) => {
|
||||||
|
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Many1)))
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok((i1, mut acc)) => {
|
||||||
|
let mut input = i1;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let _input = input.clone();
|
||||||
|
let len = input.input_len();
|
||||||
|
match f.parse(_input) {
|
||||||
|
Err(nom::Err::Error(_)) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
Ok((i, o)) => {
|
||||||
|
// infinite loop check: the parser must always consume
|
||||||
|
if i.input_len() == len {
|
||||||
|
return Err(nom::Err::Failure(E::from_error_kind(
|
||||||
|
i,
|
||||||
|
ErrorKind::Many1,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
acc = g(acc, o);
|
||||||
|
input = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, acc))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,19 @@ use std::ops::Add;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nom::character::complete::newline;
|
use nom::character::complete::newline;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::multi::fold_many1;
|
|
||||||
use nom::multi::separated_list0;
|
use nom::multi::separated_list0;
|
||||||
use nom::sequence::terminated;
|
use nom::sequence::terminated;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
|
|
||||||
use crate::common::parse_input;
|
use crate::common::parse_input;
|
||||||
|
use crate::common::reduce_many1;
|
||||||
|
|
||||||
fn parse_elf(input: &[u8]) -> IResult<&[u8], i32> {
|
fn parse_elf(input: &[u8]) -> IResult<&[u8], i32> {
|
||||||
fold_many1(
|
reduce_many1(terminated(nom::character::complete::i32, newline), Add::add)(input)
|
||||||
terminated(nom::character::complete::i32, newline),
|
|
||||||
|| 0,
|
|
||||||
Add::add,
|
|
||||||
)(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_max(input: &[u8]) -> IResult<&[u8], i32> {
|
fn parse_max(input: &[u8]) -> IResult<&[u8], i32> {
|
||||||
fold_many1(terminated(parse_elf, opt(newline)), || 0, Ord::max)(input)
|
reduce_many1(terminated(parse_elf, opt(newline)), Ord::max)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn part1(input: &[u8]) -> Result<String> {
|
pub fn part1(input: &[u8]) -> Result<String> {
|
||||||
|
|||||||
Reference in New Issue
Block a user