Javac原理剖析

Javac原理剖析

Javac是什么?

​ Javac是一种编译器,能将一种语言规范转化为另一种语言规范。Javac的任务就是将Java源代码语言转化成JVM能够识别的一种语言,然后由JVM将JVM语言转化成当前这个机器能够识别的机器语言。

这里写图片描述

​ Javac的任务就是将Java源码成Java字节码,也就是JVM能够识别二进制码。从表面上看就是上面的部分将.java文件转成.class文件,而实际上是将Java的源代码转化成一连串二进制数字,这些二进制数字是有格式的,只有JVM能够正确识别他们到底表达了什么意思。

Javac编译器的基本结构

​ Javac的作用是将符合规范源代码转化成为字节码,需要哪些过程呢?我们可以复习一下大学时候的编译原理知识。

这里写图片描述

​ 如何编译程序呢?

​ 1.词法分析:主要是读取源代码,一个字节为一节地读取进来,找些那些是语言的关键字例如,if、else等。词法分析就是从源代码中找出一些规范化的Token流,一个形象的比如就是人类能分清楚一个句子的单词和标点符号。

​ 2.语法分析:检查这些关键词组合在一起是不是符合Java规范,如在if后面是不是跟着布尔表达式,就像人类能分清楚一个句子的主语谓语宾语,检查是否符合语法。语法分析的结果就是形成一个Java语言规范的抽象语法树。

​ 3.语义分析:将一些复杂难懂的语法转化为简单易懂的语法。这个过程对应于将复杂难懂的文言文转化为白话文。语义分析的结果是将复杂难懂的语法转化为最简单的语法,对应于Java,可以将注解等转化为抽象语法树。

​ 最后是代码生成将抽象语法树生成字节码,可以对应于人类的将中文转化为英文组合成新的句子。

Javac原理分析

词法分析器

我们可以通过一个简单的Java类来进行词法分析。

package compile;

public class Cifa {
    int a;
    int b=a;
    int c=a+1;

}

​ 我们可以调用com.sun.tools.javac.main.Main类来实现手动编译指定的类。

这里写图片描述

​ Javac的主要词法分析器的结构类是com.sun.tools.javac.parser.Lexer,这个默认实现类是com.sun.tools.javac.parser.Scanner,这个类会逐步读取Java源程序的单个字符,然后解析出符合Java语言规范的Token序列。所设计的类如下图

这里写图片描述

​ 这两个Factory生成两个接口类的Scanner和JavacParser,JavacParser规定哪些词是符合Java语言规范规定,而具体读取和归类不同的词法操作由Scanner完成,Token规定了完成Java语言的合法关健词,Names用来存储和表示解析后的词法。

​ 词法分析额过程是在JavacParser的parseCompilationUnit()方法中完成,主要代码如下:

这里写图片描述

这里写图片描述

这里写图片描述

​ 这段代码表示从源文件的一个字符开始,按照Java语法规范依次出现packege、import、类定义,以及属性和方法定义等,最后构建一个抽象的语法树。

​ 词法分析器的分析结构就是将这个列中的所有关键词匹配到Token类中的所有项中的任何一项。上述代码分析的Token流是:

这里写图片描述

​ 这里有两个关键点是:Javac是如何分辨这一个个Token的呢?还有就是Javac如何知道啊哪些字符组合在一起就是一个Token的呢?

​ 第一个问题的答案是这样的:Javac在进行词法分析时会由javacParser根据Java语言规范来控制什么顺序、什么地方应该出现什么Token。

​ 判断当前的Token是不是Token.PACKAGE,使用qualident方法。这个方法的源代码是:

这里写图片描述

上述代码的执行流程如下:

这里写图片描述

通过上图,我们明白了Token的顺序规则,之后我们来解决下一个问题,如何判断哪些字符组合是一个Token 的规则是在Scanner的nextToken方法中定义,没调用一次这个方法会构成一个Token,而这个Token必须是com.sun.tools.javac.parser.Token中的任何元素之一。

那么如何将读取每个token转化呢?这个任务是在com.sun.tools.javac.parser.Keywords类中完成,Keywords负责将所有字符集合对应到Token集合中。

字符集合到Token转换相关的类关系如下图
这里写图片描述

每个字符集合都会是一个Name对象,所有的Name对象都存储在Name.Table这个内部类中,这个类也就是对应的这类的符号表。而Keywords会将Token的对应关系,这个关系保存在Keywords类的key数组中,这个key数组只保存了在com.sun.tools.javac.parser.Token类中定义的所有Token到Name对象的关系,而其他的所有字符集合Keywords都会将它对应到Token.IDENTIFIER类型。

字符集合转化成Name对象,Name对象对应到Token的转换关系如下图;

这里写图片描述

语法分析器

​ 语法分析器是将词法分析器分析的Token流建成更加结构化的语法树,也就是将一个个单词组装成一句话,一个完整的语句。

