aob_common 1.0.2

String searching with wildcards
Documentation
use crate::error::SimpleError;
use chumsky::{
    primitive::{
        choice,
        filter,
        filter_map,
        just,
    },
    Parser,
};

#[must_use]
pub(crate) fn ida_pattern() -> impl Parser<char, Vec<Option<u8>>, Error = SimpleError> {
    let whitespace = filter(|c: &char| c.is_whitespace()).repeated();
    let wildcard = just("?").repeated().at_least(1).at_most(2).to(None);
    let byte = filter_map(|span, c: char| {
        if c.is_ascii_hexdigit() {
            Ok(c as u8)
        } else {
            Err(SimpleError::invalid_hexdigit(span, c))
        }
    })
    .repeated()
    .exactly(2)
    .map(|digits| {
        let digits = String::from_utf8(digits).unwrap();
        Some(u8::from_str_radix(&digits, 16).unwrap())
    });

    choice((wildcard, byte))
        .separated_by(whitespace.at_least(1))
        .collect()
        .padded_by(whitespace)
}

#[cfg(test)]
mod tests {
    use chumsky::{
        primitive::end,
        Parser as _,
    };

    #[test]
    fn test_success() {
        let parser = super::ida_pattern().then_ignore(end());
        assert_eq!(
            parser.parse("AA ? BB").unwrap(),
            [Some(0xAA), None, Some(0xBB)]
        );
        assert_eq!(
            parser.parse("AA ?? BB").unwrap(),
            [Some(0xAA), None, Some(0xBB)]
        );
        assert_eq!(
            parser.parse("AA    ? BB").unwrap(),
            [Some(0xAA), None, Some(0xBB)]
        );
        assert_eq!(
            parser.parse(" AA ? BB").unwrap(),
            [Some(0xAA), None, Some(0xBB)]
        );
        assert_eq!(
            parser.parse("AA ? BB ").unwrap(),
            [Some(0xAA), None, Some(0xBB)]
        );
    }

    #[test]
    fn test_error() {
        let parser = super::ida_pattern().then_ignore(end());
        assert!(parser.parse("A ? BB").is_err());
        assert!(parser.parse("AAA ? BB").is_err());
        assert!(parser.parse("AA ??? BB").is_err());
        assert!(parser.parse("Ax ? BB").is_err());
        assert!(parser.parse("\"AA ? BB\"").is_err());
    }
}