编译器(你不知道的Javascript)

1,092 阅读3分钟

场景

  • ES6 编译成 ES5
  • Sass/Less 编译成 CSS
  • CSS 自动加前缀
  • ESLint
  • 把 A 语言编译成 B 语言

工作过程

大部分编译器的工作可以被分解为三个主要阶段:解析(Parsing),转化(Transformation)以及代码生成(Code Generation)

  1. 解析 将源码转换为一个更抽象的形式。

  2. 转换 接受解析产生的抽象形式并且操纵这些抽象形式做任何编译器想让它们做的事。

  3. 代码生成 基于转换后的代码表现形式(code representation)生成目标代码。

解析

解析一般被分为两个部分:词法分析和语法分析。

  1. 词法分析 通过一个叫做tokenizer(词素生成器,也叫lexer)的工具将源代码分解成一个个词素。

    词素是描述编程语言语法的对象。它可以描述数字,标识符,标点符号,运算符等等。

  2. 语法分析 收词素并将它们组合成一个描述了源代码各部分之间关系的中间表达形式:抽象语法树。

    抽象语法树是一个深度嵌套的对象,这个对象以一种既能够简单地操作又提供很多 关于源代码信息的形式来展现代码。

比如下面的代码(LISP):

(add 2 (subtract 4 2))

上面代码产生的词素会像下面这样:

[
   { type: 'paren',  value: '('        },
   { type: 'name',   value: 'add'      },
   { type: 'number', value: '2'        },
   { type: 'paren',  value: '('        },
   { type: 'name',   value: 'subtract' },
   { type: 'number', value: '4'        },
   { type: 'number', value: '2'        },
   { type: 'paren',  value: ')'        },
   { type: 'paren',  value: ')'        },
 ]
 

而产生的抽象语法树会像下面这样:

    {
      type: 'Program',
      body: [{
        type: 'CallExpression',
        name: 'add',
        params: [{
          type: 'NumberLiteral',
          value: '2',
        }, {
          type: 'CallExpression',
          name: 'subtract',
          params: [{
            type: 'NumberLiteral',
            value: '4',
          }, {
            type: 'NumberLiteral',
            value: '2',
          }]
        }]
      }]
    }

Javascript 的一句代码 let a = 1 ,转换成抽象语法树:

转换

编译器的下一阶段是转换阶段,这个过程接收解析生成的抽象语法树并对它做出改动。转换阶段可以改变抽象语法树使代码保持在同一个语言(列如Bable),或者编译成另外一门语言。

ES6 的一段代码 class Dog {} 转换成 ES5 :

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Dog = function Dog() {
  _classCallCheck(this, Dog);
};

代码生成

编译器的最后步骤是代码生成。有时候编译器在这个步骤也会执行转换阶段的一些行为,但是大体而言代码生成阶段的工作就是基于转换步骤产生的抽象语法树生成目标代码。

代码生成器的工作方式多种多样,一些编译器会重新利用更早阶段产生的词素,还有一些编译器会创建一个独立的代码表达形式从而能够线性地打印节点,但大部分编译器还是使用的抽象语法树。