首先,写这篇博文主要是为了记录下我在用antlr+idea开发时遇到的坑点来帮助大家,希望大家不要走我的弯路,同时也是记录自己的一个写编译器历程。
在这就给大家用我自己的话简单介绍一下antlr,就功能需求而言,其就是一个在你给定文法的前提下,能将输入串转换成语法树的工具。
也就是,你给定一个文法,再给定一个输入串,他能判断该输入串是否符合该文法,如果符合,就能将其生成为一棵语法树供你进一步使用。
在idea的plugins中进行搜索ANTLR4应该就能搜到该插件,进行下载重启即可,如下图所示。
重启后就能在下方工具栏看到ANTLR Preview和Tool Output两栏,也就是插件提供的功能。如何使用该插件在下文会提到。
如何来使用该工具呢,首先当然是配置环境,因为本人是使用Java语言来进行开发的,所以只能配置一下maven的依赖即可,请一定注意,版本号应和上文下载的插件对应的最新版本号吻合,否则可能会有bug
<dependency>
<groupId>org.antlrgroupId>
<artifactId>antlr4-runtimeartifactId>
<version>4.8version>
dependency>
然后配置好环境后,下一步就是开始写文法。ANTLR专门的.g4后缀文件,用于写文法,在IDEA中随便找个目录新建一个xxx.g4文件即可,这里推荐大家将xxx的第一个字母大写,因为后面会根据该文件名生成对应的类,所以首字母大写会好一些。
在创建好.g4文件后,它的图标应该长这样,如下图
当然也有可能它不长那样(比如我自己的),可能就是IDEA本身的问题了,像这样
如果遇到图标无法正常显示,那么插件的功能也就无法正常使用了,因为它没法在该文件中进行相关文法的检验。所以怎么解决呢?
我本人是通过在项目src/main/java目录里新建一个Scratch File,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UoKOeggi-1607319549983)(https://i.loli.net/2020/12/07/WQtogHCeP9EnZNA.png)]
然后里面就应该有这样的画面,然后点击第一个创建,应该就能拿到正常图标显示的文件了,也可以进行相关插件的使用了。
然后在该文件中进行相关文法的书写,文法的书写规范这里就不再赘述了,大家可以自行找相关博客来学习,在我们成功完成文法的书写后,我们就可以开始生成相应的parser和lexer了。
但是需要注意的是,ANTLR是不支持直接左递归文法的,需要进行改写。
我们在g4文件处右键首先进行配置,选择Configure ANTLR
就应该会有这个页面
然我们在第一个框里选择想要导出的位置,一般是选择在项目中新建一个包,并选择该包,然后选择OK就行。
然后我们就可以点击上图的Generate ANTLR Recognizer进行类的生成了。生成成功应该如下图所示:
其中Parser是语法分析的类,Lexer是词法分析的类,此时我们就已经可以开始通过输入一些字符串来检查是否符合我们的文法了。
我们就在一个Main方法中输入以下语句即可
public static void main(String[] args) throws IOException, RecognitionException {
File file = new File("xxx"); // 自行选择路径
InputStream in = new FileInputStream(file);
ANTLRInputStream input = new ANTLRInputStream(in);
C0Lexer lex = new C0Lexer(input);
CommonTokenStream tokens = new CommonTokenStream(lex);
C0Parser parser = new C0Parser(tokens);
C0Parser.ProgramContext tree = parser.program();
System.out.println(tree.toStringTree(parser));
}
如果输入的字符串符合文法,那么就能正常输出一个生成的语法树了,如果不符合,那么插件也会自动帮你打印出错的位置,并且不会报错。
**如果想要它报错该怎么办呢?**这也是当时困扰我很久的问题,想要其报错,我们就需要为其自己设计一个Listener来自定义错误处理。
我们就自己新建一个类,并继承BaseErrorListener类,并重载syntaxError方法即可,如下:
public class MyErrorListener extends BaseErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e)
{
throw new RuntimeException("hello");
}
}
这样在我们的Parser遇到不符合我们定义的文法时,就能报错并输出了。
即通过插件我们可以测试自己设计的文法中的一个parser,而不用每次都执行main方法,我们只用在g4文件中选择一个parser,并右键选择Test Rule xxx即可,在idea下方就会自动出现一个弹窗用于测试了。
我们经过一系列努力后,终于成功生成语法树了,那我们应该怎么处理这棵树呢?
antlr为我们提供了两种遍历语法树的方式,分别为Listener方式和Visitor方式。这两种方式的不同点在于,listener是其已经为你定义好怎么遍历了,而Visitor则需要你自己定义怎么去遍历。且listener的所有返回类型都为void,即不能通过方法返回值传参,而visitor可以自定义一个返回值类型,当然所有方法的返回值类型都是一样的。
不管要使用listener还是visitor方式,方法都是去继承对应antlr为我们生成的类,有xxxlistener和xxxvisitor,继承就可以了,然后选择自己想要重载的方法进行重载,进而就能实现自己想要的功能了。
对于listener和visitor,想要用其遍历自己生成的语法树,就通过以下代码即可:
public static void main(String[] args) throws IOException, RecognitionException {
...
C0Parser.ProgramContext tree = parser.program();
// visitor方式
YourVisitor visitor = new YourVisitor();
visitor.visit(tree);
// listener方式
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(new YourListener(), tree);
}
如果上文存在谬误请一定不吝指出。