如何为两个相似的 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);
}
两种语法共享它们转换成的结构。我们如何为 visitNumber
和 visitAddition
实现共享转换器?两个转换器都已经扩展了一个抽象 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);
}
让我们有两个语法:
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);
}
两种语法共享它们转换成的结构。我们如何为 visitNumber
和 visitAddition
实现共享转换器?两个转换器都已经扩展了一个抽象 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);
}