1
1
## JavaScript-compiler项目简介:
2
2
&emsp ;&emsp ; 编译原理在编程世界中无处不在,是我们向高级或底层开发路上不得不要逾越的一道坎。编译原理比较复杂,我们不求写出一个完整的编译器,但掌握基本原理还是很有必要的。
3
3
<br />
4
- &emsp ;&emsp ; 核心内容:自动机、上下文无关文法、自顶向下语法分析、中序转换为后序算法解决语法优先级问题、中间代码生成、内存分配、运行时分析、opcode生成等 。
4
+ &emsp ;&emsp ; 核心内容:自动机、上下文无关文法、自顶向下语法分析、中序转换为后序算法解决语法优先级问题、中间代码生成、运行时内存分配分析、opcode生成、计算机原理等 。
5
5
<br />
6
6
&emsp ;&emsp ; 理解不到位的地方还望斧正。
7
7
8
8
9
9
## 目录
10
10
### [ 01 词法分析] ( ./01_词法分析/README.md )
11
11
### [ 02 语法分析] ( ./02_语法分析/README.md )
12
- ### [ 03 中间代码生成] ( ./03_中间代码生成/README.md )
13
- ### [ 04 运行时刻环境] ( ./04_运行时刻环境/README.md )
14
- ### [ 05 目标代码生成] ( ./05_目标代码生成/README.md )
12
+ ### [ 03 中间代码生成] ( ./03_中间代码生成/README.txt )
13
+ ### [ 04 运行时刻环境] ( ./04_运行时刻环境/README.txt )
14
+ ### [ 05 目标代码生成] ( ./05_目标代码生成/README.txt )
15
15
16
16
17
17
## 源码目录结构:
18
18
```
19
19
src
20
20
├─common 公共库
21
21
├─demo
22
- │ │─tokenizer.ts 词法解析器demo
23
- │ │─parser.ts 语法解析器demo
24
- │ ├─ILGen.ts 中间码生成demo
25
- │ └─opcodeCompiler.ts 机器码生成demo
22
+ │ │─tokenizer.ts 词法解析器demo
23
+ │ │─parser.ts 语法解析器demo
24
+ │ ├─ILGen.ts 中间码生成demo
25
+ │ └─opcodeCompiler.ts 机器码生成demo
26
26
├─parse 语法分析
27
- │ ├─expression.ts 表达式
28
- │ ├─exprParser.ts 表达式解析器
29
- │ │─parser.ts 语法解析器
30
- │ ├─statement.ts 陈述语句
31
- │ └─terminal.ts 终结符
27
+ │ ├─expression.ts 表达式
28
+ │ ├─exprParser.ts 表达式解析器
29
+ │ │─parser.ts 语法解析器
30
+ │ ├─statement.ts 陈述语句
31
+ │ └─terminal.ts 终结符
32
32
├─tokenizer 词法分析
33
- │ └─tokenizer.ts 词法解析器
34
- ├─opcodeCompiler 机器码生成
35
- │ └─opcodeCompiler.ts opcode翻译
33
+ │ └─tokenizer.ts 词法解析器
34
+ ├─opcodeCompiler 机器码生成
35
+ │ └─opcodeCompiler.ts opcode翻译
36
36
├─SDT 语法制导翻译
37
- │ ├─ILGen.ts 中间码生成
38
- │ └─LexicalScope.ts 词法作用域
39
- └─tsconfig.json ts项目配置
37
+ │ ├─ILGen.ts 中间码生成
38
+ │ └─LexicalScope.ts 词法作用域
39
+ └─tsconfig.json ts项目配置
40
40
```
41
41
42
42
43
- ## 编译器 :
43
+ ## 编译器简介 :
44
44
### 什么是编译器:
45
45
&emsp ;&emsp ; 编译器就是将一种编程语言转换为另一种编程语言的程序
46
46
### 编译器的使用场景:
47
47
* 将高级代码编译成浏览器能识别的代码:例如vue中的.vue文件是无法被浏览器识别的,这时需要编译器将其编译成html文件才能正常显示。又如typescript编译成javascript,类似的还有Babel、ESLint、Stylus等等
48
48
* 热更新:接触过小程序开发的同学应该知道,小程序运行的环境禁止new Function,eval等方法的使用,导致我们无法直接执行字符串形式的动态代码。此外,许多平台也对这些JS自带的可执行动态代码的方法进行了限制,那么我们是没有任何办法了吗?既然如此,我们便可以用JS写一个解析器,让JS自己去运行自己。
49
- * 开发跨平台工具:例如京东开源框架Taro,可以只书写一套代码,再通过Taro的编译工具,将源代码分别编译出可以在不同端(微信小程序、H5、App 端等 )运行的代码。类似的还有Egret、Weex等等
49
+ * 开发跨平台工具:例如京东开源框架Taro,可以只书写一套代码,再通过Taro的编译工具,将源代码分别编译出可以在不同端(微信小程序、H5、App端等 )运行的代码。类似的还有Egret、Weex等等
50
50
* 其他常用工具:代码压缩、混淆等
51
- * 用 JavaScript 写成的 JavaScript 解释器 ,意义是什么? - caoglish的回答 - 知乎
51
+ * 用JavaScript写成的JavaScript解释器 ,意义是什么? - caoglish的回答 - 知乎
52
52
https://www.zhihu.com/question/20004379/answer/20123641
53
53
### 编译流程:
54
54
* 常规编译过程:
@@ -68,7 +68,7 @@ https://www.zhihu.com/question/20004379/answer/20123641
68
68
<br />
69
69
~ &emsp ; 源程序 → 编译器 → 目标程序
70
70
<br />
71
- ~ &emsp ; 编译器是将一种语言一次性转换为另一种语言 ,不包括执行过程。
71
+ ~ &emsp ; 编译器是将一种语言转换为另一种语言 ,不包括执行过程。
72
72
* 解释器:
73
73
<br />
74
74
~ &emsp ; 源程序 + 数据 → 解释器 → 输出
@@ -81,32 +81,28 @@ https://www.zhihu.com/question/20004379/answer/20123641
81
81
<br />
82
82
&emsp ;&emsp ; 由于一行一行的解释执行较慢,jvm后面还引入了jit,可以将常用的机器码直接保存下来,提升效率,这里就包含和编译和解释两个过程。
83
83
### 编译流程解析:
84
- * 词法分析:词法分析的目标是将文本分割成一个个的"token",例如:init、main、init、x、;、x、=、3、;、}等等。同时它可以去掉一些注释、空格、回车等等无效字符
85
- * 语法分析:将tokens解析分配在解析树的层次结构中,生成AST。它使tokens组合结构更清晰、层次更明了
86
- * 语法分析的本质:程序本质是一串字符串,但给字符串添加语法规则后,便有可能产生token,以及token之间产生关系。
84
+ * 编译器通常分为编译器前端、中间代码和编译器后端
85
+ * 编译器前端包括:词法分析、语法分析和语义分析
86
+ * 词法分析:词法分析的目标是将字符串文本分割成一个个的"token",例如:init、main、init、x、;、x、=、3、;、}等等。同时它可以去掉一些注释、空格、回车等等无效字符
87
+ * 语法分析:将tokens解析分配在解析树的层次结构中,生成AST(抽象语法树),AST使tokens形成关联树结构。总而言之,语法分析的本质就是:程序员书写的代码本质是一串字符串,但给字符串添加语法规则后,便有可能产生token,以及token之间产生关系。
87
88
* 语义分析:检查每个节点含义对每个节点进行修饰,例如cpu不能做整数乘以浮点数操作,需要将整数转换成浮点数,以满足计算机能够进行的浮点数乘以浮点数的操作
88
- * 编译器前端:词法分析、语法分析和语义分析统称为编译器的前端
89
89
* 编译器前端的目标是生成AST(抽象语法树)
90
90
* AST:
91
91
<br />
92
92
~ &emsp ; 不依赖源语言文法:如果按bnf文法解析源代码,解析为一个自定义的结构,那在解释这个自定义结构的时候,肯定是为bnf文法量身定制的。一旦这个语言有了升级,bnf文法发生变动,相应的,后端解释器也会做相应的改动,十分麻烦。抽象语法树,相当于一个后端解释器给前端定制的一个规范,无论语言规范怎么变动,只需要改变将源代码解析为抽象语法树的解析器就行了,解析抽象语法树的解释器并不用改动。
93
93
<br />
94
94
~ &emsp ; 不依赖语言的细节:比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。
95
- * 中间代码生成:将AST中的表达式转换为三地址表达式
95
+ * 中间代码生成:将AST中的表达式转换为三地址表达式(或其他中间码形式)
96
96
* 中间代码的意义:
97
97
<br />
98
98
&emsp ;&emsp ; 既然已经拿到AST,机器运行需要的又是二进制。为什么不直接翻译成二进制呢?其实到目前为止从技术上来说已经完全没有问题了。但是,我们有各种各样的操作系统,有不同的CPU类型,每一种的位数可能不同;寄存器能够使用的指令也不同,像是复杂指令集与精简指令集等;在进行各个平台的兼容之前,我们还需要替换一些底层函数。因为不同平台的汇编处理都是不一样的,AST并不能完美运行在各个硬件平台上,于是便在 AST 和多个平台的汇编代码中间,抽象出了一个中间码(Intermediate Representation),在中间码的设计里抹平了硬件平台造成的差异。中间码的强大之处在于跨平台,与语言无关。
99
99
<br />
100
100
&emsp ;&emsp ; 中间码存在的另外一个价值是提升后端编译的重用,比如我们定义好了一套中间码应该是长什么样子,那么后端机器码生成就是相对固定的。每一种语言只需要完成自己的编译器前端工作即可。这也是大家可以看到现在开发一门新语言速度比较快的原因。编译是绝大部分都可以重复使用的。
101
101
<br />
102
102
&emsp ;&emsp ; 而且为了接下来的优化工作,中间代码存在具有非凡的意义。因为有那么多的平台,如果有中间码我们可以把一些共性的优化都放到这里,可使程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码,即为中间语言程序,中间语言的复杂性介于源程序语言和机器语言之间。
103
- * 编译器后端:即三地址表达式运行时环境(虚拟机)
104
- * 编译器执行流程简述:(Java、Node、C、C++)
103
+ * 代码优化:
105
104
<br />
106
- ~ &emsp ; AST → 中间代码 → 代码生成(二进制或汇编指令)
107
- <br />
108
- ~ &emsp ; AST → 中间代码 → 字节码
109
- * 代码优化:对三地表达式进行优化,提高用户的执行速度。例如:
105
+ &emsp ;&emsp ; 对三地表达式进行优化,提高用户的执行速度。例如:
110
106
```
111
107
t1=inttofloat(60)
112
108
t2=id3*t1
@@ -118,7 +114,10 @@ https://www.zhihu.com/question/20004379/answer/20123641
118
114
t1=id3*60.0
119
115
di1=id2+t1
120
116
```
121
- * 代码生成:将优化后的中间码生成汇编或二进制指令,cpu可以直接执行指令
117
+ * 编译器后端:将中间代码翻译为机器码的程序,也叫代码生成器
118
+ * 代码生成:
119
+ <br />
120
+ &emsp ;&emsp ; 将优化后的中间码生成汇编或二进制指令,cpu可以直接执行指令
122
121
```
123
122
t1=id3*60.0
124
123
di1=id2+t1
@@ -133,24 +132,31 @@ https://www.zhihu.com/question/20004379/answer/20123641
133
132
```
134
133
* 源码转换成汇编指令的实例过程:
135
134
```
136
- 源码: 中间码: 汇编码:
137
- var x=1 0:x=1 0:set sp #1
138
- function(foo(y)){ foo 1:r=nil 1:inc sp #4
139
- return x+y 2:r=x+y 2:set sp #nil
140
- } 3:return 3:inc sp #4
141
- foo(100) 4:pass 100 4:add x y R
142
- 5:call foo 1 5:load R (sp-4)
143
- 6:goto (sp-8)
144
- 7:set sp #100
145
- 8:inc sp #4
146
- 9:set sp #11 (sp+4)
147
- 10:goto #1
135
+ 源码: 中间码: 汇编码:
136
+ var x=1 0:x=1 0:set sp #1
137
+ function(foo(y)){ foo 1:r=nil 1:inc sp #4
138
+ return x+y 2:r=x+y 2:set sp #nil
139
+ } 3:return 3:inc sp #4
140
+ foo(100) 4:pass 100 4:add x y R
141
+ 5:call foo 1 5:load R (sp-4)
142
+ 6:goto (sp-8)
143
+ 7:set sp #100
144
+ 8:inc sp #4
145
+ 9:set sp #11 (sp+4)
146
+ 10:goto #1
148
147
```
149
148
150
149
151
150
## 计算机原理概述:
152
- ### 计算机的工作原理
153
- [ 《计算机的工作原理》] ( ./计算机的工作原理.txt )
151
+ ### 概述:
152
+ &emsp ;&emsp ; 编译器前端主要用于通过词法语法规则对语言的定义的翻译,是跟机器无关的。例如JS的语法规则在ecma-262中有详细的定义,我们便可以利用相关规则和算法进行转换。
153
+ <br />
154
+ &emsp ;&emsp ; 而中间代码和机器码的生成通常是跟机器相关的,为此,掌握基本的计算机原理知识是十分必要的。本节对计算机基本知识进行了简介,并在[ 计算机的工作原理] ( ./计算机的工作原理.txt ) 一文中进行原理的系统介绍,它向我们展示了从人脑计算到冯诺依曼机、从晶体管到逻辑门、从物理内存到虚拟内存、从二进制到汇编码,人们是如何一步一步将人脑计算抽象到汇编码。
155
+ <br />
156
+ &emsp ;&emsp ; 而我们的编译器的作用就是将代码文本一步一步编译为汇编码。
157
+ <br />
158
+ &emsp ;&emsp ; 通过汇编码这个节点,便可以知道程序员编写的代码是如何被计算机理解并执行的。如此,我们的计算机知识孤岛将能够被真正连接起来。
159
+
154
160
### cpu:
155
161
* cpu主要由运算器、控制器、寄存器组成
156
162
* 运算器:信息处理
@@ -200,7 +206,7 @@ https://www.zhihu.com/question/20004379/answer/20123641
200
206
~ &emsp ; move指令:move ax,19; //将数字19送入寄存器ax中,即ax=19
201
207
<br />
202
208
~ &emsp ; add指令:add ax,18; //将寄存器ax的只加18,即ax=19+18
203
- * 下面的机器指令和汇编指令是一一对应的,它们操作的含义都是:把寄存器 BX 中的内容送到 AX 中 。
209
+ * 下面的机器指令和汇编指令是一一对应的,它们操作的含义都是:把寄存器BX中的内容送到AX中 。
204
210
<br />
205
211
机器指令: 1000100111011000
206
212
<br />
@@ -215,6 +221,8 @@ https://www.zhihu.com/question/20004379/answer/20123641
215
221
### 链接器(Linker):
216
222
* 链接器是一个程序,将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件。
217
223
* 例如,hello程序中调用了printf函数,它是每个C编译器都会提供的标准C库中的一个函数。printf函数存在于一个名为printf.o的单独的编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hello.o程序中。链接器(ld)就负责处理这种合并。结果就得到hello文件,它是一个可执行目标文件,可以被加载到内存中。由系统执行。
224
+ ### 计算机的工作原理详情:
225
+ [ 《计算机的工作原理》] ( ./计算机的工作原理.txt )
218
226
219
227
220
228
## 编译器相关常识补充:
0 commit comments