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

如何基于迭代器实现nom库中类似alt的动态解析器函数?

问题描述

我希望创建一个类似nom::alt的函数,它不从元组中取用解析器,而是从迭代器中取用解析器。

遇到的核心问题是参数化解析器的FnMut约束:Parser trait为FnMut实现,但未为Fn实现;且FnMut不可克隆,这意味着必须将其放入某种Box中,再进行可变借用。

最初设想的函数签名:

use nom::{Parser, error::ParseError};

fn alt<I, O, E, P>(parsers: impl IntoIterator<Item = P>) -> impl Parser<I, O, E>
where
    P: Parser<I, O, E>
{
    move |input| {
        // implementation
    }
}

我已有一个可行实现,但它仅在所有解析器都是可变引用时有效:

use nom::{error::ParseError, InputIter, InputTake, Parser};

pub fn alt_many<I, O, E, P>(mut parsers: Vec<&mut P>) -> impl Parser<I, O, E> + '_ 
where
    P: Parser<I, O, E>,
    I: InputIter + InputTake + Copy,
    E: ParseError<I>,
{
    move |mut input| {
        use nom::bytes::complete::take;
        use nom::combinator::fail;

        let mut value: Option<O> = None;

        for parser in &mut parsers {
            if let Ok((input_, o)) = parser.parse(input) {
                input = input_;
                value = Some(o);
                break;
            } else {
                (input, _) = take::<u8, I, E>(1)(input)?;
            };
        }

        value
            .map(|output| Ok((input, output)))
            .unwrap_or_else(|| fail::<I, O, E>(input))
    }
}

请问还有其他可行的解决方案吗?

可行解决方案

方案1:使用BoxedParser统一类型并处理可变性

nom提供了BoxedParser类型,可以将任意Parser装箱,统一迭代器中解析器的类型,同时内部处理FnMut的可变性问题,无需手动传递可变引用。

use nom::{Parser, error::ParseError, combinator::fail};

pub fn alt_iter<I, O, E, P>(parsers: impl IntoIterator<Item = P>) -> impl Parser<I, O, E>
where
    P: Parser<I, O, E> + 'static,
    I: Clone,
    E: ParseError<I>,
{
    let mut parsers: Vec<_> = parsers.into_iter().map(|p| p.boxed()).collect();
    
    move |input| {
        let mut last_err = None;
        let original_input = input.clone();
        
        for parser in &mut parsers {
            match parser.parse(input.clone()) {
                Ok(res) => return Ok(res),
                Err(err) => last_err = Some(err),
            }
        }
        
        last_err.unwrap_or_else(|| fail(original_input))
    }
}

说明:

  • boxed()是Parser trait提供的方法,可将任意解析器转换为BoxedParser
  • 对输入做clone,保证每个解析器都从初始位置尝试匹配,和nom::alt的行为一致
  • 收集所有解析器的错误,最终返回最后一个错误(可根据需求调整为合并错误逻辑)

方案2:手动用Pin<Box<dyn Parser>>实现动态分发

如果不想依赖BoxedParser,可以手动用Pin<Box<dyn Parser<I, O, E>>>装箱解析器,实现动态分发的同时处理FnMut的可变性要求。

use nom::{Parser, error::ParseError, combinator::fail};
use std::pin::Pin;

pub fn alt_dynamic<I, O, E>(parsers: impl IntoIterator<Item = impl Parser<I, O, E> + 'static>) -> impl Parser<I, O, E>
where
    I: Clone,
    E: ParseError<I>,
{
    let mut parsers: Vec<Pin<Box<dyn Parser<I, O, E>>>> = parsers
        .into_iter()
        .map(|p| Pin::new(Box::new(p)))
        .collect();
    
    move |input| {
        let mut last_err = None;
        let original_input = input.clone();
        
        for parser in &mut parsers {
            match parser.as_mut().parse(input.clone()) {
                Ok(res) => return Ok(res),
                Err(err) => last_err = Some(err),
            }
        }
        
        last_err.unwrap_or_else(|| fail(original_input))
    }
}

说明:

  • dyn FnMut需要被Pin固定,避免内部状态因移动失效,因此用Pin<Box<dyn Parser>>封装
  • 通过as_mut()获取可变的Pin<&mut dyn Parser>,调用parse方法

方案3:针对无状态解析器的克隆方案

如果解析器是无状态的(比如大部分nom内置组合子),可以要求解析器实现Clone,通过克隆实例来避免可变借用问题。

use nom::{Parser, error::ParseError, combinator::fail};

pub fn alt_stateless<I, O, E, P>(parsers: impl IntoIterator<Item = P>) -> impl Parser<I, O, E>
where
    P: Parser<I, O, E> + Clone,
    I: Clone,
    E: ParseError<I>,
{
    let parsers = parsers.into_iter().collect::<Vec<_>>();
    
    move |input| {
        let mut last_err = None;
        let original_input = input.clone();
        
        for parser in parsers.clone() {
            match parser.parse(input.clone()) {
                Ok(res) => return Ok(res),
                Err(err) => last_err = Some(err),
            }
        }
        
        last_err.unwrap_or_else(|| fail(original_input))
    }
}

说明:

  • 仅适用于无状态解析器,这类解析器能安全克隆
  • 每次循环使用独立的解析器实例,避免可变借用冲突

方案4:修正原始实现的语义与所有权

原始实现中错误地消耗了输入(take(1)),且依赖外部可变引用,可调整为直接接收解析器所有权,同时恢复alt的正确语义:

use nom::{error::ParseError, Parser, combinator::fail};

pub fn alt_many_fixed<I, O, E, P>(mut parsers: Vec<P>) -> impl Parser<I, O, E>
where
    P: Parser<I, O, E>,
    I: Clone,
    E: ParseError<I>,
{
    move |input| {
        let mut last_err = None;
        let original_input = input.clone();
        
        for parser in &mut parsers {
            match parser.parse(input.clone()) {
                Ok(res) => return Ok(res),
                Err(err) => last_err = Some(err),
            }
        }
        
        last_err.unwrap_or_else(|| fail(original_input))
    }
}

说明:

  • 直接接收Vec<P>,函数拥有解析器所有权,无需外部可变引用
  • 每个解析器尝试原始输入的克隆,符合alt的核心语义:所有解析器从同一初始位置尝试匹配

内容的提问来源于stack exchange,提问作者Wayne Van Son

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 07:05:11