Make parsers more robust

This commit is contained in:
2022-12-01 11:28:59 +01:00
parent 85a51b13c1
commit 48594a75e6
2 changed files with 81 additions and 10 deletions

View File

@@ -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))
}
}
} }
} }

View File

@@ -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> {