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

1sbrub3j  于 2023-06-23  发布在  其他
关注(0)|答案(3)|浏览(172)

我已经用nom创建了几个重要的解析器,所以现在我对它非常熟悉。到目前为止,我创建的所有解析器总是向解析器提供整个输入切片。
我想创建一个流解析器,我假设这意味着我可以继续向解析器提供字节,直到它完成。我很难找到任何说明这一点的文档或示例,而且我还质疑我对“流解析器”是什么的假设。
我的问题是:

  • 我对什么是流解析器的理解是正确的吗?
  • 如果有,有没有使用这种技术的解析器的好例子?
oxalkeyp

oxalkeyp1#

nom解析器既不维护一个缓冲区来提供更多的数据,也不维护以前需要更多字节的“状态”。
但是如果你看一下IResult结构,你会发现你可以返回一个部分的结果或者指出你需要更多的数据。
似乎提供了一些结构来处理流:我认为你应该使用consumer_from_parser!宏从解析器创建一个Consumer,为你的数据源实现一个Producer,然后调用run直到它返回None(当你有更多的数据时再开始)。到目前为止,示例和文档似乎大部分都丢失了-参见https://github.com/Geal/nom的底部:)
此外,看起来nom中的大多数函数和宏在到达输入结束时的行为都没有很好的文档记录(或根本没有)。例如,如果输入的长度不足以包含要查找的substr,则take_until!返回Incomplete,但如果输入的长度足够长但不包含substr,则返回错误。
另外,nom主要使用&[u8]&str作为输入;你不能通过这些类型发出实际的“流结束”信号。您可以实现自己的输入类型(相关traits:nom::{AsBytes,Compare,FindSubstring,FindToken,InputIter,InputLength,InputTake,Offset,ParseTo,Slice})添加一个“reached end of stream”标志,但是nom提供的宏和函数将无法解释它。
总而言之,我建议通过一些其他方式将流输入拆分为可以使用简单的非流解析器处理的块(甚至可以使用synom而不是nom)。

huwehgph

huwehgph2#

下面是一个最小的工作示例。正如@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(())
}
h43kikqp

h43kikqp3#

据我所知,nom的架构自从最初提出这个问题以来已经发生了变化(这就是为什么接受的答案对我来说不起作用)。
我也在为同样的问题挣扎,我发现的越多,我就越意识到这不是那么简单和直接的。
我写了a blog post关于我的发现。简而言之,它可以归结为以下几个步骤:

  • 创建一些数据流的东西(一个迭代器,当从文件读取字节时提供字节)
  • 决定你想要流出来的内容(通常是“日志行”或“视频帧”等)。
  • 构建一个迭代器来输出这些东西。在iterator-struct中,跟踪未解析的数据。在next()函数中,首先查看解析器是否会根据当前未解析的数据进行解析。如果它返回一个Err(Err::Incomplete(_)),添加更多的数据并重试,直到它返回一个在迭代器中返回的对象。

请参阅the blog postthis GitHub repo了解更多信息。

相关问题