如何为两个相似的 ANTLR4 语法实现共享访问者?

How to implement a shared visitor for two similar ANTLR4 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
     ;

我们可以看到它们共享一个数字和加法,而其他一切都不同。

将两个转换器转换成 Expression AST 将如下所示:

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);
}

两种语法共享它们转换成的结构。我们如何为 visitNumbervisitAddition 实现共享转换器?两个转换器都已经扩展了一个抽象 class 所以基本的继承是没有问题的。两个转换器在参数中都有不同的上下文类型,因此我们不能只为共享方法设置一个普通访问者,因为我们必须在上下文之间进行转换。有没有办法避免重复代码?

正如评论中指出的那样,我们可以利用 antlr 提供的层次结构和接口。

我们将制作一个新实用程序 class 来处理共享功能(如果需要,您可以使用泛型)。

public final class SharedGrammerConverter() { ... }

对于叶节点,我们不需要传递访问者:

// 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);
}

如果我们需要访问子树,我们也会传递访问者:

// 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);
}