如何在ANTLR4 C++目标中捕获解析异常?
解决ANTLR4 C++目标捕获解析异常并获取详细错误信息的问题
我明白你遇到的困扰了——BailErrorStrategy确实不会在抛出的ParseCancellationException中携带有用的错误信息,这是它的设计逻辑:它的核心作用是遇到错误就立即终止解析流程,而非收集和生成详细的错误描述。下面给你两种可行的解决方案,帮你拿到类似line 1:19 missing ';' at ''的错误信息,并重新抛出自定义异常。
方案一:使用StrictErrorStrategy捕获RecognitionException
如果你想要类似BailErrorStrategy的「遇错即停」行为,同时又能拿到详细错误信息,StrictErrorStrategy是更合适的选择。它会在遇到第一个语法错误时终止解析,并抛出带有完整错误上下文的RecognitionException。
修改后的代码如下:
try { ANTLRInputStream instream(sql); MyLexer lexer(&instream); CommonTokenStream tokens(&lexer); MyParser parser(&tokens); // 替换BailErrorStrategy为StrictErrorStrategy parser.setErrorHandler(std::make_shared<antlr4::StrictErrorStrategy>()); auto ctx = parser.singleStatement(); // ... 后续处理逻辑 } catch(antlr4::RecognitionException& e) { // 构建包含行号、列号和错误描述的完整信息 std::string errorMsg = "Parse error at line " + std::to_string(e.getOffendingToken()->getLine()) + ":" + std::to_string(e.getOffendingToken()->getCharPositionInLine()) + " - " + e.getMessage(); std::cout << errorMsg << std::endl; throw MyParseException(errorMsg); // 抛出自定义异常 } catch (std::exception& e) { std::cout << e.what() << std::endl; }
说明:
StrictErrorStrategy是ANTLR提供的严格错误策略,遇到错误就终止解析,行为接近BailErrorStrategy,但会生成详细错误信息- 通过
RecognitionException的getMessage()可以拿到错误描述,getOffendingToken()能获取错误发生的行号和列号,方便精准定位问题
方案二:结合BailErrorStrategy和自定义ErrorListener
如果你一定要保留BailErrorStrategy,可以通过自定义ErrorListener来捕获错误信息——因为BailErrorStrategy在抛出异常前,会先调用错误监听器的syntaxError方法。
首先定义一个自定义错误监听器:
class CustomErrorListener : public antlr4::BaseErrorListener { public: std::string lastError; void syntaxError(antlr4::Recognizer *recognizer, antlr4::Token *offendingSymbol, size_t line, size_t charPositionInLine, const std::string &msg, std::exception_ptr e) override { // 拼接完整错误信息 lastError = "line " + std::to_string(line) + ":" + std::to_string(charPositionInLine) + " " + msg; } };
然后在解析代码中使用这个监听器:
// 把错误监听器声明在try外部,确保catch块能访问到 std::shared_ptr<CustomErrorListener> errorListener = std::make_shared<CustomErrorListener>(); try { ANTLRInputStream instream(sql); MyLexer lexer(&instream); CommonTokenStream tokens(&lexer); MyParser parser(&tokens); // 移除默认的控制台错误监听器,添加自定义监听器 parser.removeErrorListeners(); parser.addErrorListener(errorListener); // 继续使用BailErrorStrategy parser.setErrorHandler(std::make_shared<antlr4::BailErrorStrategy>()); auto ctx = parser.singleStatement(); // ... 后续处理逻辑 } catch(antlr4::ParseCancellationException& e) { std::string errorMsg = errorListener->lastError.empty() ? "Unknown parse error" : errorListener->lastError; std::cout << errorMsg << std::endl; throw MyParseException(errorMsg); // 抛出自定义异常 } catch (std::exception& e) { std::cout << e.what() << std::endl; }
说明:
BailErrorStrategy触发错误时,会先调用CustomErrorListener的syntaxError方法,我们在这里提前记录错误信息- 捕获
ParseCancellationException后,直接从监听器中取出之前记录的错误内容即可
总结
- 优先推荐方案一,
StrictErrorStrategy兼顾了「遇错即停」的需求和错误信息的完整性,代码更简洁 - 如果必须使用
BailErrorStrategy,方案二的自定义监听器可以帮你捕获到所需的错误详情
内容的提问来源于stack exchange,提问作者mmch
相关产品推荐
相关产品推荐

