QFirstLast
QFirstLast

Reputation: 239

Making a Visitor for walking a parser tree more maintainable

The abstract Visitor (& subsequently the concrete one) generated from the TSqlLexer.g4 and TSqlParser.g4 at https://github.com/antlr/grammars-v4/blob/master/sql/tsql/ has a lot of Visit methods corresponding to each grammar rule.

For my project, I only need a subset of these Visit methods but even these are numerous. As I start to put my implementation specific code into the Visit methods of my concrete Visitor, I have a fear that the Visitor is going to have hundreds of lines of code and get unruly.

How do I stop this from happening ? Can I "group" related Visit methods to different classes ? E.g the Visitor has a rule called 'expression' and there are recursively defined sub rules like 'function expression', 'bracketed expressions' etc. Can I have a separate class handle all 'expression' visit methods with my implementation?

Am I overthinking this? This appears to be a common problem encountered while visiting trees and I am hoping that developers more experienced than me have already solved this using other patterns/mechanisms. Any pointers appreciated.

And a big shout out to the amazing guys/gals who developed ANTLR and the .g4 files.

Upvotes: 0

Views: 361

Answers (1)

Mike Lischke
Mike Lischke

Reputation: 53532

Yes, that's possible. Start with a top level listener and for a subtree create another listener where you implement only the methods needed for that sub object.

In the MySQL Workbench DB object parser I have implemented numerous object type listeners and use them in a nested fashion. For example in the table listener (used to get MySQL table properties from an SQL statement) I use a dedicated identifier listener:

void TableListener::exitTableRef(MySQLParser::TableRefContext *ctx) {
  // CREATE TABLE LIKE...
  IdentifierListener listener(ctx);

  db_SchemaRef schema = _schema;
  if (listener.parts.size() > 1 && !listener.parts[0].empty())
    schema = find_named_object_in_list(_catalog->schemata(), listener.parts[0]);

  ...
  }

This listener does only a simple job, but shows how to "outsource" subtasks into separate listeners:

IdentifierListener::IdentifierListener(tree::ParseTree *tree) {
  tree::ParseTreeWalker::DEFAULT.walk(this, tree);
}

void IdentifierListener::enterIdentifier(MySQLParser::IdentifierContext *ctx) {
  parts.push_back(base::unquote(ctx->getText()));
}


As easy as that.

Upvotes: 1

Related Questions