编译技术在我们日常的工作中可以说无处不在,React JSX语法的解析,Typescript转化为Javascript,XML、JSON的解析,Spring字节码生成技术,PHP的模板引擎,还有最近很火的文言文编程项目(wenyan-lang)……这些都和编译技术息息相关,既然编译技术有这么大作用,那么它到底是什么?首先我们给出一个整体的介绍,如下图所示:
从图中可以知道,编译技术总共分为6个过程,其中词法分析、语法分析、语义分析这三个过程统称为前端,生成中间码、优化、生成目标代码这三个过程叫做后端。有了一个整体的印象之后,就可以一步一步来解释每一个过程的作用。
写过程序的人都知道,程序语言一般包含一些特殊关键字:if、else、for、while、class、import……运算符:>、<、=、+、-、*、/……以及特殊字符:{、}、(、)、,、:……所以第一步就是要把程序代码先转化成这样一个一个的词法记号(Token),这个过程叫做词法分析(Lexical Analysis)。
接下来请回想一下自己在写代码的过程中是不是会写各种各样的语句,比如下面这样的if语句:
if(a>b) return a;
如果把a>b抽象成expr(表示任意表达式),把return a;抽象成statement(表示任意语句),那么上面的if语句就可以转化成如下形式:
if(expr) statement
这就是if语句的结构,同样很容易写出while语句的结构:
while(expr) statement
编译的第二个阶段就是要识别出程序代码中一个一个这样类似的结构,这个过程就叫做语法分析(Syntactic Analysis,or Parsing)。但是马上就会有一个疑问,我们到底把这些结构识别成什么了呢?还是回到刚才的例子,用如下图形表示if语句:
看到这里有没有很熟悉?这不就是数据结构中学习过的树结构嘛,是的,这就是一棵树,它叫做抽象语法树(Abstract Syntax Tree,AST),现在很容易就可以画出while语句的抽象语法树了:
同样地,还可以画出很多这样的图形,而我们的程序就是由这样一个一个的语句构成的,那么最终的程序就可以用如下图形表示出来:
这就是整个程序的语法树。你看语法树其实一点也不难理解,而整个语法分析的过程就是构建出这样一棵树。
有了语法树之后还要做什么呢?让我们再次回到刚才if语句的例子:
if(a>b) return a;
在if语句中使用了a、b两个变量,很多语言都要求在使用变量之前先申明,不然就是不合法的,编译器要能检测出这种错误。还有不同的语言有不同的作用域,比如Javascript有函数作用域,Java有类作用域,C语言有块作用域,编译器要能分析不同的作用域,以及每个变量所属的作用域等。在函数作为一等公民的语言中,还要实现闭包,比如如下Javascript代码:
function outer() {
var a = 0;
function inner() {
a = a + 1;
return a;
}
return inner;
}
var inner = outer();
console.log(inner()); // 1
console.log(inner()); // 2
按理说outer函数调用结束后,a应该被销毁了,但是在调用inner的时候还是可以引用到a,这就是我们常说的闭包,编译器要能识别出这样的a。还有比如很多静态类型的语言在编译的时候要做类型检查等等。给生成的语法树添加足够的信息,让它可以生成正确的目标代码,这个添加信息的过程就叫做语义分析(Semantic Analysis)。
理论上,拥有了足够信息的语法树之后已经可以生成目标代码了,这个过程可以用下面的图形表示:
再来看,如果引入中间码的过程会是怎么样:
对比两个图我们可以很容易看出,当引入中间码的时候,可以大大节省工作量,这就是引入中间码的其中一个原因。
我们都知道,衡量程序好坏的其中一个重要指标就是程序的性能,这就要求我们对生成的中间码进行优化,这个过程就是接下来的优化过程,可以用下面的图形表示:
经过优化器以后就可以生成最终的目标代码了,到此我们已经把编译的整个过程过了一遍。
总结:
- 词法分析:把源代码分割成一个一个的词法记号
- 语法分析:识别程序结构,生成语法树
- 语义分析:给语法树添加信息,用于生成正确的目标代码
- 生成中间码:节省工作量,解放生产力,增加灵活性
- 优化:让程序跑得更快
关注公众号获取第一手信息
下篇预告:《人人都可以学会编译原理-词法分析之四则运算》,在该篇中我们的主要目标是实现类似于下面的四则运算表达式的词法记号解析
-3.14 + 3.14 \* 3.14 + (3.14 + 3.14) / 3.14 - 3.14
最终将它转化成如下形式
Minus -
NumberLiteral 3.14
Plus +
NumberLiteral 3.14
Star \*
NumberLiteral 3.14
Plus +
LeftParen (
NumberLiteral 3.14
Plus +
NumberLiteral 3.14
RightParen )
Slash /
NumberLiteral 3.14
Minus -
NumberLiteral 3.14