如何基于迭代器实现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()是Parsertrait提供的方法,可将任意解析器转换为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
相关产品推荐
相关产品推荐

