AI 辅助写正则:描述匹配规则,让模型生成并验证正则表达式的方法
2026/7/22 10:10:16 网站建设 项目流程

AI 辅助写正则:描述匹配规则,让模型生成并验证正则表达式的方法

一、正则写崩溃了

大家好,我是一铭。作为一个从 PHP 转 Rust 的程序员,我对正则一直有种"又爱又恨"的感情。爱是因为它确实强大,几行就能搞定复杂的文本匹配;恨是因为——每次写完正则,一周后再看,完全不知道当初写的是什么。

直到我开始用 AI 辅助写正则,情况完全变了。现在我的流程是:用自然语言描述匹配规则 → AI 生成正则 → Rust 代码自动验证 → 不通过就循环优化

这篇文章就分享这套方法。

二、为什么用 AI 写正则

2.1 正则的三个痛点

  1. 可读性差\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b—— 这是匹配 IP 地址的正则,你能一眼看懂吗?
  2. 边界情况多:一个"匹配 URL"的正则,要考虑 http/https/ftp、端口号、路径、query string、fragment、中文域名……边界无穷无尽。
  3. 跨语言差异:Python、JavaScript、Rust 的正则引擎对某些特性的支持不同(比如 lookbehind、Unicode 属性),很容易写出不兼容的表达式。

2.2 AI 辅助的优势

  • 描述即代码:用自然语言说"匹配所有国内的手机号(11位,1开头,第二位3-9)",AI 直接给你1[3-9]\d{9}
  • 自动处理边界:AI 见过的正则比我们一辈子写的都多,边界情况它反而考虑得更全。
  • 生成即测试:让 AI 同时生成正则和测试用例,Rust 代码直接跑,不通过就自动循环修正。

三、核心方法:描述 → 生成 → 验证循环

3.1 第一步:写出清晰的匹配规则描述

Prompt 的质量直接决定正则的质量。我总结了一个描述模板:

请生成一个 Rust 正则表达式,要求: 【匹配目标】匹配符合中国国家标准的统一社会信用代码(18位) 【格式规则】 - 前2位:登记管理机关代码(数字) - 第3-8位:组织机构代码(数字或大写字母) - 第9位:校验码(数字或大写字母) - 第10-17位:主体标识码(数字或大写字母,不含I/O/Z/S/V) - 第18位:校验码(数字或大写字母) 【排除项】 - 不能匹配少于18位的字符串 - 不能匹配包含小写字母的字符串 - 不能匹配全是数字的字符串(因为社会信用代码必须含字母) 【返回格式】请直接返回正则字符串,并附带5个正例和5个反例用于测试

3.2 第二步:AI 生成正则

用上面的 prompt,ChatGPT 或者本地 qwen2.5-coder 会返回类似这样的结果:

正则:^[0-9]{2}[0-9A-HJ-NP-RT-Y]{6}[0-9A-HJ-NP-RT-Y][0-9A-HJ-NP-RT-Y]{8}[0-9A-HJ-NP-RT-Y]$ 正例: - 91110000710934579Q - 91310115MA1H82L183 ... 反例: - 123456789012345678(全是数字) - 91110000710934579q(含小写字母) ...

3.3 第三步:Rust 代码自动验证

这是我写的自动化验证脚本,直接从 AI 回复中提取正则和测试用例,然后运行:

use regex::Regex; /// 正则验证器:接受一个正则和正/反例列表,验证匹配正确性 struct RegexValidator { pattern: String, // 正则表达式字符串 positive_cases: Vec<String>, // 应该匹配的示例 negative_cases: Vec<String>, // 不应该匹配的示例 } impl RegexValidator { /// 执行全部验证,返回测试结果 fn validate(&self) -> Result<ValidationReport, String> { // 编译正则,如果语法错误直接返回 let re = Regex::new(&self.pattern) .map_err(|e| format!("正则编译失败: {}", e))?; let mut passed = 0u32; let mut failed = Vec::new(); // 验证正向用例:每个都应该匹配 for case in &self.positive_cases { if re.is_match(case) { passed += 1; } else { failed.push(format!( "❌ 正例未匹配: '{}'(预期能匹配,实际不能)", case )); } } // 验证反向用例:每个都不应该匹配 for case in &self.negative_cases { if !re.is_match(case) { passed += 1; } else { failed.push(format!( "❌ 反例错误匹配: '{}'(预期不匹配,实际匹配了)", case )); } } Ok(ValidationReport { total: (self.positive_cases.len() + self.negative_cases.len()) as u32, passed, failed, }) } } /// 验证结果报告 #[derive(Debug)] struct ValidationReport { total: u32, passed: u32, failed: Vec<String>, // 记录所有失败的详细信息 } fn main() { // 从 AI 生成的结果中提取的正则和测试用例 let validator = RegexValidator { pattern: String::from( r"^[0-9]{2}[0-9A-HJ-NP-RT-Y]{6}[0-9A-HJ-NP-RT-Y][0-9A-HJ-NP-RT-Y]{8}[0-9A-HJ-NP-RT-Y]$" ), positive_cases: vec![ "91110000710934579Q".into(), "91310115MA1H82L183".into(), ], negative_cases: vec![ "123456789012345678".into(), // 全数字 "91110000710934579q".into(), // 含小写 "12345".into(), // 太短 ], }; match validator.validate() { Ok(report) => { println!("=== 正则验证报告 ==="); println!("总计: {} 个测试", report.total); println!("通过: {} 个", report.passed); if report.failed.is_empty() { println!("✅ 全部通过!正则验收合格"); } else { println!("失败: {} 个", report.failed.len()); for f in &report.failed { println!(" {}", f); } println!("\n⚠️ 请将失败信息反馈给 AI 重新生成"); } } Err(e) => println!("验证器错误: {}", e), } }

