词法分析器语法分析器 语义分析器代码生成器 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的com.sun.tools.javac.main.Main类来手动编译指定的类。Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,代码逻辑集中在这个类的compile()和compile2()方法中,整个编译最关键的处理就由图中标注的8个方法来完成,回到顶部词法分析器目的:将源码转换为Token流流程:一个字符一个字符的读取源代码,形成规范化的Token流。规范化的Token包含:java关键词:package、import、public、class、int等自定义单词:包名、类名、变量名、方法名符号:=、;、+、-、、/、%、{、}等源码关键:词法分析过程是在的JavacParser.parseCompilationUnit()中完成的com.sun.tools.javac.parser.JavacParser 规定哪些词符合Java语言规范,具体读取和归类不同词法的操作由scanner完成com.sun.tools.javac.parser.Scanner 负责逐个读取源代码的单个字符,然后解析符合Java语言规范的Token序列,调用一次nextToken()都构造一个Tokencom.sun.tools.javac.parser.Tokens
T
o
k
e
n
K
i
n
d
里
面
包
含
了
所
有
t
o
k
e
n
的
类
型
,
譬
如
B
O
O
L
E
A
N
,
B
R
E
A
K
,
B
Y
T
E
,
C
A
S
E
。
c
o
m
.
s
u
n
.
t
o
o
l
s
.
j
a
v
a
c
.
u
t
i
l
.
N
a
m
e
s
用
来
存
储
和
表
示
解
析
后
的
词
法
,
每
个
字
符
集
合
都
会
是
一
个
N
a
m
e
对
象
,
所
有
的
对
象
都
存
储
在
N
a
m
e
.
T
a
b
l
e
这
个
内
部
类
中
。
c
o
m
.
s
u
n
.
t
o
o
l
s
.
j
a
v
a
c
.
p
a
r
s
e
r
.
K
e
y
W
o
r
d
s
负
责
将
字
符
集
合
对
应
到
t
o
k
e
n
集
合
中
,
如
,
p
a
c
k
a
g
e
z
x
y
.
d
e
m
o
.
c
o
m
;
T
o
k
e
n
.
P
A
C
K
A
G
E
=
p
a
c
k
a
g
e
,
T
o
k
e
n
.
I
D
E
N
T
I
F
I
E
R
=
z
x
y
.
d
e
m
o
.
c
o
m
,
(
这
部
分
又
分
为
读
取
第
一
个
t
o
k
e
n
,
为
z
x
y
,
判
断
下
一
个
t
o
k
e
n
是
否
为
“
.
”
,
是
的
话
接
着
读
取
下
一
个
T
o
k
e
n
.
I
D
E
N
T
I
F
I
E
R
类
型
的
t
o
k
e
n
,
反
复
直
至
下
一
个
t
o
k
e
n
不
是
”
.
”
,
也
就
是
说
下
一
个
不
是
T
o
k
e
n
.
I
D
E
N
I
F
I
E
R
类
型
的
t
o
k
e
n
,
T
o
k
e
n
.
S
E
M
I
=
;
即
这
个
T
I
D
E
N
T
I
F
I
E
R
类
型
的
t
o
k
e
n
的
N
a
m
e
读
完
)
,
K
e
y
W
o
r
d
s
类
负
责
此
任
务
。
例
子
:
p
a
c
k
a
g
e
c
o
m
p
i
l
e
;
p
u
b
l
i
c
c
l
a
s
s
C
i
f
a
i
n
t
a
;
i
n
t
c
=
a
+
1
;
以
上
代
码
转
化
为
的
T
o
k
e
n
流
:
回
到
顶
部
语
法
分
析
器
目
的
:
将
进
行
词
法
分
析
后
形
成
的
T
o
k
e
n
流
中
的
一
个
个
T
o
k
e
n
组
成
一
句
句
话
,
检
查
这
一
句
句
话
是
不
是
符
合
J
a
v
a
语
言
规
范
。
流
程
:
p
a
c
k
a
g
e
i
m
p
o
r
t
类
(
包
含
c
l
a
s
s
、
i
n
t
e
r
f
a
c
e
、
e
n
u
m
)
,
一
下
提
到
的
类
泛
指
这
三
类
,
并
不
单
单
是
指
c
l
a
s
s
源
码
关
键
:
c
o
m
.
s
u
n
.
t
o
o
l
s
.
j
a
v
a
c
.
t
r
e
e
.
T
r
e
e
M
a
k
e
r
所
有
语
法
节
点
都
是
由
它
生
成
的
,
根
据
N
a
m
e
对
象
构
建
一
个
语
法
节
点
c
o
m
.
s
u
n
.
t
o
o
l
s
.
j
a
v
a
c
.
t
r
e
e
.
J
C
T
r
e
e
TokenKind 里面包含了所有token的类型,譬如BOOLEAN,BREAK,BYTE,CASE。com.sun.tools.javac.util.Names 用来存储和表示解析后的词法,每个字符集合都会是一个Name对象,所有的对象都存储在Name.Table这个内部类中。com.sun.tools.javac.parser.KeyWords 负责将字符集合对应到token集合中,如,package zxy.demo.com; Token.PACKAGE = package, Token.IDENTIFIER = zxy.demo.com,(这部分又分为读取第一个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语言规范。流程:packageimport类(包含class、interface、enum),一下提到的类泛指这三类,并不单单是指class源码关键:com.sun.tools.javac.tree.TreeMaker 所有语法节点都是由它生成的,根据Name对象构建一个语法节点com.sun.tools.javac.tree.JCTree
TokenKind 里面包含了所有token的类型,譬如BOOLEAN,BREAK,BYTE,CASE。com.sun.tools.javac.util.Names 用来存储和表示解析后的词法,每个字符集合都会是一个Name对象,所有的对象都存储在Name.Table这个内部类中。com.sun.tools.javac.parser.KeyWords 负责将字符集合对应到token集合中,如,packagezxy.demo.com;Token.PACKAGE=package,Token.IDENTIFIER=zxy.demo.com,(这部分又分为读取第一个token,为zxy,判断下一个token是否为“.”,是的话接着读取下一个Token.IDENTIFIER类型的token,反复直至下一个token不是”.”,也就是说下一个不是Token.IDENIFIER类型的token,Token.SEMI=;即这个TIDENTIFIER类型的token的Name读完),KeyWords类负责此任务。例子:packagecompile;publicclassCifainta;intc=a+1;以上代码转化为的Token流:回到顶部语法分析器目的:将进行词法分析后形成的Token流中的一个个Token组成一句句话,检查这一句句话是不是符合Java语言规范。流程:packageimport类(包含class、interface、enum),一下提到的类泛指这三类,并不单单是指class源码关键:com.sun.tools.javac.tree.TreeMaker 所有语法节点都是由它生成的,根据Name对象构建一个语法节点com.sun.tools.javac.tree.JCTreeJCIf 所有的节点都会继承jctree和实现**tree,譬如 JCIf extends JCTree.JCStatement implements IfTreecom.sun.tools.javac.tree.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; }}复制代码最终语法树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,内部类解析成一个与外部类相关联的外部类)最后,将经过上述处理的语法树转化为最后的注解语法树源码关键:com.sun.tools.javac.comp.Enter 将java类中的符号输入到符号表中,主要是两个步骤:将所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号(泛型参数类型)、超类符号和继承的接口类型符号等都存储到一个未处理的列表中。将这个未处理的列表中所有的类都解析到各自的类符号列表中,这个操作是在MemberEnter.complete()中完成(默认构造器也是在这里完成的)。com.sun.tools.javac.processing.JavacProcessingEnvironment 处理注解com.sun.tools.javac.comp.Attr 检查语义的合理性并进行逻辑判断,类型是否匹配,是否初始化,泛型是否可推导,字符串常量合并com.sun.tools.javac.comp.Check 协助attr,变量类型是否正确com.sun.tools.javac.comp.Resolve 协助attr,变量方法类的访问是否合法,是否是静态变量com.sun.tools.javac.comp.ConstFold 协助attr,常量折叠com.sun.tools.javac.comp.Infer 协助attr,推导泛型com.sun.tools.javac.comp.Flow 数据流分析和替换等价源代码的分析(即上面的进一步语义分析)回到顶部代码生成器目的:将注解语法树转化成字节码,并将字节码写入.class文件。流程:将java的代码块转化为符合JVM语法的命令形式,这就是字节码按照JVM的文件组织格式将字节码输出到*.class文件中



