如何用JavaScript RegExp实现ANTLR4中循环依赖的call与expr规则?
用JavaScript正则实现带循环依赖的ANTLR4规则校验方案
问题背景
我有一份ANTLR4语法文件,其中call和expr规则存在循环依赖,定义如下:
call : ID LPAREN (expr (COMMA expr)*)? RPAREN ; expr : operand | call | NOT expr | expr (AND|OR|ADD|SUB|MUL|DIV) expr | expr LSQUARE expr RSQUARE ;
这里call引用expr,expr也引用call。我想用JavaScript RegExp实现这类字符串校验,同时需要为expr规则实现递归正则。尝试过以下方法但都不行:
- 在正则中使用
(?R)?,但JavaScript RegExp不支持该语法; - 使用XRegExp.matchRecursive,不符合需求;
- 自定义RecursiveRegExp类重写构造器和exec()方法,但能力不足没能实现。
想问:能不能在正则字面量中插入指向另一个RegExp的“链接”,或者有其他可行方案?
可行方案说明
JavaScript原生正则本身不支持直接引用其他正则实例,也没有原生递归语法,但可以通过手动展开递归逻辑+分步校验的方式实现需求,核心思路是把递归结构拆解为可迭代的层级,结合正则和代码逻辑配合完成校验。
1. 先定义基础原子正则
先把语法里的基础标识符和符号转成正则片段:
// 基础符号与原子(需根据实际语法调整符号匹配) const ID = /[a-zA-Z_][a-zA-Z0-9_]*/; const LPAREN = /\(/; const RPAREN = /\)/; const COMMA = /,/; const NOT = /!/; const OPERATORS = /(&&|\|\||\+|-|\*|\/)/; // 对应AND/OR/ADD/SUB/MUL/DIV const LSQUARE = /\[/; const RSQUARE = /\]/; // operand假设是ID或字面量,这里先简化为ID,可按需扩展 const operand = new RegExp(ID.source);
2. 递归函数+正则片段结合校验
由于expr和call互相依赖,可以写递归函数,结合动态生成的正则片段完成校验:
// 生成expr的正则片段(用于嵌套匹配) function getExprPattern() { const parts = [ operand.source, `(${ID.source}${LPAREN.source}((${getExprPattern().source})(${COMMA.source}${getExprPattern().source})*)?${RPAREN.source})`, `${NOT.source}(${getExprPattern().source})`, `(${getExprPattern().source})${OPERATORS.source}(${getExprPattern().source})`, `(${getExprPattern().source})${LSQUARE.source}(${getExprPattern().source})${RSQUARE.source}` ]; return new RegExp(`(?:${parts.join('|')})`); } const exprPattern = getExprPattern(); function isValidExpr(str) { // 基础情况:operand if (operand.test(str)) return true; // 匹配call结构:ID( expr(,expr)* ) const callPattern = new RegExp( `^${ID.source}${LPAREN.source}((${exprPattern.source})(${COMMA.source}${exprPattern.source})*)?${RPAREN.source}$` ); if (callPattern.test(str)) return true; // 匹配NOT expr结构 const notPattern = new RegExp(`^${NOT.source}(.+)$`); const notMatch = str.match(notPattern); if (notMatch) return isValidExpr(notMatch[1]); // 匹配二元运算:expr OP expr // 注:这里未处理运算符优先级,如需精确匹配需结合优先级调整拆分逻辑 const opMatch = str.match(new RegExp(`^(.+)${OPERATORS.source}(.+)$`)); if (opMatch) return isValidExpr(opMatch[1]) && isValidExpr(opMatch[3]); // 匹配数组访问:expr[expr] const arrayMatch = str.match(new RegExp(`^(.+)${LSQUARE.source}(.+)${RSQUARE.source}$`)); if (arrayMatch) return isValidExpr(arrayMatch[1]) && isValidExpr(arrayMatch[2]); return false; }
该方案可以处理无限深度的递归,适合复杂场景,但需要额外处理括号分组、运算符优先级等细节。
3. 预展开递归层级生成正则
如果对递归深度有预期(比如最多3-5层),可以预先展开N层递归,生成一个大的正则:
function buildExprRegex(levels = 3) { let expr = operand.source; for (let i = 0; i < levels; i++) { const call = `${ID.source}${LPAREN.source}((${expr})(${COMMA.source}${expr})*)?${RPAREN.source}`; const not = `${NOT.source}(${expr})`; const binary = `(${expr})${OPERATORS.source}(${expr})`; const array = `(${expr})${LSQUARE.source}(${expr})${RSQUARE.source}`; expr = `(?:${[expr, call, not, binary, array].join('|')})`; } return new RegExp(`^${expr}$`); } // 生成支持3层递归的校验正则 const exprRegex = buildExprRegex(3); // 示例校验 console.log(exprRegex.test("foo(bar[baz+1])")); // 返回true
这种方案是纯正则校验,性能较好,但无法处理超出预定义层级的递归结构。
关于正则字面量引用其他RegExp的问题
JavaScript原生正则不支持在字面量中直接引用另一个RegExp实例,但可以通过拼接正则的source属性实现类似效果,就像上面代码中${ID.source}的用法,把其他正则的源码片段拼接到新正则中。
内容的提问来源于stack exchange,提问作者akrick
相关产品推荐
相关产品推荐

