如何用nom库解析匹配分隔符?优化多格式YMD日期解析代码
用nom优雅实现多格式YMD日期解析
嘿,你这个需求刚好能用上nom里几个非常好用的组合子来优化!之前的手动if let分支确实有点繁琐,我来给你分享几个更符合nom设计哲学的实现方式,既简洁又易维护:
方案1:用cond组合子处理条件匹配(推荐)
nom的cond组合子天生就是用来处理“满足某个条件才执行解析器”的场景,完美适配你这种「如果匹配到第一个分隔符,就必须匹配相同的第二个分隔符」的需求,还能自动保证分支类型一致,不用手动构造占位符:
use nom::{ character::complete::{digit1, one_of}, combinator::{map_res, opt, value}, sequence::tuple, IResult, branch::cond, }; // 假设你的DateType定义是这样的 #[derive(Debug, PartialEq)] enum DateType { YMD { year: u32, month: u32, day: u32 }, } fn parse_ymd(i: &[u8]) -> IResult<&[u8], DateType> { // 先封装年、月、日的基础解析器 let parse_num = |input: &[u8]| map_res(digit1, |s: &[u8]| { String::from_utf8_lossy(s).parse() })(input); let (i, year) = parse_num(i)?; // 捕获可选的分隔符 let (i, sep) = opt(one_of(".-/"))(i)?; let (i, month) = parse_num(i)?; // 用cond:如果有分隔符就匹配相同的,否则啥也不做 let (i, _) = cond( sep.is_some(), // 把字符转成字节数组的tag解析器,没有分隔符就用空tag sep.map(|c| tag(&[c as u8])).unwrap_or(value((), tag(""))) )(i)?; let (i, day) = parse_num(i)?; Ok(( i, DateType::YMD { year, month, day } )) }
这里的cond会根据sep.is_some()的布尔值来决定是否执行分隔符匹配,完全替代了手动的分支判断,代码逻辑更紧凑。
方案2:用alt枚举所有格式(可读性拉满)
如果你不想处理条件逻辑,也可以直接用alt组合子把所有可能的格式列出来,这种方式虽然代码稍长,但可读性极强,后期维护起来超方便:
use nom::{ character::complete::{digit1, tag}, combinator::map_res, sequence::tuple, IResult, branch::alt, }; #[derive(Debug, PartialEq)] enum DateType { YMD { year: u32, month: u32, day: u32 }, } fn parse_ymd(i: &[u8]) -> IResult<&[u8], DateType> { let parse_num = |input: &[u8]| map_res(digit1, |s: &[u8]| { String::from_utf8_lossy(s).parse() })(input); // 分别定义四种格式的解析器 let no_sep = tuple((parse_num, parse_num, parse_num)) .map(|(y, m, d)| DateType::YMD { y, m, d }); let dot_sep = tuple((parse_num, tag("."), parse_num, tag("."), parse_num)) .map(|(y, _, m, _, d)| DateType::YMD { y, m, d }); let dash_sep = tuple((parse_num, tag("-"), parse_num, tag("-"), parse_num)) .map(|(y, _, m, _, d)| DateType::YMD { y, m, d }); let slash_sep = tuple((parse_num, tag("/"), parse_num, tag("/"), parse_num)) .map(|(y, _, m, _, d)| DateType::YMD { y, m, d }); // 用alt匹配任意一种格式 alt((no_sep, dot_sep, dash_sep, slash_sep))(i) }
这种方式把每种格式都清晰地列出来,就算是不熟悉nom的人也能一眼看懂解析逻辑,尤其适合格式数量不多的场景。
为什么这些方案更优?
- 完全遵循nom的组合子优先设计理念,避免了手动控制流,让解析逻辑更贴近声明式风格。
- 利用nom内置的组合子处理条件和分支,代码更简洁,也减少了手动构造占位符(比如你之前用的
&[' ' as u8][..])带来的潜在问题。 - 两种方案各有侧重:
cond适合需要复用匹配结果的场景,alt则胜在可读性和维护性。
内容的提问来源于stack exchange,提问作者Argentumbolo
相关产品推荐
相关产品推荐

