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监听者与解析器代码解析后,出现两个问题:
- 输出的GroupList末尾出现重复条目(问题Q1);
- 不知道如何在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重复条目问题
原因分析
- 语法规则歧义:原
def规则的第二个分支(OR? '(' def ')')*是递归重复匹配结构,会导致解析树中生成多层def节点,每次exitDef触发时都会向列表添加Group,最终产生重复项; - 静态变量累积:
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
相关产品推荐
相关产品推荐

