如何在 nom 中创建流解析器?

How do I create a streaming parser in nom?

我已经在 nom 中创建了一些重要的解析器,所以我现在对它非常熟悉。到目前为止,我创建的所有解析器始终将整个输入切片提供给解析器。

我想创建一个流式解析器,我假设这意味着我可以继续将字节输入解析器直到它完成。我很难找到任何说明这一点的文档或示例,而且我也质疑我对 "streaming parser" 是什么的假设。

我的问题是:

nom 解析器既不维护缓冲区以将更多数据输入,也不维护 "state" 以前需要更多字节的地方。

但是如果您查看 IResult 结构,您会发现您可以 return 部分结果或表明您需要更多数据。

似乎提供了一些结构来处理流式传输:我认为您应该使用 consumer_from_parser! macro, implement a Producer for your data source, and call run until it returns None (and start again when you have more data). Examples and docs seem to be mostly missing so far - see bottom of https://github.com/Geal/nom 从解析器创建一个 Consumer :)

此外,nom 中的大多数函数和宏似乎都没有很好地(或根本没有)记录它们在输入结束时的行为。例如 take_until! returns Incomplete 如果输入的长度不足以包含要查找的 substr,但如果输入 returns 则为错误足够长但不包含 substr.

此外,nom 主要使用 &[u8]&str 作为输入;您不能通过这些类型发出实际的 "end of stream" 信号。您可以实现自己的输入类型(相关特征:nom::{AsBytes,Compare,FindSubstring,FindToken,InputIter,InputLength,InputTake,Offset,ParseTo,Slice})以添加 "reached end of stream" 标志,但 nom 提供的宏和函数将无法解释它。

总而言之,我建议通过其他方式将流式输入拆分为可以使用简单的非流式解析器处理的块(甚至可以使用 synom 而不是 nom)。

这是一个最小的工作示例。正如@Stefan 所写,“我建议通过其他方式将流式输入拆分为您可以处理的块”。

有些工作(我很高兴提出有关如何改进它的建议)是组合 File::bytes() 方法,然后仅 take 必要的字节数并传递它们至 nom::streaming::take.

let reader = file.bytes();
let buf = reader.take(length).collect::<B>()?;
let (_input, chunk) = take(length)(&*buf)...; 

完整的函数如下所示:

/// Parse the first handful of bytes and return the bytes interpreted as UTF8
fn parse_first_bytes(file: std::fs::File, length: usize) -> Result<String> {
    type B = std::result::Result<Vec<u8>, std::io::Error>;
    let reader = file.bytes();

    let buf = reader.take(length).collect::<B>()?;
    let (_input, chunk) = take(length)(&*buf)
        .finish()
        .map_err(|nom::error::Error { input: _, code: _ }| eyre!("..."))?;
    let s = String::from_utf8_lossy(chunk);

    Ok(s.to_string())
}

这是 main 的其余部分,用于实现类似于 Unix 的 head 命令。

use color_eyre::Result;
use eyre::eyre;
use nom::{bytes::streaming::take, Finish};
use std::{fs::File, io::Read, path::PathBuf};
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(about = "A minimal example of parsing a file only partially. 
  This implements the POSIX 'head' utility.")]
struct Args {
    /// Input File
    #[structopt(parse(from_os_str))]
    input: PathBuf,
    /// Number of bytes to consume
    #[structopt(short = "c", default_value = "32")]
    num_bytes: usize,
}

fn main() -> Result<()> {
    let args = Args::from_args();
    let file = File::open(args.input)?;

    let head = parse_first_bytes(file, args.num_bytes)?;
    println!("{}", head);

    Ok(())
}