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

ANTLR4解析器生成重复Group条目及语法异常捕获咨询

问题描述

需要解析以下逻辑表达式:

([LANGUAGE] IN ("Arabic", "Dutch") AND [Content Series] IN ("The Walking Dead") AND [PUBLISHER_NAME] IN ("Yahoo Search", "Yahoo! NAR") )  

OR 

([LANGUAGE] IN ("English") AND [PUBLISHER_NAME] IN ("Aol News", "Microsoft-Bing!") )

该表达式包含2个由OR分隔的Group,每个Group内是多个由AND分隔的TargetEntity(基础表达式)。使用指定的Exp.g4语法文件、Java监听者与解析器代码解析后,出现两个问题:

  1. 输出的GroupList末尾出现重复条目(问题Q1);
  2. 不知道如何在Java中捕获语法文件针对错误输入抛出的软异常(问题Q2)。

附相关文件及当前输出:

语法文件(Exp.g4)

grammar Exp;

options {
language = Java;
}

start
:  def EOF
;

def : (AND? base)+
|  (OR? '(' def ')')*
;

base : key operator values ;

key : LSQR ID RSQR ;

values : '('  VALUE   (','  VALUE)* ')'  ;

operator : IN
| NIN
;
VALUE:  '"' .*? '"' ;
AND : 'AND' ;
OR : 'OR' ;
NOT : 'not' ;
EQ : '=' ;
COMMA : ',' ;
SEMI : ';' ;
IN : 'IN' ;
NIN : 'NOT_IN' ;
LSQR : '[' ;
RSQR : ']' ;

INT : [0-9]+ ;
ID: [a-zA-Z_][a-zA-Z_0-9-!]* ;
WS: [\t\n\r\f ]+ -> skip ;

Java监听者与解析器代码

@Component
@NoArgsConstructor
public class ANTLRTargetingExpressionParser {

static List<Group> groupList = new ArrayList<>();
public String entityOperator;

public static class ExpMapper extends ExpBaseListener {
TargetEntity targetEntity;
Group group;
List<TargetEntity> targetEntities;
private static int inc = 1;

    @Override
    public void exitDef(ExpParser.DefContext ctx) {
    group.setTargets(targetEntities);
    groupList.add(group);
    super.exitDef(ctx);
    }
    
    @Override
    public void exitValues(ExpParser.ValuesContext ctx) {
      targetEntity.setValues(
          Arrays.asList(
              Arrays.toString(ctx.VALUE().stream().collect(Collectors.toSet()).toArray())));
      super.exitValues(ctx);
      targetEntities.add(targetEntity);
    }
    
    @Override
    public void exitOperator(ExpParser.OperatorContext ctx) {
      targetEntity.setOperator(ctx.getText());
      super.exitOperator(ctx);
    }
    
    @Override
    public void exitKey(ExpParser.KeyContext ctx) {
      targetEntity = new TargetEntity();
      ctx.getParent();
      targetEntity.setEntity(ctx.ID().getText());
      super.exitKey(ctx);
    }
    
    @Override
    public void enterDef(ExpParser.DefContext ctx) {
      group = new Group();
      targetEntities = new ArrayList<>();
      super.enterDef(ctx);
    }

}

public List<Group> parse(String expression) {

    ANTLRInputStream in = new ANTLRInputStream(expression);
    ExpLexer lexer = new ExpLexer(in);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    ExpParser parser = new ExpParser(tokens);
    parser.setBuildParseTree(true);
    ParseTree tree = parser.def();
    
    ParseTreeWalker walker = new ParseTreeWalker();
    System.out.println(tree.toStringTree(parser));
    ExpMapper mapper = new ExpMapper();
    walker.walk(mapper, tree);
    
    return groupList;

}
}

当前输出

[Group(targets=[{LANGUAGE, IN, [["Dutch", "Arabic"]]}, {Content_Series, IN, [["The Walking Dead"]]}, {PUBLISHER_NAME, IN, [["Yahoo Search", "Yahoo! NAR"]]}]),  
Group(targets=[{LANGUAGE, IN, [["English"]]}, {PUBLISHER_NAME, IN, [["Aol News", "Microsoft-Bing!"]]}]),  
Group(targets=[{LANGUAGE, IN, [["English"]]}, {PUBLISHER_NAME, IN, [["Aol News", "Microsoft-Bing!"]]}])]

解决方案

Q1:解决GroupList重复条目问题

原因分析

  1. 语法规则歧义:原def规则的第二个分支(OR? '(' def ')')*是递归重复匹配结构,会导致解析树中生成多层def节点,每次exitDef触发时都会向列表添加Group,最终产生重复项;
  2. 静态变量累积:groupList是静态变量,若多次调用parse方法,旧数据不会清空,也会导致重复,但当前单次调用的重复问题主要由语法规则导致。

修复步骤

1. 修正语法规则

