编译器都是由词法分析器(lexical analysis)也叫扫描器(Scanner)开始的,OpenJDK中的词法分析器是由接口com.sun.tools.javac.parser.Lexer和其实现类com.sun.tools.javac.parser.Scanner来组成其功能。然后Scanner的主要功能由com.sun.tools.javac.parser.JavaTokenizer来实现。
public
其实这部分没有什么好讲的,都是根据Java语言规范(The Java Language Specification, Java SE 14 Edition)把源代码扫描解析成com.sun.tools.javac.parser.Tokens$Token,目前一共有112种类型的Token,都在com.sun.tools.javac.parser.Tokens$TokenKind里定义。
Lexer类把源码分解成Token之后,由com.sun.tools.javac.parser.Parser接口的实现类com.sun.tools.javac.parser.JavacParser把Token封装成com.sun.tools.javac.tree.JCTree的子类。这些类的关系如下图所示,中间省略了各种工厂,因为他们对理解程序没有帮助。
解析一个Java文件由JavacParser.parseCompilationUnit()开始,一个合法的java文件同由下面的主要元素组成的:
简单说明一下“[]”里面的内容表示这个内容可以出现0次或是1次;"{}"里面的内容可以出现0次或是多次。所以下面的内容慢慢体会、理解一下就知道什么样是合法的.java文件了。
[{"@" Annotation} package packagename ";"]
{Import}
{ClassType} //这个是广类
所以总体流程是这样的:
这些实现除了“读取各种广类”复杂点之外,这个是我们接下来要讲的重点,其它的都是很直接简单的。
再讲“读取各种广类”这个流程之前,我们先来看看一些基本的类JCTree$JCCompilationUnit:
public
这个类里面的字段defs是存放这个文件解析出来的顶级对象有以下几种:
JCTree$JCPackageDecl // 包申明
JCTree$JCImport // import,一条一个,所以有多个。
JCTree$JCClassDecl // 广类申明,可以有多个,但是最多只能有一个是public的。
好了,“读取各种广类”其实就是解析各种类(类,接口,枚举,注解,记录),函数入口在JavacParser.typeDeclaration,然后都是调用JavacParser类里面的函数。流程图如下(点击查看大图):
只画了类的解析,接口、枚举、记录也相似,我倒是建议大概了解一下就行了。这部分又复杂又没有技术含量。目前如果不是性能要求太强或是自己初步写一般都用框架生成。JavaCC或是Antlr都能很好地生成Java语言的解释器。
JavaCC有Java13的解释器描述文件,在这里能找到。
Antlr只找到最新是Java9的,在这里查看。
这个流程走完了,每个文件会生成一个