You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为nom v8.0.0的many0组合子实现自定义结构体作为输入?

如何为nom v8.0.0的many0组合子实现自定义结构体作为输入?

嘿,我来帮你搞定nom v8.0.0里many0组合子配合自定义Token结构体的问题!

首先得明确核心问题:nom的many0默认是处理&[u8]这类字节切片的,但你用了自定义的Token结构体和基于LocatedSpan的Span类型,要让many0能正常工作,关键是让你的输入类型(也就是Token的切片&[Token<'a>])实现nom组合子依赖的几个核心trait,比如InputLength和Slice。

我一步步给你拆解:

1. 先补全你的Token和Span定义

先把你之前截断的代码补全(应该是LocatedSpan<&'a str>对吧?),顺便加上一个示例的TokenType枚举:

use nom_locate::LocatedSpan;

// 先定义你的Token类型枚举,根据实际需求加变体
#[derive(Debug, Clone, PartialEq)]
pub enum TokenType<'a> {
    Ident(&'a str),
    Number(u32),
    Keyword(&'a str),
    // 其他你需要的token类型
}

#[derive(Debug, Clone, PartialEq)]
pub struct Token<'a> {
    pub token_type: TokenType<'a>,
    pub span: Span<'a>,
}

// 基于str的LocatedSpan作为位置信息
pub type Span<'a> = LocatedSpan<&'a str>;

2. 为Token切片实现nom需要的核心Trait

many0这类重复解析的组合子需要知道输入的长度,以及如何对输入进行切片,所以我们要为&[Token<'a>]实现InputLength和Slice:

实现InputLength

这个trait用来获取输入的长度,对于Token切片来说,长度就是元素的个数,直接用原生切片的len()就行:

use nom::input::InputLength;

impl<'a> InputLength for &[Token<'a>] {
    fn input_len(&self) -> usize {
        self.len()
    }
}

另外,单个Token也可以实现InputLength(如果需要的话),这里我们把每个Token视为长度为1的输入单元:

impl<'a> InputLength for Token<'a> {
    fn input_len(&self) -> usize {
        1
    }
}

实现Slice

这个trait用来对输入进行切片,比如解析完一个Token后,剩下的输入是原切片从第1个元素开始的部分。我们可以直接委托给原生切片的Slice实现:

use nom::input::Slice;

impl<'a, R> Slice<R> for &[Token<'a>]
where
    &'a [Token<'a>]: Slice<R>,
{
    fn slice(&self, range: R) -> Self {
        <&[Token<'a>] as Slice<R>>::slice(self, range)
    }
}

3. 写单个Token的解析器

接下来你需要一个能解析单个Token的解析器,比如我们写一个专门识别Ident类型Token的解析器:

use nom::{IResult, error::Error};

fn parse_ident<'a>(input: &[Token<'a>]) -> IResult<&[Token<'a>], Token<'a>, Error<&[Token<'a>]>> {
    // 检查输入的第一个Token是不是Ident类型
    match input.first() {
        Some(token) if matches!(token.token_type, TokenType::Ident(_)) => {
            // 解析成功,返回剩下的输入(从第1个元素之后)和当前Token的克隆
            Ok((&input[1..], token.clone()))
        }
        // 不匹配就返回错误
        _ => Err(nom::Err::Error(Error::new(input, nom::error::ErrorKind::Tag))),
    }
}

4. 用many0组合多个解析结果

现在就可以用many0来批量解析符合条件的Token了:

use nom::multi::many0;

fn parse_all_idents<'a>(input: &[Token<'a>]) -> IResult<&[Token<'a>], Vec<Token<'a>>, Error<&[Token<'a>]>> {
    many0(parse_ident)(input)
}

5. 测试一下效果

我们写个main函数测试一下:

fn main() {
    // 先构造几个测试用的Token
    let test_span = Span::new("foo 123 bar");
    let test_tokens = vec![
        Token { token_type: TokenType::Ident("foo"), span: test_span.clone() },
        Token { token_type: TokenType::Number(123), span: test_span.clone() },
        Token { token_type: TokenType::Ident("bar"), span: test_span },
    ];

    // 用我们的解析器解析
    match parse_all_idents(&test_tokens) {
        Ok((remaining_tokens, collected_idents)) => {
            println!("解析后剩下的Token: {:?}", remaining_tokens);
            println!("收集到的Ident类型Token: {:?}", collected_idents);
        }
        Err(e) => println!("解析出错啦: {:?}", e),
    }
}

运行这个代码,你会看到输出里剩下的是那个Number类型的Token,收集到的是foo和bar两个Ident Token,完全符合预期!

一些额外注意点

  • 如果你需要自定义错误类型,记得为你的输入类型实现nom的ParseError trait,这样错误信息会更贴合你的场景。
  • 如果你是从原始的LocatedSpan<&str>先解析成Token列表,再用many0处理,这是最常见的用法——nom适合先做词法分析生成Token流,再用组合子做语法分析。

备注:内容来源于stack exchange,提问作者Revanth Shalon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 17:03:05