1. 编译器简单介绍
compiler 也叫编译器,是一种电脑程序,它会将用某种编程语言写成源代码,转换成另一种编程语言。
从维基百科的定义来看,编译器就是个将当前语言转为其他语言的过程,回到babel上,它所做的事就是语法糖之类的转换,比如ES6/ES7/JSX转为ES5或者其他指定版本,因此称之为compiler也是正确的。像其他的
- Less/Saas
- TypeScript/coffeeScript
- Eslint
- etc...
都可以看到compiler的身影,也是通过这些工具,才使得目前的前端工程化能走入相对深水区。这里主要以前端的视角进行探讨。
2. 编译器的基本思路
主要的是compiler的思路
2.1 词法分析
2.1.1 目的
将文本分割成一个个的“token”,例如:init、main、init、x、;、x、=、3、;、}等等。同时它可以去掉一些注释、空格、回车等等无效字符;
2.1.2 生成方式
词法分析生成token的办法有2种:
1.使用正则进行词法分
需要写大量的正则表达式,正则之间还有冲突需要处理,不容易维护,性能不高,所以正则只适合一些简单的模版语法,真正复杂的语言并不适合。并且有的语言并不一定自带正则引擎。
2.使用自动机进行词法分析
自动机可以很好的生成token;
有穷状态自动机(finite state machine):在有限个输入的情况下,在这些状态中转移并期望最终达到终止状态。
有穷状态自动机根据确定性可以分为:
- “确定有穷状态自动机“(DFA-Deterministic finite automaton):在输入一个状态时,只得到一个固定的状态。DFA可以认为是一种特殊的NFA;
- “非确定有穷状态机“(NFA-Non-deterministic finite automaton):当输入一个字符或条件得到一个状态机的集合。JavaScript 正则采用的是 NFA引擎。
2.2 语法分析
编译原理就是将一种语言转换为另一种语言。编译原理被称为形式语言,它是一类无需知道太多语言背景、无歧义的语言。而自然语言通常难以处理,主要是因为难以识别语言中哪些是名词哪些是动词那些事形容词。例如:“进口汽车”这句话,“进口”到底是动词还是形容词?所以我们要解析一门语言,前提是这门语言有严格的语法规定的语言,而定义的语言的语法规格称为文法。
1956年,乔姆斯基将文法按照规范的严格性分为0型、1型、2型和3型共4种文法,从0到3文法规则是逐渐增加严的。一般的计算机语言是2型,因为0和1型文法定义宽松,将大大增加解析难度、降低解析效率,而3型文法限制又多,不利于设计语言设计灵活性。2型文法也叫做上下文无关文法(CFG)。
语法分析的目的就是通过词法分析器拿到的token流 + 结合文法规则,通过一定算法得到一颗抽象语法树(AST)。抽象语法树是非常重要的概念,尤其在前端领域引用很广。典型应用如babel插件,它的原理就是:es6代码 -> Babylon.parse -> AST -> babel-traverse -> 新的AST -> es5代码。
从生成AST效率和实现难度上,前人总结主要有2种解析算法:自顶向下的分析方法和自底向上的分析方法。自底向上算法分析文法范围广,但实现难度大。而自顶向下算法实现相对简单,并且能够解析文法的范围也不错,所以一般的 compolier 都是采用深度优先索引的方式。
2.3 代码转换(Transformation)
在得到AST后,我们一般会先将AST转为另一种AST,目的是生成更符合预期的AST,这一步称为代码转换。
代码转换的优势:主要是产生工程上的意义
- 易移植:与机器无关,所以它作为中间语言可以为生成多种不同型号的目标机器码服务;
- 机器无关优化:对中间码进行机器无关优化,利于提高代码质量;
- 层次清晰:将AST映射成中间代码表示,再映射成目标代码的工作分成进行,使编译算法更加清晰;
对于一个 Compiler 而言,在转换阶段通常有两种形式,同语言的AST转换。这里有一种通用的做法是,对我们之前的AST从上至下的解析(称为traversal),然后会有个映射表(称为visitor),把对应的类型做对应的转换。
2.4 代码生成(Code Generation)
在实际的代码处理中,可能会递归的分析(recursive)我们最终生成AST,然后对于每种type都有个对应的函数处理,当然,这可能是最简单的做法。总之,我们的目标代码会在这一步输出,对于我们的目标语言,它就是HTML了。
2.5 完整链路(Compiler)
至此,我们就完成了一个完整的 Compiler 的所有过程:
input => tokenizer => tokens; // 词法分析
tokens => parser => ast; // 语法分析,生成AST
ast => transformer => newAst; // 中间层代码转换
newAst => generator => output; // 生成目标代码