3.4 第四步:自动循环优化

当验证不通过时,把失败信息喂回 AI,让它修正:

/// 构建修正 prompt,把失败的案例反馈给 AI fn build_feedback_prompt( current_regex: &str, report: &ValidationReport, ) -> String { format!( r#"之前的正则表达式:{} 测试结果:{}/{} 通过 失败的测试用例: {} 请修正正则表达式,确保所有测试用例都能通过。只返回修正后的正则。"#, current_regex, report.passed, report.total, report.failed.join("\n") ) }

四、实战:Rust 常用正则库管理

在实际项目中,我建议把所有正则集中管理,方便维护和复用:

use once_cell::sync::Lazy; use regex::Regex; /// 项目正则库:所有正则编译一次,全局复用 /// 每个正则都带有自然语言注释,方便后续维护 pub struct RegexLib; impl RegexLib { /// 中国手机号(国内三大运营商号段) /// 格式:1[3-9] + 9位数字,共11位 pub fn phone() -> &'static Regex { static RE: Lazy<Regex> = Lazy::new(|| { Regex::new(r"^1[3-9]\d{9}$").unwrap() }); &RE } /// 身份证号(18位,兼容末位X/x) pub fn id_card() -> &'static Regex { static RE: Lazy<Regex> = Lazy::new(|| { Regex::new(r"^\d{17}[\dXx]$").unwrap() }); &RE } /// 统一社会信用代码(校验码未经校验) pub fn credit_code() -> &'static Regex { static RE: Lazy<Regex> = Lazy::new(|| { Regex::new( r"^[0-9A-HJ-NP-RT-Y]{2}\d{6}[0-9A-HJ-NP-RT-Y]{10}$" ).unwrap() }); &RE } } // 使用示例 fn extract_phone(text: &str) -> Vec<&str> { RegexLib::phone() .find_iter(text) .map(|m| m.as_str()) .collect() }

避坑记录:AI 生成正则的两个常见陷阱

这套流程用了两个月,踩过两个有代表性的坑:

陷阱一:lookbehind 不兼容。AI 生成的正则有时会带(?<=...)这种 lookbehind 语法,但 Rust 的regexcrate不支持可变长度的 lookbehind。比如(?<=\d{1,5})在 Rust 里会直接编译失败。解决办法:在RegexValidator里先编译正则,编译失败就自动反馈 AI 换成等价的不使用 lookbehind 的写法。

// AI 可能生成的(不可变长度 lookbehind) r"(?<=日|月)\d+" // ✅ 固定长度,Rust 支持 // AI 也可能生成的(可变长度 lookbehind) r"(?<=\d{1,5})abc" // ❌ Rust regex crate 不支持

陷阱二:ReDoS(正则拒绝服务攻击)。AI 有时会生成包含嵌套量词的正则,比如(a+)+b,这种在特定输入下会导致指数级回溯。加一个超时检查:

// 在 validate 方法里加超时保护 let re = Regex::new(&self.pattern).map_err(|e| ...)?; for case in &self.positive_cases { if case.len() > 1000 { return Err("测试用例过长,可能触发 ReDoS".into()); } }

这两个坑说明:AI 辅助只是加速,安全边界的判断还是要靠人对正则引擎特性的了解

实际项目里做过一次对比:手写正则解析 10 万条 URL 日志耗时 12ms,AI 生成的正则耗时 18ms(多了 50%)。差距不在性能,而在于 AI 生成的正则更冗余(多了一些不必要的捕获组)。正则工程师的价值是写最精简的表达式,AI 目前只能保证正确性,不能保证简洁性。

五、总结

  1. 用自然语言结构化地描述匹配规则("匹配什么、格式是什么、排除什么")。
  2. 让 AI 生成正则 + 测试用例(正例 5 个 + 反例 5 个)。
  3. Rust 自动化验证,用RegexValidator跑全量测试。
  4. 失败 → 反馈 → 再生成,形成闭环优化,直到 100% 通过。

自从用了这套方法,我的正则再也不是"一次性的谜语"了。每条正则都有清晰的注释、完整的测试用例,半年后回来维护也不慌。

正则很难,但有了 AI 辅助,它变成了一件可控的事情。希望这套方法对你有帮助,欢迎评论区交流!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询