如何从Nom的many0中传播失败上下文信息?
问题分析
你的Nom解析器遇到关键词时错误信息丢失,核心原因是many0的行为特性:当子解析器返回**可恢复的Err::Error时,它会停止解析并返回已成功解析的结果;当子解析器返回不可恢复的Err::Failure**时,本应直接传递该错误,但在你的代码中,后续的eof检查失败覆盖了原始错误,导致上下文信息丢失。
解决方案
以下是几种简洁有效的修复方式:
方案1:用all_consuming替代手动eof检查
all_consuming组合子会强制整个输入被完全解析,同时优先保留子解析器产生的错误,不会被末尾的eof检查错误覆盖。
修改后的parse函数:
use nom::combinator::all_consuming; fn parse(s: &str) -> ParseResult<Vec<&str>> { all_consuming(many0(label))(s) }
方案2:让label返回Error类型错误,结合many_till
如果需要保留手动eof检查逻辑,可以将label中的fail()替换为返回Error类型错误,再用many_till组合子绑定解析器与eof检查,确保捕获子解析器的错误。
修改后的代码:
use nom::error::make_error; use nom::multi::many_till; fn label(s1: &str) -> ParseResult<&str> { let (s2, a) = take_while1(|c: char| c.is_alphabetic())(s1)?; let (s3, _) = take_while(|c: char| c.is_whitespace())(s2)?; if a == "derp" { return Err(nom::Err::Error(make_error( s1, nom::error::ErrorKind::Custom(0), ))); } Ok((s3, a)) } fn parse(s: &str) -> ParseResult<Vec<&str>> { let (s, (labels, _)) = many_till(label, eof)(s)?; Ok((s, labels)) }
方案3:手动确保Failure错误被正确传递
如果坚持使用fail()和context,可以手动构建VerboseError并添加上下文,确保Failure错误不被many0忽略:
fn label(s1: &str) -> ParseResult<&str> { let (s2, a) = take_while1(|c: char| c.is_alphabetic())(s1)?; let (s3, _) = take_while(|c: char| c.is_whitespace())(s2)?; if a == "derp" { return Err(nom::Err::Failure( VerboseError::from_error_kind(s1, nom::error::ErrorKind::Fail) .add_context(s1, "this message is lost"), )); } Ok((s3, a)) }
效果验证
使用方案1修改后,解析"foo bar herp derp"会返回包含上下文的原始错误:
Err(Failure(VerboseError { errors: [("derp", Context("this message is lost")), ("derp", Nom(Fail))] }))
内容的提问来源于stack exchange,提问作者sshine
相关产品推荐
相关产品推荐

