You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 17:12:39