Let's have two grammars:
grammar Grammar1;
NUMBER : [0-9]+ ;
WS : [ \r\n\t]+ -> skip ;
root : expr EOF
;
expr : '*' expr expr # Multiplication
| expr '+' expr # Addition
| NUMBER # Number
;
grammar Grammar2;
NUMBER : [0-9]+ ;
WS : [ \r\n\t]+ -> skip ;
root : expr EOF
;
expr : NUMBER '!' # Factorial
| NUMBER '^' NUMBER # Exponentiation
| expr '+' expr # Addition
| NUMBER # Number
;
We can see that they both share a number and addition while everything else is different.
The two convertors into an Expression
AST will look like this:
public class Grammar1Converter extends Grammar1BaseVisitor<Expression> {
public Expression visitRoot(Grammar1Parser.RootContext ctx);
public Expression visitMultiplication(Grammar1Parser.MultiplicationContext ctx);
public Expression visitNumber(Grammar2Parser.NumberContext ctx);
public Expression visitAddition(Grammar1Parser.AdditionContext ctx);
}
public class Grammar2Converter extends Grammar2BaseVisitor<Expression> {
public Expression visitRoot(Grammar2Parser.RootContext ctx);
public Expression visitFactorial(Grammar2Parser.FactorialContext ctx);
public Expression visitExponentiation(Grammar2Parser.ExponentiationContext ctx);
public Expression visitNumber(Grammar2Parser.NumberContext ctx);
public Expression visitAddition(Grammar2Parser.AdditionContext ctx);
}
Both grammars share the structure they're converted into. How can we implement a shared converter for the visitNumber
and visitAddition
? Both converters already extend an abstract class so the basic inheritance is out of question. Both convertors have a different context type in the argument so we can't just have a general visitor for the shared method as we would have to convert between the contexts. Is there a way of avoiding repeating the code?
As it's been pointed out in the comments, we can make use of the hierarchies and interfaces we're given by antlr.
We'll make a new utility class that'll take care of the shared functionality (you can use generic types if you want).
public final class SharedGrammerConverter() { ... }
For the leaf nodes we won't need to pass the visitor:
// SharedGrammarConverter
public static Expression visitNumber(ParserRuleContext ctx) {
return new Number(Integer.parseInt(ctx.getChild(0).getText()));
}
// Grammar1Converter
public Expression visitNumber(Grammar1Parser.NumberContext ctx) {
return SharedGrammarConverter.visitNumber(ctx);
}
// Grammar2Converter
public Expression visitNumber(Grammar2Parser.NumberContext ctx) {
return SharedGrammarConverter.visitNumber(ctx);
}
If we need to visit the subtrees, we'll pass the visitor as well:
// SharedGrammarConverter
public static Expression visitAddition(ParserRuleContext ctx,
AbstractParseTreeVisitor<Expression> visitor) {
Expression left = visitor.visit(ctx.getChild(0));
Expression right = visitor.visit(ctx.getChild(2));
return new Addition(left, right);
}
// Grammar1Converter
public Expression visitAddition(Grammar1Parser.AdditionContext ctx) {
return SharedGrammarConverter.visitAddition(ctx, this);
}
// Grammar2Converter
public Expression visitAddition(Grammar2Parser.AdditionContext ctx) {
return SharedGrammarConverter.visit(ctx, this);
}