Skip to content

Commit

Permalink
Add ParserMatchesIterator
Browse files Browse the repository at this point in the history
  • Loading branch information
ictrobot committed Dec 3, 2024
1 parent c541182 commit d93ff20
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 23 deletions.
25 changes: 24 additions & 1 deletion crates/utils/src/parser/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::parser::combinator::{
Map, MapResult, Optional, Or, RepeatArrayVec, RepeatN, RepeatVec, WithPrefix, WithSuffix,
};
use crate::parser::error::{ParseError, WithErrorMsg};
use crate::parser::iterator::ParserIterator;
use crate::parser::iterator::{ParserIterator, ParserMatchesIterator};
use crate::parser::simple::{Constant, Eol};
use crate::parser::then::{Then, Then2, Unimplemented};

Expand Down Expand Up @@ -414,6 +414,29 @@ pub trait Parser: Sized {
parser: self,
}
}

/// Create an iterator which returns matches only and skips over errors.
///
/// This is intended for cases that require extracting matches out of the input.
/// Otherwise, [`parse_iterator`](Self::parse_iterator) should be used with a parser that can
/// match the entire input structure.
///
/// # Examples
/// ```
/// # use utils::parser::{self, Parser};
/// assert_eq!(
/// parser::u32()
/// .matches_iterator("abc123d456efg7hi8jk9lmnop")
/// .collect::<Vec<_>>(),
/// vec![123, 456, 7, 8, 9]
/// );
/// ```
fn matches_iterator(self, input: &str) -> ParserMatchesIterator<Self> {
ParserMatchesIterator {
remaining: input.as_bytes(),
parser: self,
}
}
}

// Workaround to allow using methods which consume a parser in methods which take references.
Expand Down
28 changes: 28 additions & 0 deletions crates/utils/src/parser/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,31 @@ impl<'a, P: Parser> Iterator for ParserIterator<'a, P> {
}

impl<P: Parser> FusedIterator for ParserIterator<'_, P> {}

/// An iterator which returns successful parse outputs only, skipping over errors.
///
/// See [`Parser::matches_iterator`].
#[derive(Copy, Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ParserMatchesIterator<'a, P> {
pub(super) remaining: &'a [u8],
pub(super) parser: P,
}

impl<'a, P: Parser> Iterator for ParserMatchesIterator<'a, P> {
type Item = P::Output<'a>;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
while !self.remaining.is_empty() {
if let Ok((v, remaining)) = self.parser.parse(self.remaining) {
self.remaining = remaining;
return Some(v);
}
self.remaining = &self.remaining[1..];
}
None
}
}

impl<P: Parser> FusedIterator for ParserMatchesIterator<'_, P> {}
2 changes: 1 addition & 1 deletion crates/utils/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod then;

pub use base::*;
pub use error::ParseError;
pub use iterator::ParserIterator;
pub use iterator::{ParserIterator, ParserMatchesIterator};
pub use number::{i128, i16, i32, i64, i8, number_range, u128, u16, u32, u64, u8};
pub use one_of::one_of;
pub use simple::{byte, byte_range, constant, eof, eol, noop, take_while, take_while1};
Expand Down
41 changes: 20 additions & 21 deletions crates/year2024/src/day03.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,31 @@ pub struct Day03 {
part2: u32,
}

enum Instruction {
Mul(u32, u32),
Do,
Dont,
}

impl Day03 {
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
let matches = parser::parse_tree!(
("mul(", a @ parser::u32(), ",", b @ parser::u32(), ")") => Instruction::Mul(a, b),
("don't()") => Instruction::Dont,
("do()") => Instruction::Do,
)
.matches_iterator(input);

let (mut part1, mut part2) = (0, 0);
let mut enabled = true;

let mut input = input.as_bytes();
while !input.is_empty() {
if let Ok(([a, b], remaining)) = parser::u32()
.repeat_n(b',')
.with_prefix("mul(")
.with_suffix(")")
.parse(input)
{
part1 += a * b;
if enabled {
part2 += a * b;
for instruction in matches {
match instruction {
Instruction::Mul(a, b) => {
part1 += a * b;
part2 += if enabled { a * b } else { 0 };
}
input = remaining;
} else if let Some(remaining) = input.strip_prefix(b"do()") {
enabled = true;
input = remaining;
} else if let Some(remaining) = input.strip_prefix(b"don't()") {
enabled = false;
input = remaining;
} else {
input = &input[1..];
Instruction::Do => enabled = true,
Instruction::Dont => enabled = false,
}
}

Expand Down

0 comments on commit d93ff20

Please sign in to comment.