​ Javac的语法树使得Java源码更加结构化,这种结构化可以为后面进一步处理提供方便。每个语法书上的节点都是com.sun.tools.javac.tree.JCTree的一个实例。

​ 关于语法规则是:

​ 1.每一个语法节点都会实现一个接口xxxTree,这个接口继承自com.sun.source.tree.Tree接口。

​ 2.每个语法节点都是com.sun.tools.javac.tree.JCTree的子类,并且会实现第一节点中的xxxTree接口类,这个类的名称类似于JCxxx。

​ 3.所有的JCxxx类都作为一个静态内部类定义在JCTree类中。

这里写图片描述

JCTree类中有如下三个重要的属性项:

​ 1.Tree tag:每个语法节点都会用一个整形常数表示,并且每个节点类型的数值是在其哪一个的基础上加1,顶层节点TOPLEVEL是1,而IMPORT节点等于TOPLEVEL加1,等于2.

​ 2.pos:也是一个整数,它存储的是这个语法节点在源代码中的起始位置,一个文件的位置是0,而-1表示不存在。

​ 3.type:它表示的是这个节点是什么Java类型,如是int、float还是String。

​ 在package的词法分析的过程是

这里写图片描述

这行代码会调用TreeMaker类,根据Name对象构建成一个JCIdent语法节点,如果多几目录,将构建成JCFieldAccess语法节点,JCFieldAccess节点可以是嵌套关系。

​ 下面是import语法树的构造代码:

这里写图片描述

这里写图片描述

​ 整个JCImport节点的语法树如下:
这里写图片描述

​ Import节点解析完成之后就是类的鸡西,类包含interface、class、enum,下面是以calss为例来介绍class是如何解析成一颗语法树的。

这里写图片描述

这里写图片描述

​ 第一个Token是Token.CLASS这个累的关键词,接下来是一个用户自定义的Token.IDENTIFIER,这个Token是类名。

​ 最后解析整个classBody解析的结果保存在list集合中,最后将会把这些子节点添加到JCClassDeca这颗class树中。

​ 下面的代码为例;

这里写图片描述

​ 这段代码对应的语法树如下:

这里写图片描述

​ 上面的语法树去掉了一些节点类型。在将这个类解析完成之后,会将这个类节点添加到这个类对应的包路径的顶层节点中,这个顶层节点是JCCompilationUnit.JCCompilationUnit持有以package作为pid和JCClassDec1的集合,这个整个java文件被解析完成,这颗完整的语法树如下:

这里写图片描述

​ 关于语法分析的一点需要说明的是,所有语法节点的生成树都是在TreeMaker类中完成的,TreeMaker实现了在JCTree.Factory接口中定义的所有节点的构成方法。

语义分析器

​ 在语法书的基础上进一步处理,例如给类添加默认的构造器函数,检查变量在使用前是否已经初始化,将一些常量进行合并处理,检查操作变量类型是否匹配,检查所有的操作数据是否可达,检查checked exception异常是否已经捕获或者破除,解除Java语法糖等。

​ 将在Java类中的符号输入到符号表中主要由com.sun.tools.javac.comp.Enter类来完成,这个类主要完成以下两个步骤:

​ 1.将所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号、超类符号和继承的接口类型符号等都存储到一个未处理的列表中。

​ 2.将这个未处理列表中所有的类都解析到各自的类符号列表中,这个操作是在MemberEnter.complete()中完成。

​ 在上面的Yufa类中,会添加一个无参构造函数。

​ 接下来使用com.sun.tools.javac.comp.Attr检查语法的合法性进行逻辑判断:

​ 1.变量的类型是否匹配

​ 2.变量在使用前是狗已经初始化

​ 3.能够推导出泛型方法的参数类型

​ 4.字符串常量的合并

​ 例如:

这里写图片描述

​ 经过解析后,源代码变成:

这里写图片描述

​ 这里将两个字符串合并成一个字符串。

​ 在标注完成以后,由com.sun.tools.javac.comp.Flow完成数据流分析,主要完成的工作如下:

1.检查变量的使用前时候已经被正确赋值,除了Java的原始类型,其他像String类型和对象的引用都必须在使用前先赋值

2.保证final修饰的变量不会被重复复制。

3.要确定方法的返回值类型

4.所有的CheckedException都要捕获或者向上抛出。

5.所有的语句都要被执行到。

​ 语义分析的足后一个步骤是执行com.sun.tools.javac.comp.Flow,这就是在进一步对语法树进行语义分析,如消除一些无用的代码;去除永不真的条件判断;解除一些语法糖,类型的自动转化操作。

一些例子如下:

​ 类型转化:

这里写图片描述

​ for循环解析:

这里写图片描述

​ 内部类:

这里写图片描述

这里写图片描述

微信公众号

有兴趣的同学可以关注小编哟!
这里写图片描述

猜你喜欢

转载自blog.csdn.net/oeljeklaus/article/details/81000939
今日推荐