Rust中获取导致指定文本模式匹配失败的首个字符索引的便捷方法
核心结论
标准regex库无法直接实现你的需求,捕获组仅在全匹配成功时生成,也没有内置API返回匹配失败的精准位置,这是因为正则的NFA匹配逻辑为了性能优化,不会记录匹配过程中的最长有效前缀终止位置。
可行方案
方案1:拆分正则为递进匹配段
你可以把原本的完整正则拆分为多个前缀匹配的小正则,逐段匹配并记录当前游标位置,哪一段匹配失败,当前游标就是错误位置。
比如长选项^--(\w+)=(\w+)$可以拆分为:
- 匹配开头
^--:失败则错误位置为0/1 - 匹配
^\w+:失败则错误位置为2 - 匹配
^=:失败则错误位置为2 + 匹配到的key长度 - 匹配
^\w+$:失败则错误位置为2 + key长度 +1
这种方案不需要完全放弃正则,适合规则简单的场景。
方案2:手写迷你线性解析器(更推荐)
你的场景规则非常简单,仅需要校验短选项/长选项格式,手写线性遍历的解析器代码量很小,且可以完全自定义错误信息和错误位置,灵活度远高于正则。
示例代码如下:
#[derive(Debug)] enum ParseError { /// 错误位置,错误字符,错误描述 InvalidCharAt(usize, char, &'static str), /// 意外结束位置,期望内容 UnexpectedEnd(usize, &'static str), } fn parse_option(s: &str) -> Result<(), ParseError> { let mut chars = s.char_indices().peekable(); // 校验开头必须为- match chars.next() { Some((0, '-')) => {}, Some((idx, c)) => return Err(ParseError::InvalidCharAt(idx, c, "选项必须以'-'开头")), None => return Err(ParseError::UnexpectedEnd(0, "期望输入选项")), } match chars.peek() { // 长选项逻辑 Some((_, '-')) => { chars.next(); let mut key_valid = false; // 校验key部分 while let Some((_, c)) = chars.peek() { if c.is_alphanumeric() || *c == '_' { key_valid = true; chars.next(); } else if *c == '=' { break; } else { let (idx, bad_c) = chars.next().unwrap(); return Err(ParseError::InvalidCharAt(idx, bad_c, "长选项key仅支持字母、数字、下划线,后续需接'='")); } } if !key_valid { return Err(ParseError::InvalidCharAt(2, s.chars().nth(2).unwrap(), "--后必须填写长选项key")); } // 校验= match chars.next() { Some((_, '=')) => {}, Some((idx, c)) => return Err(ParseError::InvalidCharAt(idx, c, "长选项key后必须接'='和value")), None => return Err(ParseError::UnexpectedEnd(s.len(), "长选项key后缺少'='和value")), } // 校验value部分 let mut val_valid = false; while let Some((_, c)) = chars.peek() { if c.is_alphanumeric() || *c == '_' { val_valid = true; chars.next(); } else { let (idx, bad_c) = chars.next().unwrap(); return Err(ParseError::InvalidCharAt(idx, bad_c, "长选项value仅支持字母、数字、下划线")); } } if !val_valid { return Err(ParseError::UnexpectedEnd(s.len(), "'='后必须填写长选项value")); } Ok(()) } // 短选项逻辑 Some((_, c)) if c.is_alphanumeric() || *c == '_' => { chars.next(); while let Some((idx, c)) = chars.next() { if !c.is_alphanumeric() && c != '_' { return Err(ParseError::InvalidCharAt(idx, c, "短选项仅支持字母、数字、下划线")); } } Ok(()) } Some((idx, c)) => { Err(ParseError::InvalidCharAt(*idx, *c, "'-'后需接选项名或第二个'-'")) } None => { Err(ParseError::UnexpectedEnd(1, "'-'后缺少选项名")) } } }
拿到ParseError后就可以按照你的需求格式化输出诊断信息,比如输入---abc时会返回InvalidCharAt(2, '-', "--后必须填写长选项key"),直接取错误位置即可生成你示例中的提示。
内容的提问来源于stack exchange,提问作者CaffeinateOften
相关产品推荐
相关产品推荐

