最近工作涉及到编译器实现方面的东西,找到ANTLR这个工具,功能强大,大大简化了工作,现给出一个简单介绍。
简单来说,ANTLR通过自定义/预定义的语法文件,自动生成语法树解析器代码,在此基础上只需要自己做一点工作,就可以解析原始文本并进行需要的操作。比如,如果想自动分析java源文件生成接口信息,源文件可能像下面这样
Demo.java:
import java.util.List;
import java.util.Map;
public class Demo {
void f(int x, String y) { }
int[ ] g(/*no args*/) { return null; }
List<Map<String, Integer>>[] h() { return null; }
}
希望生成的接口信息应该是下面这样
interface IDemo {
void f(int x, String y);
int[ ] g(/*no args*/);
List<Map<String, Integer>>[] h();
}
下载ANTLR(用到的版本是antlr-4.7-complete.jar)及java语法文件(Java.g4,可在 https://github.com/antlr/grammars-v4 下载),运行如下命令生成语法解析源代码
$ java -jar ./antlr-4.7-complete.jar ./Java.g4
执行上述命令后,生成6个文件:Java.tokens, JavaLexer.tokens, JavaLexer.java, JavaParser.java, JavaListener.java, JavaBaseListener.java。其中前两个文件是tokens定义,可用于调试中查询状态机信息,后面4个java文件即为自动生成的语法解析源代码。
为了实现一开始的目标,仅需要在如下两个文件中实现两个类,ExtractInterfaceListener用来处理语法树,ExtractInterfaceTool则作为main类调用工具类实现整个流程。
ExtractInterfaceListener.java:
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.Interval;
public class ExtractInterfaceListener extends JavaBaseListener {
JavaParser parser;
public ExtractInterfaceListener(JavaParser parser) {this.parser = parser;}
/** Listen to matches of classDeclaration */
@Override
public void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx){
System.out.println("interface I"+ctx.Identifier()+" {");
}
@Override
public void exitClassDeclaration(JavaParser.ClassDeclarationContext ctx) {
System.out.println("}");
}
/** Listen to matches of methodDeclaration */
@Override
public void enterMethodDeclaration(
JavaParser.MethodDeclarationContext ctx
)
{
// need parser to get tokens
TokenStream tokens = parser.getTokenStream();
String type = "void";
if ( ctx.type()!=null ) {
type = tokens.getText(ctx.type());
}
String args = tokens.getText(ctx.formalParameters());
System.out.println("\t"+type+" "+ctx.Identifier()+args+";");
}
}
ExtractInterfaceListener中仅实现了需要用到的方法,其他方法在JavaBaseListener中均为空方法。
ExtractInterfaceTool.java:
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.*;
import java.io.FileInputStream;
import java.io.InputStream;
public class ExtractInterfaceTool {
public static void main(String[] args) throws Exception {
String inputFile = null;
if ( args.length>0 ) inputFile = args[0];
InputStream is = System.in;
if ( inputFile!=null ) {
is = new FileInputStream(inputFile);
}
ANTLRInputStream input = new ANTLRInputStream(is);
JavaLexer lexer = new JavaLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
JavaParser parser = new JavaParser(tokens);
ParseTree tree = parser.compilationUnit(); // parse
ParseTreeWalker walker = new ParseTreeWalker(); // create standard walker
ExtractInterfaceListener extractor = new ExtractInterfaceListener(parser);
walker.walk(extractor, tree); // initiate walk of tree with listener
}
}
至此即可编译运行,运行结果如下
$ javac -cp ".:./antlr-4.7-complete.jar:$CLASSPATH" Java*.java Extract*.java
$ java -cp ".:./antlr-4.7-complete.jar:$CLASSPATH" ExtractInterfaceTool ./Demo.java
interface IDemo {
void f(int x, String y);
int[ ] g(/*no args*/);
List<Map<String, Integer>>[] h();
}
根据这个简单例子,可以进一步实现更加复杂的功能,比如编译器前端,翻译器或识别器等。特别是ANTLR语法文件非常全,能直接解析几乎所有流行编程语言格式,且支持生成c++, java, c#, python, javascript, go, swift等主流语言的源代码,可以在少量修改的基础上实现复杂功能,非常方便。