现在在前端开发中,我们经常使用babel来编译react、vue等框架的代码,以支持更多(更老)的浏览器。babel编译代码的过程是compiling原理的应用之一。Babel是一个JavaScript编译器!这是Babel官方对babel的定义。作为一名前端工程师,了解编译原理是很有必要的。幸运的是,“TheSuperTinyCompiler”开源项目使用JavaScript编写了一个简单的编译器。麻雀虽小,五脏俱全。通过本项目的学习,可以共同加深对编译过程的理解,从而提高自己编写出更高质量的程序!对编程语言的理解,无论是哪种编程语言,都是一样的。有利于了解babel等翻译器、eslint、prettier、less等工具的工作原理,可以开发相关插件造轮子🐶2.编译过程概述编译过程的具体实现主要分为三个步骤:代码分析(parse)代码转换(Transformation)代码生成(CodeGeneration)通过以上三个步骤,我们原来的代码可以转换(compiled)到目标代码,比如将javascript代码转成python。编译过程在《TheSuperTinyCompiler》项目中,将LISP语言编译成C语言,如下:*LISP(source)C(target)**2+2(add22)add(2,2)*4-2(subtract42)subtract(4,2)*2+(4-2)(add2(subtract42))add(2,subtract(4,2))2.转换过程基于“TheSuperTinyCompiler”项目,实现了一个LISPFunction转换为C语言函数书写形式!(源代码)LISPCODE:(add2(subtract42))(objectcode)CCODE:add(2,subtract(4,2))2.1分析(Parse)从具体到具体的转换非常困难,但是从抽象到具体的转换混凝土通常很容易。语法分析过程包括两个关键步骤,词法分析(LexicalAnalysis)和语法分析(SyntacticAnalysis),语法分析是一个从具体到抽象的过程。2.1.1词法分析词法分析的过程主要是通过对原始代码(字符串)进行切分,生成描述程序语义的记号列表。分词原则:一个一个读取源代码中的字符,根据LISP语言定义的语法相关规则如预设关键字,将其转换为{type:'xx',value:'xx',字符串、数字、运算符}的描述形式如LISP:(add2(subtract42)),经过词法分析,会得到如下结果:[{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:')'},]这样一个列表(暂称:tokenslist)依次描述了源代码中的字符串和编程语义。2.1.2语法分析词法分析后得到的token列表已经可以描述LISP的语法,但还不抽象,因为从直觉上,我们无法理解这段程序的意思,所以需要将其转化为AST(摘要语法树,抽象语法树)。为什么要转成AST,AST可以更好的描述源码的语义,而且描述结构更通用,tokens列表只是描述“符号”的意思,词法分析过程可以看成是一个分类过程,并且语法分析的过程就是将符号组合起来,使其具有执行顺序和执行规则的语法。抽象语法树,因为更抽象,可以更高效地转换为其他形式。操作LISP得到的AST可以更好的转化为C语言的AST,因为它们的AST结构相似,操作AST比token更容易。语法分析过程将标记列表转换为树结构:{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',}]}]}]}2.2代码转换(Transform)代码转换的过程是通过对AST的属性进行增删改查,将传入的AST结构转换为C语言要求的标准AST结构。为了实现转换,我们添加了一个traverser(ast,visitor)函数,接收解析过程得到的AST和visitor规则转换对象。visitor对象其实可以理解为一个转换规则。遍历器函数在遍历AST结构时,会根据visitor中定义的规则进行转换,生成新的符合C语言描述标准的AST结构。最后通过transform(ast)(预处理,定义visitor对象)->traverser(ast,visitor)过程,得到一个新的AST结构:{type:'Program',body:[{type:'ExpressionStatement',表达式:{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'}]}}}]}2.3生成目标代码(CodeGenerate)最后,通过分析符合C语言标准的AST,根据C语言的语法规则,将其转化为C语言格式codeGenerator(newAst)函数如下接收新生成的AST结构:functioncodeGenerator(newAst){switch(newAst.type){case"Program":returnnewAst.body.map(codeGenerator).join("\n");case"ExpressionStatement":returncodeGenerator(newAst.expression)+";";case"CallExpression":return(codeGenerator(newAst.callee)+"("+newAst.arguments.map(codeGenerator).join(",")+")");case"Identifier":returnnewAst.name;case"NumberLiteral":returnnewAst。value;case"StringLiteral":return'"'+newAst.value+'"';default:thrownewTypeError(newAst.type);}}同时对代码进行一些格式化,比如加空格换行等.这个时候自然会想,VScode编辑器中的Prettier代码格式化插件是不是也是这样呢?4.总结推荐大家完整阅读《TheSuperTinyCompiler》开源项目,作者写的代码注释也很详细。编译(翻译)的过程原理基本类似,还有很多优秀的项目,codeMirror、babel、esprima、acorn、recast都是值得一读的源码。如果你喜欢它,你必须检查它。参考《Babel Docs》-https://babeljs.io/docs/en/《the super tiny complier》-https://github.com/jamiebuilds/the-super-tiny-compiler/
