如何在nom中实现"take while, but at most N characters"?

How to implement "take while, but at most N characters" in nom?

我如何给 nom 的 take_while 一个它应该匹配的字符数的上限?我想按一定条件取字符,但是最多N.

由于这将是解析器中性能非常关键的部分,我想避免使用一堆单独的 take(1usize)。部分原因是因为不得不一个接一个地处理单个元素切片感觉很尴尬,而且还因为编译器可能无法在编译时知道它们的大小必须为 1,即,它可能必须生成绑定检查或尺寸断言,对吗?

从概念上讲,带有上限的 take_while 感觉更合适。我希望我可以像这样自己使用可变循环变量进行计数:

let mut i = 0;

let (input, _) = take_while(|c| {
    i += 1;
    c >= 0x80 && i < 9
})(input)?;

事实上,我什至可以在闭包中提取必要的信息,这很可能会导致非常高效的代码生成。 (目标是实现某种 VarInt LSB 编码,即我可以更新局部可变变量 x = x + (if last_byte { c } else { c & 0x7f }) << 7。)

不幸的是 take_while 似乎只允许 Fn 而不是 FnMut 这可能意味着这是不可能的(为什么要限制?)。我还能做些什么来很好地实现它?

使用 Cell 使您的闭包具有内部可变性。然后它可以有可变状态,但仍然实现 Fn:

let i = Cell::new(0);

let (input, _) = take_while(|c| {
    i.set(i.get() + 1);
    c > 0x80 && i.get() < 9
})(input)?;

有个nom-function take_while_m_n:

const N: usize = ...

fn take_native(input: &[u8]) -> IResult<&[u8], &[u8]> {
    take_while_m_n(0, N, |c| c > 0x80)(input)
}

然而,使用快速基准测试,它似乎比 慢得多(或者基准优化错误,因为 Cell -version 只需要 1ns/iter,而 13ns/iter take_while_m_n).

#![feature(test)]
extern crate test;

use std::cell::Cell;

use nom::{
    bytes::complete::{take_while, take_while_m_n},
    IResult,
};

fn take_cell(input: &[u8]) -> IResult<&[u8], &[u8]> {
    let i = Cell::new(0);

    let (input, output) = take_while(|c| {
        i.set(i.get() + 1);
        c > 0x80 && i.get() < 5
    })(input)?;

    Ok((input, output))
}

fn take_native(input: &[u8]) -> IResult<&[u8], &[u8]> {
    take_while_m_n(0, 4, |c| c > 0x80)(input)
}

#[cfg(test)]
mod tests {
    use super::*;

    const INPUT: &[u8] = &[
        0x81, 0x82, 0x83, 0x84, 0x81, 0x82, 0x83, 0x84, 0x81, 0x82, 0x83, 0x84, 0x81, 0x82, 0x83,
        0x84, 0x81, 0x82, 0x83, 0x84, 0x81, 0x82, 0x83, 0x84, 0x81, 0x82, 0x83, 0x84,
    ];

    #[bench]
    fn bench_cell(b: &mut test::Bencher) {
        assert_eq!(take_cell(INPUT).unwrap().1, &[0x81, 0x82, 0x83, 0x84]);

        b.iter(|| take_cell(INPUT).unwrap());
    }

    #[bench]
    fn bench_native(b: &mut test::Bencher) {
        assert_eq!(take_native(INPUT).unwrap().1, &[0x81, 0x82, 0x83, 0x84]);

        b.iter(|| take_native(INPUT).unwrap());
    }
}