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

使用Nom解析递归枚举遇栈溢出问题,求正确实现方式

解决Nom递归解析算术表达式的栈溢出问题

问题根源

你的解析函数形成了无限递归调用链:parse_arithexp优先调用parse_ae_add,而parse_ae_add又直接调用parse_arithexp。当解析像"3"这样的常量时,这个递归链会无限循环,直到栈被撑爆。


修复方案一:用循环替代递归处理加法(推荐)

把表达式拆分为原子表达式(仅常量)和加法组合表达式,用循环处理连续的加法操作,彻底避免递归栈溢出:

use nom::{
    character::complete::{digit1, multispace0},
    combinator::terminated,
    multi::many0,
    bytes::complete::tag,
    sequence::pair,
    IResult,
};
use std::rc::Rc;

#[derive(Debug, PartialEq, Eq)]
enum ArithExp {
    Const(i32),
    Add(Rc<ArithExp>, Rc<ArithExp>),
}

// 解析常量表达式
fn parse_ae_const(input: &str) -> IResult<&str, ArithExp> {
    let (input, numb) = terminated(digit1, multispace0)(input)?;
    Ok((input, ArithExp::Const(numb.parse::<i32>().unwrap())))
}

// 解析原子表达式:目前仅包含常量
fn parse_atom(input: &str) -> IResult<&str, ArithExp> {
    parse_ae_const(input)
}

// 解析加法表达式:先解析一个原子,再循环处理后续的 "+ 原子"
fn parse_add_expr(input: &str) -> IResult<&str, ArithExp> {
    let (input, mut current_exp) = parse_atom(input)?;
    let (input, additions) = many0(pair(tag(" + "), parse_atom))(input)?;
    
    for (_, next_exp) in additions {
        current_exp = ArithExp::Add(Rc::new(current_exp), Rc::new(next_exp));
    }
    
    Ok((input, current_exp))
}

// 顶层解析函数:直接调用加法表达式解析
fn parse_arithexp(input: &str) -> IResult<&str, ArithExp> {
    parse_add_expr(input)
}

#[test]
fn arithexp1() {
    assert_eq!(parse_ae_const("3"), Ok(("", ArithExp::Const(3))));
}

#[test]
fn arithexp2() {
    assert_eq!(parse_arithexp("3"), Ok(("", ArithExp::Const(3))));
}

#[test]
fn arithexp3() {
    assert_eq!(
        parse_arithexp("3 + 5"),
        Ok((
            "",
            ArithExp::Add(Rc::new(ArithExp::Const(3)), Rc::new(ArithExp::Const(5)))
        ))
    );
}

#[test]
fn arithexp4() {
    assert_eq!(
        parse_arithexp("3 + 5 + 7"),
        Ok((
            "",
            ArithExp::Add(
                Rc::new(ArithExp::Add(Rc::new(ArithExp::Const(3)), Rc::new(ArithExp::Const(5)))),
                Rc::new(ArithExp::Const(7))
            )
        ))
    );
}

这种方式不仅解决了栈溢出,还天然支持连续加法,符合算术表达式的常规语义,执行效率也更高。


修复方案二:用Nom的recursive组合子处理递归

如果需要处理更复杂的嵌套表达式(比如带括号的结构),可以用Nom提供的recursive组合子延迟递归调用,避免栈溢出:

use nom::{
    branch::alt,
    bytes::complete::tag,
    character::complete::{digit1, multispace0},
    combinator::{terminated, recursive},
    sequence::separated_pair,
    IResult,
};
use std::rc::Rc;

#[derive(Debug, PartialEq, Eq)]
enum ArithExp {
    Const(i32),
    Add(Rc<ArithExp>, Rc<ArithExp>),
}

fn parse_ae_const(input: &str) -> IResult<&str, ArithExp> {
    let (input, numb) = terminated(digit1, multispace0)(input)?;
    Ok((input, ArithExp::Const(numb.parse::<i32>().unwrap())))
}

// 调整加法解析函数,接收递归解析器作为参数
fn parse_ae_add(input: &str, parser: impl Fn(&str) -> IResult<&str, ArithExp>) -> IResult<&str, ArithExp> {
    let (input, (exp1, exp2)) = terminated(
        separated_pair(parser, tag(" + "), parser),
        multispace0,
    )(input)?;
    Ok((input, ArithExp::Add(Rc::new(exp1), Rc::new(exp2))))
}

// 用recursive包装递归逻辑,延迟求值避免栈溢出
fn parse_arithexp(input: &str) -> IResult<&str, ArithExp> {
    recursive(|parser| {
        alt((
            |i| parse_ae_add(i, parser),
            parse_ae_const,
        ))(input)
    })
}

#[test]
fn arithexp1() {
    assert_eq!(parse_ae_const("3"), Ok(("", ArithExp::Const(3))));
}

#[test]
fn arithexp2() {
    assert_eq!(parse_arithexp("3"), Ok(("", ArithExp::Const(3))));
}

#[test]
fn arithexp3() {
    assert_eq!(
        parse_arithexp("3 + 5"),
        Ok((
            "",
            ArithExp::Add(Rc::new(ArithExp::Const(3)), Rc::new(ArithExp::Const(5)))
        ))
    );
}

这种方式适合扩展到更复杂的语法,但相比循环方式有一定的递归栈开销。


内容的提问来源于stack exchange,提问作者cptkidd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:57:02