6

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

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

我的问题是:

  • 我对流解析器的理解是否正确?
  • 如果是这样,有没有使用这种技术的解析器的好例子?
4

2 回答 2

3

nom解析器既不维护一个缓冲区来提供更多数据,也不维护他们以前需要更多字节的“状态”。

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

似乎提供了一些结构来处理流式传输:我认为您应该使用宏Consumer从解析器创建一个,为您的数据源实现一个,然后调用直到它返回(当您有更多数据时重新开始)。到目前为止,示例和文档似乎大部分都丢失了 - 请参阅https://github.com/Geal/nom的底部:)consumer_from_parser!ProducerrunNone

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

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

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

于 2017-10-22T21:15:47.763 回答
2

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

这是类似于 Unixhead命令的实现的其余部分。

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(())
}
于 2020-11-29T21:37:39.080 回答