用JavaScript实现一个编译器

开发 前端
Babel is a JavaScript compiler!这是Babel官方对于babel的定义。身为前端工程师,因此有必要了解编译原理,幸运的是,“The Super Tiny Compiler”开源项目利用JavaScript写了一个简单的编译器。

 现在前端开发中,我们常常会用到babel来编译例如react、vue框架的代码,以支持更多的(更古老的)浏览器,babel编译代码的过程就是编译原理的应用之一。

Babel is a JavaScript compiler!这是Babel官方对于babel的定义。身为前端工程师,因此有必要了解编译原理,幸运的是,“The Super Tiny Compiler”开源项目利用JavaScript写了一个简单的编译器。

麻雀虽小,五脏俱全,通过对该项目的学习,一起加深对编译过程的理解,以提升我们写出更高质量的程序!

一、收益

通过掌握(了解)编译原理,将有如下收益:

加深对编程语言的认识,无论何种编程语言,万变不离其宗

殊途同归,有利于理解babel等转译器、eslint、prettier、less等工具的工作原理,可开发相关插件

可以造更多轮子了🐶

二、编译过程概述

编译过程的具体实现主要分为三步骤:

  1. 代码解析(parse)
  2. 代码转换(Transformation)
  3. 代码生成(Code Generation)

通过上述三步骤,可以将我们的原代码,转换(编译)到目标代码,例如把javascript代码转换到python一样。

编译过程

“The Super Tiny Compiler”项目中是将LISP语言编译为C语言,如下案例:

*                  LISP(source)             C(target) 

*   2 + 2          (add 2 2)                 add(2, 2) 
*   4 - 2          (subtract 4 2)            subtract(4, 2) 
*   2 + (4 - 2)    (add 2 (subtract 4 2))    add(2, subtract(4, 2)) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

二、转换过程

基于“The Super Tiny Compiler”项目,实现一个将LISP函数转换到C语言函数编写形式!

(源代码)LISP CODE: (add 2 (subtract 4 2)) 
(目标代码)C CODE:    add(2, subtract(4, 2)) 
  • 1.
  • 2.

2.1 解析(Parse)

具象到具象的转换是很难的,但抽象到具象的转换往往是容易的。

解析的过程中包含了两个关键步骤词法分析(Lexical Analysis)和语法分析(Syntactic Analysis),解析就是一个具象到抽象的过程。

2.1.1 词法分析

词法分析的过程,主要是将原代码(字符串),通过分词的方式生成一个具有描述程序语义的token列表。

分词的原理:逐个读取源代码中的字符,与预设的关键词、字符串、数字、操作符等LISP语言定义的语法相关规则,转换成 {type: 'xx', value: 'xx'} 的具有描述意义的形式

例如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: ')'        }, 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

这样一个列表(暂称作:tokens列表)按照顺序下来很好的描述了源代码中的字符串和编程语义。

2.1.2 语法分析

词法分析后得到的tokens列表已经可以描述LISP的语法,但是还并不抽象,因为直观看来,我们无法解读这个程序的意思,这就需要将其转换为AST(Abstract Syntax Tree,抽象语法树)。

为什么要将其转换到AST,AST能更好的描述源代码的语义、描述结构更加通用,tokens列表只是描述了“符号”的意义,可以将词法分析过程看作是分类过程,而语法分析的过程,则是将符号组合,使其具有了执行顺序以及执行规则的语法,抽象语法树,因为更抽象,因而能更高效率转换到其他形式。

操作LISP得到的AST能更好转换到C语言的AST,因为他们的AST结构都是类似的,操作AST比tokens更容易。

语法分析的过程,将tokens列表转化成为一个树结构:


  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'
            } 
          ] 
        } 
      ] 
    } 
  ] 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

2.2 代码转换(Transform)

代码转换的过程是将传入的AST结构,通过在AST上例如增、删、改属性,将传入AST转换为C语言需要的标准AST结构。

