When you try first with SLL you don't need to collect syntax errors nor is a full error recovery necessary, since you are going to do a second parse run anyway. Hence it is useful to stop parsing as early as possible. That's what the BailErrorStrategy is for. It throws a special exception, which is not catched by the normal error processing and hence stops the ongoing parse run and returns to your code. You can then catch it to know if you should do that LL run.
Here's a full parse function with time measurement and token/parse tree dump I use for testing my MySQL grammar:
void parse(const std::string &sql, bool dumpTokenStream, bool dumpParseTree) {
ANTLRInputStream input(sql);
MySQLLexer lexer(&input);
CommonTokenStream tokens(&lexer);
MySQLParser parser(&tokens);
lexer.serverVersion = version;
lexer.charsets = { "_utf8", "_latin1", "_latin2", "_cp1250" };
//lexer.sqlMode = MySQLBaseLexer::NoBackslashEscapes;
parser.serverVersion = version;
parser.setBuildParseTree(true);
// First parse with the bail error strategy to get quick feedback for correct queries.
parser.setErrorHandler(std::make_shared<BailErrorStrategy>());
parser.getInterpreter<ParserATNSimulator>()->setPredictionMode(PredictionMode::SLL);
parser.removeErrorListeners();
parser.sqlMode = MySQLBaseLexer::AnsiQuotes;
try {
auto start = std::chrono::steady_clock::now();
tokens.fill();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start);
std::cout << "Token fill time: " << duration.count() / 1000.0 << " secs" << std::endl;
} catch (IllegalStateException &) {
std::cout << "Error: illegal state found, probably unfinished string." << std::endl;
}
if (dumpTokenStream) {
for (auto token : tokens.getTokens())
std::cout << token->toString() << std::endl;
std::cout << std::endl;
}
tree::ParseTree *tree;
auto start = std::chrono::steady_clock::now();
try {
tree = parser.query();
} catch (ParseCancellationException &pce) {
// If parsing was cancelled we either really have a syntax error or we need to do a second step,
// now with the default strategy and LL parsing.
tokens.reset();
parser.reset();
parser.setErrorHandler(std::make_shared<DefaultErrorStrategy>());
parser.getInterpreter<ParserATNSimulator>()->setPredictionMode(PredictionMode::LL);
parser.addErrorListener(&ConsoleErrorListener::INSTANCE);
tree = parser.query();
}
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start);
if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
std::cout << "Errors encountered: " << parser.getNumberOfSyntaxErrors() + lexer.getNumberOfSyntaxErrors()<< std::endl;
std::cout << "Query: " << sql << std::endl;
}
std::cout << "Parse time: " << duration.count() / 1000.0 << " ms" << std::endl;
if (dumpParseTree && tree != nullptr) {
std::cout << std::endl << "Parse tree: " << tree->toStringTree(&parser) << std::endl;
}
}
Hope that gets you started.