如何让ANTLR显示所有语法错误?(Java开发Pascal编译器场景)
解决ANTLR仅捕获首个语法错误的问题(Pascal编译器场景)
要让ANTLR捕获所有语法错误而不是遇到第一个就终止,需要从错误恢复策略和错误收集机制两方面入手,以下是具体实现步骤:
1. 重写DefaultErrorStrategy,确保解析不终止
ANTLR默认的错误策略在遇到严重错误时会抛出ParseCancellationException终止解析,你需要重写核心方法,让解析器记录错误后继续执行:
import org.antlr.v4.runtime.DefaultErrorStrategy; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.TokenStream; public class PascalErrorStrategy extends DefaultErrorStrategy { @Override public void recover(Parser recognizer, RecognitionException e) { // 记录错误信息到自定义容器 recordError(recognizer, e); // 跳过错误符号,定位到下一个可恢复的语法点(比如语句结束、块结束) TokenStream tokens = recognizer.getInputStream(); if (tokens != null) { consumeUntil(recognizer, getErrorRecoverySet(recognizer)); } } @Override public Token recoverInline(Parser recognizer) throws RecognitionException { RecognitionException e = new InputMismatchException(recognizer); recordError(recognizer, e); // 尝试匹配下一个合法符号,恢复解析 return super.recoverInline(recognizer); } @Override public void sync(Parser recognizer) { // 禁用默认的同步异常抛出逻辑,避免解析终止 } private void recordError(Parser recognizer, RecognitionException e) { Token offendingToken = e.getOffendingToken(); String errorMsg = String.format("语法错误 @ 行 %d, 列 %d: %s", offendingToken.getLine(), offendingToken.getCharPositionInLine(), e.getMessage()); // 假设PascalCompiler有一个静态List<String>用于存储所有错误 PascalCompiler.errorList.add(errorMsg); } }
关键说明:
recover方法负责处理严重错误,跳过错误内容到安全点继续解析sync方法重写为空,避免默认逻辑抛出异常终止解析- 自定义
recordError方法统一收集错误信息
2. 注册自定义错误策略到解析器
创建Parser实例后,替换默认的错误策略:
// 假设已经生成了PascalLexer和PascalParser PascalLexer lexer = new PascalLexer(CharStreams.fromPath(Paths.get("your-pascal-file.pas"))); CommonTokenStream tokens = new CommonTokenStream(lexer); PascalParser parser = new PascalParser(tokens); // 设置自定义错误策略 parser.setErrorHandler(new PascalErrorStrategy());
3. 自定义ErrorListener收集所有错误
除了重写错误策略,还需要自定义ErrorListener来捕获ANTLR抛出的所有语法错误(包括错误策略未覆盖的情况):
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 PascalErrorListener extends BaseErrorListener { private final List<String> errorList = new ArrayList<>(); @Override public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { String errorMsg = String.format("语法错误 @ 行 %d, 列 %d: %s", line, charPositionInLine, msg); errorList.add(errorMsg); } public List<String> getErrorList() { return errorList; } }
注册监听器时,记得移除默认的控制台输出监听器,避免干扰:
PascalErrorListener errorListener = new PascalErrorListener(); // 移除默认的ConsoleErrorListener parser.removeErrorListeners(); parser.addErrorListener(errorListener);
解析完成后,即可从errorListener.getErrorList()获取所有错误信息。
4. 优化语法规则的错误恢复能力
如果语法规则没有合适的同步点,ANTLR可能无法从错误中恢复继续解析。可以在Pascal语法文件(.g4)中添加错误恢复规则,比如:
// 在语句规则中添加错误分支,跳过未知符号直到下一个语句分隔符 statement : assignmentStmt | ifStmt | whileStmt | errorStmt ; errorStmt : UNEXPECTED_TOKEN { skip(); } // 跳过当前错误符号,继续解析后续内容 ;
或者在块级规则中定义同步点,让解析器能快速定位到合法的语法位置:
block : 'BEGIN' statement* 'END' ; // 定义错误恢复的同步集合,比如块结束符、语句分隔符 tokens { UNEXPECTED_TOKEN }
常见问题排查
- 确保错误策略没有抛出
ParseCancellationException,否则解析会直接终止 - 必须移除默认的
ConsoleErrorListener,否则可能导致错误信息重复或收集不全 - 错误收集容器(比如
List<String>)要确保在解析过程中能被正确访问
内容的提问来源于stack exchange,提问作者Phalanx
相关产品推荐
相关产品推荐

