user1244932
user1244932

Reputation: 8142

How do I match multiple bytes at arbitrary positions?

I have to match bytes in a &[u8] and want to simplify my code. Right now it looks like this:

fn f(s: &[u8]) {
    if Some(&b'A') == s.get(5) && Some(&b'X') == s.get(16) &&
        (Some(&b'B') == s.get(17) || Some(&b'C') == s.get(18)) {
    } else if Some(&b'1') == s.get(4) && Some(&b'2') == s.get(7) && Some(&b'D') == s.get(10) {
    }
}

I know about nom, but it does not make this particular case simpler, although I'm using nom afterwards to extract the data after the match.

So my first attempt to simplify code is to write these macros:

macro_rules! m {
    ($ch:expr, $pos:expr) => {{
        Some(&$ch) == line.get($pos)
    }};
    ($ch1:expr, $ch2:expr, $pos:expr) => {{
        Some(&$ch1) == line.get($pos) || Some(&$ch2) == line.get($pos)
    }}
}

It reduces code size, and decreases the possibility of making a mistake, but I want more:

macro_rules! match_several {
    ($($ch:expr, $pos:expr),*, $last_ch:expr, $last_pos:expr) => {{
        (Some(&$ch) == line.get($pos) &&)* Some(&$last_ch) == line.get($last_pos)
    }}
}

But the compiler gives this error:

 error: local ambiguity: multiple parsing options: built-in NTs expr ('last_ch') or expr ('ch').
  --> lib.rs:45:32
   |
45 |     if match_several!(b'P', 4, b' ', 5, b'A', 12) {
   |        ------------------------^^^^-------------- in this macro invocation

Test code:

let line: &'static [u8] = b"0123P 6789ABA";
println!("match result {:?}", match_several!(b'P', 4, b' ', 5, b'A', 12));

Upvotes: 2

Views: 394

Answers (2)

red75prime
red75prime

Reputation: 3861

Repetitive patterns in this case could be abstracted as somewhat useful functions.

#[inline]
fn all_match(s: &[u8], pattern: &[(u8, usize)]) -> bool {
    pattern.into_iter().all(|&(b, p)| s.get(p) == Some(&b))
}

#[inline]
fn any_match(s: &[u8], pattern: &[(u8, usize)]) -> bool {
    pattern.into_iter().any(|&(b, p)| s.get(p) == Some(&b))
}

fn f(s: &[u8]) {
    let and_pattern = [(b'A', 5), (b'X', 16)];
    let or_pattern = [(b'B', 17), (b'C', 18)];
    if all_match(s, &and_pattern) 
       && any_match(s, &or_pattern) {
    } else if all_match(s, &[(b'1', 4), (b'2', 7), (b'D', 10)]) {
    }
}

Upvotes: 3

DK.
DK.

Reputation: 59125

Change the macro to:

macro_rules! match_several {
    ($($ch:expr, $pos:expr),*) => {{
        $(Some(&$ch) == line.get($pos))&&*
    }}
}

Note that I'm using && as the separator, and I got rid of $last_ch and $last_pos. This works because you can use any token other than + or * as a separator, and && is a single token.

Upvotes: 4

Related Questions