后续内容更新请前往:个人博客,欢迎共同交流。前言源文件:the-super-tiny-compiler中文详解:the-super-tiny-compiler接触过前端,大家都知道前端“ES6即正义”,但是浏览器支持还是有的在进行中,所以我们经常使用一个神奇的工具,将ES6语法转换成目前支持的ES5语法。我们这里说的神器就是编译器。编译器的功能非常纯粹。它将字符串形式的输入语言编译成目标语言的代码字符串(和sourcemap)。除了大家熟知的Babel,还有gcc编译器。不过,我们今天的主角是-super-tiny-compiler,据说是有史以来最小的编译器。删除注释只需要200多行代码。作者JamesKyle是Babel的积极维护者之一。这个编译器的功能非常简单。主要是将Lisp风格的函数调用转换为C风格,例如:Lisp风格(转换前)C风格(转换后)2+2(add22)add(2,2)4-2(subtract42)subtract(4,2)2+(4-2)(add2(subtract42))add(2,subtract(4,2))编译器工作的三个阶段编译器的编译过程类似,主要分为三个阶段:解析:将代码串解析成抽象语法树。Transformation:对抽象语法树进行变换操作。代码生成:根据转换后的抽象语法树生成目标代码串。解析解析过程主要分为词法分析和语法分析两部分。1.词法分析就是通过词法分析器将原始代码串转换成一系列的词法单元(tokens)。词汇单元是由一系列描述独立语法的对象组成的数组。它们可以是值、标签、标点符号、运算符号、括号等。2.语法分析是将词法分析器生成的词法单元转化为中间表示(IntermediateRepresentation)或抽象语法树(AbstractSyntaxTree)),可以描述语法结构(包括语法成分及其关系),其中抽象语法树(简称AST)是一个深度嵌套的对象。下面简单看一下the-super-tiny-compiler的整个解析过程://原码字符串(add2(subtract42))//词法分析转换后生成的词法单元[{type:'paren',值:'('},{类型:'名称',值:'添加'},{类型:'数字',值:'2'},{类型:'paren',值:'('},{type:'name',value:'subtract'},{type:'number',value:'4'},{type:'number',value:'2'},{type:'paren',值:')'},{type:'paren',value:')'},]//语法分析转换后生成的抽象语法树(AST){type:'Program',body:[{type:'CallExpression',名称:'add',参数:[{类型:'NumberLiteral',值:'2',},{类型:'CallExpression',名称:'subtract',参数:[{类型:'NumberLiteral',值:'4',},{type:'NumberLiteral',value:'2',}]}]}]}转换转换过程的主要任务是修改AST,即遍历解析生成的AST同时处理并执行一系列的操作,如添加/删除/修改节点、添加/删除/修改属性、创建新树等。下面简单看一下the-super-tiny的整个转换过程-compiler://原始代码字符串(add2(subtract42))//解析过程生成的AST{type:'Program',body:[{type:'CallExpression',name:'add',params:[{type:'NumberLiteral',value:'2',},{type:'CallExpression',名称:'subtract',params:[{type:'NumberLiteral',value:'4',},{type:'NumberLiteral',value:'2',}]}]}]}//转换生成的AST过程{类型:'程序',正文:[{类型:'ExpressionStatement',表达式:{类型:'CallExpression',被调用者:{类型:'标识符',名称:'添加'},参数:[{类型:'NumberLiteral',value:'2'},{type:'CallExpression',callee:{type:'Identifier',name:'subtract'},arguments:[{type:'NumberLiteral',value:'4'},{type:'NumberLiteral',value:'2'}]}}}]}代码生成根据转换过程生成的抽象语法树生成目标代码字符串源码实现下面我们按照编译器工作的三个阶段一一分析超微型编译器的源码实现。词法分析词法分析器的主要任务是将原始代码串转换成一系列的词法单元(tokens)。//词法分析器参数:codestringinputfunctiontokenizer(input){//当前正在处理的字符索引letcurrent=0;//词法单元数组lettokens=[];//遍历字符串得到词法单元数组while(current{traverseNode(child,parent);});}//接受一个`node`和它的父节点`parent`作为参数functiontraverseNode(node,parent){//从访问者那里获取相应方法的对象letmethods=visitor[node.type];//通过访问者对应的方法操作当前节点if(methods&&methods.enter){methods.enter(node,parent);}switch(node.type){//根节点case'Program':traverseArray(node.body,node);休息;//函数调用案例'CallExpression':traverseArray(node.params,node);休息;//valuesumString,不需要处理case'NumberLiteral':case'StringLiteral':break;//无法识别的字符,抛出错误提示default:thrownewTypeError(node.type);}if(methods&&methods.exit){methods.exit(node,parent);}}//开始遍历traverseNode(ast,null);}递归遍历AST,在遍历过程中通过访问者对应的方法对当前节点进行操作,类似于节。Transform//Transformer,parameter:ASTfunctiontransformer(ast){//创建`newAST`,和之前的AST类似,Program:新AST的根节点letnewAst={type:'Program',body:[],};//通过_context维护新旧AST,注意_context是旧AST对新AST的引用。ast._context=newAst.body;//通过traverser遍历参数:AST和visitortraverser(ast,{//value,直接插入新的AST原样NumberLiteral:{enter(node,parent){parent._context.push({type:'NumberLiteral',value:node.value,});},},//String,直接插入到新的AST中StringLiteral:{enter(node,parent){parent._context.push({type:'StringLiteral',value:node.value,});},},//函数调用CallExpression:{enter(node,parent){//创建不同的AST节点letexpression={type:'CallExpression',callee:{type:'Identifier',name:node.name,},arguments:[],};//函数调用有子类,建立节点对应关系供子节点使用node._context=expression.arguments;//顶层函数调用是语句,打包成一个特殊的AST节点if(parent.type!=='CallExpression'){expression={type:'ExpressionStatement',expression:expression?,};}parent._context.push(表达式);},}});//最后返回新的ASTreturnnewAst;}这里通过_context引用维护新旧AST,简单方便,但会污染旧AST代码生成//代码生成器参数:newASTfunctioncodeGenerator(node){switch(node.type){//遍历body属性中的节点,递归调用codeGenerator,逐行输出结果case'Program':returnnode.body.map(codeGenerator).join('\n');//表达式,处理表达式内容,以分号结尾case'ExpressionStatement':return(codeGenerator(node.expression)+';');//函数调用,加左右括号,参数用逗号分隔case'CallExpression':return(codeGenerator(node.callee)+'('+node.arguments.map(codeGenerator).join(',')+')');//标识符、值、原样输出case'Identifier':returnnode.name;case'NumberLiteral':返回节点值;//字符串,用双引号括起来并输出case'StringLiteral':return'"'+node.value+'"';//无法识别的字符,抛出错误提示default:thrownewTypeError(node.type);}}根据转换后的新AST生成目标代码字符串。编译器函数compiler(input){lettokens=tokenizer(input);让ast=解析器(令牌);让newAst=变压器(ast);让output=codeGenerator(newAst);returnoutput;}编译器的整个工作流程:1.Input=>tokenizer=>tokens2,tokens=>parser=>ast3,ast=>transformer=>newAst4,newAst=>output将以上过程连接起来形成一个简单的编译器。