java-在ANTLR中,如何使用生成的Visitor代码
背景
- 上一遍文章说了使用listener去遍历树,这篇使用visitor去遍历,上一篇文章链接是
- 目标是将类似字符串{42,43,{32,7+6,3+9,5,6+55},3,7+1,44},不是加的按照原来模式返回16进制,是加法的返回两个数之和。
g4文件
这个和listener有点不一样,为了更好的使用visitor,我们经常会把备选规则使用#标记起来,便于后面生成visitor代码的时候会针根据标记生成,如下
grammar Hello;
line:LEFT value(','value)* RIGHT;
value:INT #intShow
| line #lineShow
| INT'+'INT #getSUM
;
INT:[0-9]+;
LEFT:'{';
RIGHT:'}';
生成对应的visitor文件
idea中生成参考链接
最终生成的文件中,我们关系visitor的,如下
D:\源码\kafka-2.1\antlr\gen\HelloBaseVisitor.java
D:\源码\kafka-2.1\antlr\gen\HelloVisitor.java
其中base的内容如下,和自己在g4文件中定义的标记一致,如果不定义标记,就会根据每条规则生成方法,下面就是根据备选规则标记而生成的内容,如下:
// Generated from D:/源码/kafka-2.1/antlr/Hello.g4 by ANTLR 4.13.1
import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor;
/**
* This class provides an empty implementation of {@link HelloVisitor},
* which can be extended to create a visitor which only needs to handle a subset
* of the available methods.
*
* @param <T> The return type of the visit operation. Use {@link Void} for
* operations with no return type.
*/
@SuppressWarnings("CheckReturnValue")
public class HelloBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements HelloVisitor<T> {
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLine(HelloParser.LineContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitIntShow(HelloParser.IntShowContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLineShow(HelloParser.LineShowContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitGetSUM(HelloParser.GetSUMContext ctx) { return visitChildren(ctx); }
}
main方法调用
- 需要使用自己的visitor类
- 使用自己的visitor类对语法树进行遍历
- override的方法,不管返回什么都可以,这里还不知道使用的场景在哪里?
- 测试字符串共计长度36个,从0开始,所以最后应该是35,这里不知道为什么34是可以的?
- 这里自己写了一个方法检测当前的节点的位置,不知道有没有其他方法,暂时没找到,自己写了一个
public int getLastPosition(String ruleString){
// String cc="[@25,28:29='44',<3>,1:28]";
String lastPositionStr=ruleString.split("=")[0].split(":")[1];
return Integer.parseInt(lastPositionStr);
}
具体代码如下
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.List;
public class main {
public static void main(String[] args) {
//需要分析的语句
String inputStr = "{42,43,{32,7+6,3+9,5,6+55},3,7+1,44}";
System.out.println("input size is "+inputStr.length());
//将字符串转换为ANTLR的CharStream
CharStream input = CharStreams.fromString(inputStr);
//使用词法分析器分析转换后的输入
Lexer lexer = new HelloLexer(input);
//新建一个词法符号的缓冲区,存储生成的词法符号
CommonTokenStream commonTokenStream = new CommonTokenStream(lexer);
//使用语法分析器处理缓冲区的内容
HelloParser helloParser = new HelloParser(commonTokenStream);
//对第一个line规则进行语法分析
ParseTree parseTree = helloParser.line();
//获取树的子数目
int childCount = parseTree.getChildCount();
//打印LISP风格的树
System.out.println(parseTree.toStringTree());
//循环打印出子节点
for (int i = 0; i < childCount; i++) {
System.out.println("child " + i + ":" + parseTree.getChild(i).toStringTree());
}
System.out.println(" ");
// catToDog cc=new catToDog();
// ParseTreeWalker walker=new ParseTreeWalker();
// walker.walk(cc,parseTree);
myVisitor mv = new myVisitor();
mv.visit(parseTree);
}
public static class myVisitor extends HelloBaseVisitor<String> {
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override
public String visitLine(HelloParser.LineContext ctx) {
// System.out.println("visitLine"+ctx.value().toString()+" text:"+ctx.getText());
System.out.printf("{");
return visitChildren(ctx);
}
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override
public String visitIntShow(HelloParser.IntShowContext ctx) {
int stopInt=ctx.getParent().getStop().getCharPositionInLine()-1;
// System.out.println("dd1:"+getLastPosition(ctx.getStop().toString()) +" "+stopInt);
if(getLastPosition(ctx.getStop().toString())==stopInt){
System.out.printf(Integer.toHexString(Integer.parseInt(ctx.getText()))+"},");
if(getLastPosition(ctx.getStop().toString())==34){
System.out.printf(Integer.toHexString(Integer.parseInt(ctx.getText()))+"}");
}else{
System.out.printf(Integer.toHexString(Integer.parseInt(ctx.getText()))+"},");
}
}else{
System.out.printf(Integer.toHexString(Integer.parseInt(ctx.getText()))+",");
}
return visitChildren(ctx);
}
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override
public String visitLineShow(HelloParser.LineShowContext ctx) {
return visitChildren(ctx);
}
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public String visitGetSUM(HelloParser.GetSUMContext ctx) {
int stopInt=ctx.getParent().getStop().getCharPositionInLine()-1;
// System.out.println("dd:"+getLastPosition(ctx.INT(1).getSymbol().toString()) +" "+stopInt);
if(getLastPosition(ctx.INT(1).getSymbol().toString())==stopInt){
if(getLastPosition(ctx.INT(1).getSymbol().toString())==34){
System.out.printf((Integer.parseInt(ctx.INT(0).toString())+Integer.parseInt(ctx.INT(1).toString()))+"}");
}else{
System.out.printf((Integer.parseInt(ctx.INT(0).toString())+Integer.parseInt(ctx.INT(1).toString()))+"},");
}
}else{
System.out.printf((Integer.parseInt(ctx.INT(0).toString())+Integer.parseInt(ctx.INT(1).toString()))+",");
}
return "4444";
}
public int getLastPosition(String ruleString){
// String cc="[@25,28:29='44',<3>,1:28]";
String lastPositionStr=ruleString.split("=")[0].split(":")[1];
return Integer.parseInt(lastPositionStr);
}
}
}
测试结果如下,可以看到成功的将{42,43,{32,7+6,3+9,5,6+55},3,7+1,44}转换成了{2a,2b,{20,13,12,5,61},3,8,2c},2c}
input size is 36
([] { ([5] 42) , ([7] 43) , ([7] ([16 7] { ([5 16 7] 32) , ([7 16 7] 7 + 6) , ([7 16 7] 3 + 9) , ([7 16 7] 5) , ([7 16 7] 6 + 55) })) , ([7] 3) , ([7] 7 + 1) , ([7] 44) })
child 0:{
child 1:([5] 42)
child 2:,
child 3:([7] 43)
child 4:,
child 5:([7] ([16 7] { ([5 16 7] 32) , ([7 16 7] 7 + 6) , ([7 16 7] 3 + 9) , ([7 16 7] 5) , ([7 16 7] 6 + 55) }))
child 6:,
child 7:([7] 3)
child 8:,
child 9:([7] 7 + 1)
child 10:,
child 11:([7] 44)
child 12:}
{2a,2b,{20,13,12,5,61},3,8,2c},2c}
总结
- 如何获取最后一个字符串的位置
int stopInt=ctx.getParent().getStop().getCharPositionInLine()-1
也可以使用getStart().getCharPositionInLine()获取开始的字符串位置
- 根据标记生产的方法什么时候会执行
就是在遍历语法树的时候,会执行对应的方法,如给规则INT’+'INT 定义了一个#getSUM标记,在定义visitor的时候,就会有一个生成的getSUM方法。在字符串中如果有多个加法的情况下,会在每个加法的时候调用getSUM
- listener的遍历和visiotr的遍历方法不同
listener使用的walk,而visitor使用的是myVisitor.visit(parseTree)
- ctx.getStop()返回的格式是[@25,28:29=‘44’,<3>,1:28]
- 如果要计算一个表达式的值,如数值加减乘除,可以是使用visit(ctx.expr(xxxx))