为了实现转换,我们增加了一个 traverser(ast, visitor) 函数,这个函数接收parse过程得到的AST和visitor规则转换对象。

visitor对象实际可理解为转换规则,traverser函数在遍历AST结构时,会根据visitor中定义的规则执行转换,用于生成新的符合C语言描述标准的AST结构。

最后通过 transform(ast)(预处理,定义visitor对象) -> traverser(ast, visitor) 过程,得到一个新的AST结构:


  type: 'Program'
  body: [ 
    { 
      type: 'ExpressionStatement'
      expression: { 
        type: 'CallExpression'
        callee: { 
          type: 'Identifier'
          name'add' 
        }, 
        arguments: [ 
          { 
            type: 'NumberLiteral'
            value: '2' 
          }, 
          { 
            type: 'CallExpression'
            callee: { 
              type: 'Identifier'
              name'subtract' 
            }, 
            arguments: [ 
              { 
              type: 'NumberLiteral'
              value: '4' 
            }, 
            { 
              type: 'NumberLiteral'
              value: '2' 
            } 
          ] 
        } 
      } 
    } 
  ] 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

2.3 生成目标代码(Code Generate)

最后通过解析符合C语言标准的AST,根据C语言的语法规则,转换成为C语言格式

codeGenerator(newAst) 函数如下,接收新生成的AST结构:

function codeGenerator(newAst) { 
  switch (newAst.type) { 
    case "Program"
      return newAst.body.map(codeGenerator).join("\n"); 
    case "ExpressionStatement"
      return codeGenerator(newAst.expression) + ";"
    case "CallExpression"
      return ( 
        codeGenerator(newAst.callee) + 
        "(" + 
        newAst.arguments.map(codeGenerator).join(", ") + 
        ")" 
      ); 
    case "Identifier"
      return newAst.name
    case "NumberLiteral"
      return newAst.value; 
    case "StringLiteral"
      return '"' + newAst.value + '"'
    default
      throw new TypeError(newAst.type); 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

同时还做了代码的部分格式化,比如空格、换行符添加等。

此时自然会思考下,VScode编辑器中的Prettier代码格式化插件是不是也是这么做的?

四、总结

推荐大家完整阅读一下“The Super Tiny Compiler”开源项目,作者写的代码注释也非常详细。编译(转译)的过程原理基本类似,还有很多优秀的项目,比如codeMirror、babel、esprima、acorn、recast都是值得阅读的源码,喜欢的小伙伴一定要去瞅瞅。

Reference

  • 《Babel Docs》- https://babeljs.io/docs/en/
  • 《the super tiny complier》- https://github.com/jamiebuilds/the-super-tiny-compiler/

 

责任编辑:姜华 来源: DYBOY
相关推荐

2016-11-08 18:53:08

编译器

2020-08-26 09:05:03

函数编译词法

2020-11-30 06:20:13

javascript

2022-03-28 10:25:27

前端文件编译器

2021-12-30 11:26:31

语言编译器脚本

2016-06-15 09:28:09

新型编译器JavaScript类型

2014-04-14 15:54:00

print()Web服务器

2020-10-16 08:26:07

JavaScript开发技术

2022-06-02 16:46:25

京东APP升级Android升级AGP

2021-04-08 13:54:52

LinuxIBM编译器

2020-06-04 12:55:44

PyTorch分类器神经网络

2013-06-13 10:02:36

JavaScriptJavaScript编

2014-05-04 12:51:21

Javascript编译器

2012-07-18 11:31:50

ibmdw

2022-10-21 14:21:46

JavaScript笔记技能

2010-01-27 16:39:48

C++编译器

2018-12-04 13:30:28

Javascript编译原理前端

2021-06-08 07:48:26

lambda表达式编译器

2020-01-10 18:04:01

Python编程语言Windows

2020-06-11 08:48:49

JavaScript开发技术
点赞
收藏

51CTO技术栈公众号