重新定义语法,明确区分OR连接的Group组、AND连接的基础表达式,消除递归歧义:

grammar Exp;

options {
language = Java;
}

start
:  exp EOF
;

exp : group (OR group)* ; // 顶级为多个OR连接的Group
group : '(' base (AND base)* ')' ; // 单个Group是括号内多个AND连接的基础表达式
base : key operator values ;

key : LSQR ID RSQR ;
values : '(' VALUE (',' VALUE)* ')' ;
operator : IN | NIN ;

VALUE:  '"' .*? '"' ;
AND : 'AND' ;
OR : 'OR' ;
NOT : 'not' ;
EQ : '=' ;
COMMA : ',' ;
SEMI : ';' ;
IN : 'IN' ;
NIN : 'NOT_IN' ;
LSQR : '[' ;
RSQR : ']' ;

INT : [0-9]+ ;
ID: [a-zA-Z_][a-zA-Z_0-9-!]* ;
WS: [\t\n\r\f ]+ -> skip ;

2. 调整监听者逻辑

  • 移除groupList的静态修饰,改为监听者内部维护,避免多次调用的数据累积;
  • 新增对应group节点的监听方法,精准控制Group的创建与添加:
@Component
@NoArgsConstructor
public class ANTLRTargetingExpressionParser {

public String entityOperator;

public static class ExpMapper extends ExpBaseListener {
    private List<Group> groupList = new ArrayList<>();
    private Group currentGroup;
    private List<TargetEntity> currentTargets;
    private TargetEntity currentTarget;

    @Override
    public void enterGroup(ExpParser.GroupContext ctx) {
        currentGroup = new Group();
        currentTargets = new ArrayList<>();
    }

    @Override
    public void exitGroup(ExpParser.GroupContext ctx) {
        currentGroup.setTargets(currentTargets);
        groupList.add(currentGroup);
    }

    @Override
    public void exitValues(ExpParser.ValuesContext ctx) {
        List<String> values = ctx.VALUE().stream()
                .map(v -> v.getText().replace("\"", ""))
                .collect(Collectors.toList());
        currentTarget.setValues(values);
        currentTargets.add(currentTarget);
    }

    @Override
    public void exitOperator(ExpParser.OperatorContext ctx) {
        currentTarget.setOperator(ctx.getText());
    }

    @Override
    public void exitKey(ExpParser.KeyContext ctx) {
        currentTarget = new TargetEntity();
        currentTarget.setEntity(ctx.ID().getText());
    }

    public List<Group> getGroupList() {
        return groupList;
    }
}

public List<Group> parse(String expression) {
    ANTLRInputStream in = new ANTLRInputStream(expression);
    ExpLexer lexer = new ExpLexer(in);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    ExpParser parser = new ExpParser(tokens);
    parser.setBuildParseTree(true);
    ParseTree tree = parser.exp();

    ParseTreeWalker walker = new ParseTreeWalker();
    ExpMapper mapper = new ExpMapper();
    walker.walk(mapper, tree);
    
    return mapper.getGroupList();
}
}

Q2:捕获语法错误软异常

实现步骤

ANTLR默认会将错误打印到控制台,要捕获并处理错误,需自定义错误监听器:

1. 自定义错误监听器

import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import java.util.ArrayList;
import java.util.List;

public class CustomErrorListener extends BaseErrorListener {
    private List<String> errors = new ArrayList<>();

    @Override
    public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
                            int line, int charPositionInLine, String msg, RecognitionException e) {
        errors.add(String.format("语法错误:行%d,列%d - %s", line, charPositionInLine, msg));
    }

    public List<String> getErrors() {
        return errors;
    }

    public boolean hasErrors() {
        return !errors.isEmpty();
    }
}

2. 在解析器中注册监听器

修改parse方法,替换默认错误监听器,捕获并抛出错误:

public List<Group> parse(String expression) throws IllegalArgumentException {
    ANTLRInputStream in = new ANTLRInputStream(expression);
    ExpLexer lexer = new ExpLexer(in);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    ExpParser parser = new ExpParser(tokens);
    
    // 移除默认监听器,添加自定义监听器
    parser.removeErrorListeners();
    CustomErrorListener errorListener = new CustomErrorListener();
    parser.addErrorListener(errorListener);
    
    parser.setBuildParseTree(true);
    ParseTree tree = parser.exp();
    
    // 检查错误并抛出
    if (errorListener.hasErrors()) {
        throw new IllegalArgumentException(String.join("\n", errorListener.getErrors()));
    }

    ParseTreeWalker walker = new ParseTreeWalker();
    ExpMapper mapper = new ExpMapper();
    walker.walk(mapper, tree);
    
    return mapper.getGroupList();
}

上层代码可通过捕获IllegalArgumentException获取错误信息,进行后续处理。


内容的提问来源于stack exchange,提问作者Karijma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 23:40:22