Javac编译原理
java源代码(符合语⾔规范)-->javac-->.class(⼆进制⽂件)-->jvm-->机器语⾔(不同平台不同种类)
如何让java的语法规则适应java虚拟机的语法规则?这个任务由javac编译器来完成java语⾔规范转换成java虚拟机语⾔规范。
编译流程:
流程:
词法分析器:将源码转换为Token流
将源代码划分成⼀个个Token(出java语⾔中的if,else,for等关键字)
语法分析器:将Token流转化为语法树
将上述的⼀个个Token组成⼀句句话(或者说成⼀句句代码块),检查这⼀句句话是不是符合Java语⾔规范(如if后⾯跟的是不是布尔判断表达式)
语义分析器:将语法树转化为注解语法树
将复杂的语法转化成简单的语法(eg.注解、foreach转化为for循环、去掉永不会⽤到的代码块)并做⼀些检查,添加⼀些代码(默认构造器)
代码⽣成器:将注解语法树转化为字节码(即将⼀个数据结构转化成另⼀个数据结构)
ps:要获取javac编译器,可以通过OpenJDK来下载源码,可以⾃⼰编译javac的源码,也可以通过调⽤jdk的ls.javac.main.Main 类来⼿动编译指定的类。Javac编译动作的⼊⼝是l
s.javac.main.JavaCompiler类,代码逻辑集中在这个类的compile()和compile2()⽅法中,整个编译最关键的处理就由图中标注的8个⽅法来完成,
词法分析器
⽬的:将源码转换为Token流
流程:
⼀个字符⼀个字符的读取源代码,形成规范化的Token流。规范化的Token包含:
java关键词:package、import、public、class、int等
⾃定义单词:包名、类名、变量名、⽅法名
符号:=、;、+、-、*、/、%、{、}等
源码关键:
词法分析过程是在的JavacParser.parseCompilationUnit()中完成的
ls.javac.parser.JavacParser 规定哪些词符合Java语⾔规范,具体读取和归类不同词法的操作由scanner完成
ls.javac.parser.Scanner 负责逐个读取源代码的单个字符,然后解析符合Java语⾔规范的Token序列,调⽤⼀次nextToken()都构造⼀个Token
ls.javac.parser.Tokens$TokenKind ⾥⾯包含了所有token的类型,譬如BOOLEAN,BREA
K,BYTE,CASE。
ls.javac.util.Names ⽤来存储和表⽰解析后的词法,每个字符集合都会是⼀个Name对象,所有的对象都存储在Name.Table 这个内部类中。
ls.javac.parser.KeyWords 负责将字符集合对应到token集合中,如,package zxy.demo; Token.PACKAGE = package, Token.IDENTIFIER = zxy.demo,(这部分⼜分为读取第⼀个token,为zxy,判断下⼀个token是否为“.”,是的话接着读取下⼀个Token.IDENTIFIER类型的token,反复直⾄下⼀个token不是”.”,也就是说下⼀个不是Token.IDENIFIER类型的token,Token.SEMI = ;即这个TIDENTIFIER类型的token的Name读完),KeyWords类负责此任务。
例⼦:
package compile;
public class Cifa {
int a;
int c = a + 1;
}
以上代码转化为的Token流:
语法分析器
⽬的:将进⾏词法分析后形成的Token流中的⼀个个Token组成⼀句句话,检查这⼀句句话是不是符合Java语⾔规范。
流程:
package
import
类(包含class、interface、enum),⼀下提到的类泛指这三类,并不单单是指class
源码关键:
TreeMaker 所有语法节点都是由它⽣成的,根据Name对象构建⼀个语法节点
JCTree$JCIf 所有的节点都会继承jctree和实现**tree,譬如 JCIf extends JCTree.JCStatement implements IfTree
JCTree的三个属性
Tree tag:每个语法节点都会以整数的形式表⽰,下⼀个节点在上⼀个节点上加1;
pos:也是⼀个整数,它存储的是这个语法节点在源代码中的起始位置,⼀个⽂件的位置是0,⽽-1表⽰不存在
type:它代表的是这个节点是什么java类型,如int,float,还是string等
例⼦:
package compile;
public class Yufa {
int a;
private int c = a + 1;
//getter
public int getC() {
return c;
}
//setter
public void setC(int c) {
this.c = c;java源代码加密
}
}
最终语法树
ps:左边还少⼀个import的语法节点
说明:
每⼀个包package下的所有类都会放在⼀个JCCompilationUnit节点下,在该节点下包含:package语法树(作为pid)、各个类的语法树
每⼀个从JCClassDecl发出的分⽀都是⼀个完整的代码块,上述是四个分⽀,对应我们代码中的两⾏属性操作语句和两个⽅法块代码块,这样其实就完成了语法分析器的作⽤:将⼀个个Token单词组成了⼀句句话(或者说成⼀句句代码块)
在上述的语法树部分,对于属性操作部分是完整的,但是对于两个⽅法块,省略了⼀些语法节点,例如:⽅法修饰符public、⽅法返回类型、⽅法参数。
语义分析器
⽬的:将语法树转化为注解语法树
流程:
添加默认的⽆参构造器(在没有指定任何有参构造器的情况下),把引⽤其他类的⽅法或者变量,抑或是继承实现来的变量和⽅法等输⼊到类⾃⾝的符号表中
处理注解
标注:检查语义合法性、进⾏逻辑判断
检查语法树中的变量类型是否匹配(eg.String s = 1 + 2;//这样"="两端的类型就不匹配)
检查变量、⽅法或者类的访问是否合法(eg.⼀个类⽆法访问另⼀个类的private⽅法)
变量在使⽤前是否已经声明、是否初始化
常量折叠(eg.代码中:String s = "hello" + "world",语义分析后String s = "helloworld")
推导泛型⽅法的参数类型
数据流分析
变量的确定性赋值(eg.有返回值的⽅法必须确定有返回值)
final变量只能赋⼀次值,在编译的时候再赋值的话会报错
所有的检查型异常是否抛出或捕获
所有的语句都要被执⾏到(return后边的语句就不会被执⾏到,除了finally块⼉)
进⼀步语义分析
去掉永假代码(eg.if(false))
变量⾃动转换(eg.int和Integer)⾃动装箱拆箱
去掉语法糖(eg.foreach转化为for循环,assert转化为if,内部类解析成⼀个与外部类相关联的外部类)最后,将经过上述处理的语法树转化为最后的注解语法树
源码关键:
ls.javacp.Enter 将java类中的符号输⼊到符号表中,主要是两个步骤:
将所有类中出现的符号输⼊到类⾃⾝的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接⼝类型符号等都存储到⼀个未处理的列表中。
将这个未处理的列表中所有的类都解析到各⾃的类符号列表中,这个操作是在MemberEnterplete()
中完成(默认构造器也是在这⾥完成的)。
ls.javac.processing.JavacProcessingEnvironment 处理注解
ls.javacp.Attr 检查语义的合理性并进⾏逻辑判断,类型是否匹配,是否初始化,泛型是否可推导,字符串常量合并
ls.javacp.Check 协助attr,变量类型是否正确
ls.javacp.Resolve 协助attr,变量⽅法类的访问是否合法,是否是静态变量
ls.javacp.ConstFold 协助attr,常量折叠
ls.javacp.Infer 协助attr,推导泛型
ls.javacp.Flow 数据流分析和替换等价源代码的分析(即上⾯的进⼀步语义分析)
代码⽣成器
⽬的:将注解语法树转化成字节码,并将字节码写⼊*.class⽂件。
流程:
将java的代码块转化为符合JVM语法的命令形式,这就是字节码
按照JVM的⽂件组织格式将字节码输出到*.class⽂件中
源码关键:
ls.javac.jvm.Gen 遍历语法树⽣成最终的java字节码
ls.javac.jvm.Items 辅助gen,这个类表⽰任何可寻址的操作项,这些操作项都可以作为⼀个单位出现在操作栈上ls.javac.jvm.Code 辅助gen,存储⽣成的字节码,并提供⼀些能够影射操作码的⽅法
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论