编写一个javascript元循环求值器的方法
在上一篇文章中,我们通过AST完成了微信小程序组件的多端编译,在这篇文章中,让我们更深入一点,通过AST完成一个javascript元循环求值器
结构
一个元循环求值器,完整的应该包含以下内容:
- tokenizer:对代码文本进行词法和语法分析,将代码分割成若干个token
- parser:根据token,生成AST树
- evaluate:根据AST树节点的type,执行对应的apply方法
- apply:根据环境,执行实际的求值计算
- scope:当前代码执行的环境
代码目录
根据结构看,我将代码目录大致拆分为以下几个文件
- parser
- eval
- scope
tokenizer和parser这两个过程不是本文的重点,我统一放在了parser中,交由@babel/parser来处理。
evaluate和apply这两个过程我统一放在了eval文件中处理,一会我们重点看下这部分。
scope则放入scope文件。
evaluate-apply
这其实是一个递归计算的过程。
首先,evaluate接收两个参数,node当前遍历的AST树节点和scope当前环境。然后,evaluate去根据node的type属性,判断该节点是什么类型。判断出类型后,执行apply去求值这个节点所代表的表达式。apply中会再次递归的执行evaluate去计算当前节点的子节点。最终,执行完整颗AST树。
我们来看下具体代码吧
constevaluate=(node:t.Node,scope)=>{ constevalFunc=evaluateMap[node.type]; if(!evalFunc){ throw`${node.loc}${node.type}还未实现`; } returnevalFunc(node,scope); }
以上就是evaluate具体做的事。
其中,evaluateMap是目前实现的内容集合,我们来看下具体的代码
constevaluateMap:EvaluateMap={ File(node:t.File,scope){ evaluate(node.program,scope); }, Program(node:t.Program,scope){ for(constnofnode.body){ evaluate(n,scope); } }, Identifier(node:t.Identifier,scope){ const$var=scope.$find(node.name); if(!$var){ throw`[Error]${node.loc},'${node.name}'未定义`; } return$var.$get(); }, StringLiteral(node:t.StringLiteral,scope){ returnnode.value; }, NumericLiteral(node:t.NumericLiteral,scope){ returnnode.value; }, BooleanLiteral(node:t.BooleanLiteral,scope){ returnnode.value; }, NullLiteral(node:t.NullLiteral,scope){ returnnull; }, BlockStatement(block:t.BlockStatement,scope){ constblockScope=scope.shared?scope:newScope('block',scope); for(constnodeofblock.body){ constres=evaluate(node,blockScope); if(res===BREAK||res===CONTINUE||res===RETURN){ returnres; } } }, DebuggerStatement(node:t.DebuggerStatement,scope){ debugger; }, ExpressionStatement(node:t.ExpressionStatement,scope){ evaluate(node.expression,scope); }, ReturnStatement(node:t.ReturnStatement,scope){ RETURN.result=(node.argument?evaluate(node.argument,scope):void0); returnRETURN; }, BreakStatement(node:t.BreakStatement,scope){ returnBREAK; }, ContinueStatement(node:t.ContinueStatement,scope){ returnCONTINUE; }, IfStatement(node:t.IfStatement,scope){ if(evaluate(node.test,scope)){ returnevaluate(node.consequent,scope); } if(node.alternate){ constifScope=newScope('block',scope,true); returnevaluate(node.alternate,ifScope) } }, SwitchStatement(node:t.SwitchStatement,scope){ constdiscriminant=evaluate(node.discriminant,scope); constswitchScope=newScope('switch',scope); for(constcaofnode.cases){ if(ca.test===null||evaluate(ca.test,switchScope)===discriminant){ constres=evaluate(ca,switchScope); if(res===BREAK){ break; }elseif(res===RETURN){ returnres; } } } }, SwitchCase(node:t.SwitchCase,scope){ for(constitemofnode.consequent){ constres=evaluate(item,scope); if(res===BREAK||res===RETURN){ returnres; } } }, ThrowStatement(node:t.ThrowStatement,scope){ throwevaluate(node.argument,scope); }, TryStatement(node:t.TryStatement,scope){ try{ returnevaluate(node.block,scope); }catch(error){ if(node.handler){ constcatchScope=newScope('block',scope,true); catchScope.$let((node.handler.param).name,error); returnevaluate(node.handler,catchScope); }else{ throwerror; } }finally{ if(node.finalizer){ returnevaluate(node.finalizer,scope); } } }, CatchClause(node:t.CatchClause,scope){ returnevaluate(node.body,scope); }, WhileStatement(node:t.WhileStatement,scope){ while(evaluate(node.test,scope)){ constwhileScope=newScope('loop',scope,true); constres=evaluate(node.body,whileScope); if(res===CONTINUE)continue; if(res===BREAK)break; if(res===RETURN)returnres; } }, ForStatement(node:t.ForStatement,scope){ for( constforScope=newScope('loop',scope), initVal=evaluate(node.init,forScope); evaluate(node.test,forScope); evaluate(node.update,forScope) ){ constres=evaluate(node.body,forScope); if(res===CONTINUE)continue; if(res===BREAK)break; if(res===RETURN)returnres; } }, ForInStatement(node:t.ForInStatement,scope){ constkind=( node.left).kind; constdecl=( node.left).declarations[0]; constname=( decl.id).name; for(constvalueinevaluate(node.right,scope)){ constforScope=newScope('loop',scope,true); scope.$define(kind,name,value); constres=evaluate(node.body,forScope); if(res===CONTINUE)continue; if(res===BREAK)break; if(res===RETURN)returnres; } }, ForOfStatement(node:t.ForOfStatement,scope){ constkind=( node.left).kind; constdecl=( node.left).declarations[0]; constname=( decl.id).name; for(constvalueofevaluate(node.right,scope)){ constforScope=newScope('loop',scope,true); scope.$define(kind,name,value); constres=evaluate(node.body,forScope); if(res===CONTINUE)continue; if(res===BREAK)break; if(res===RETURN)returnres; } }, FunctionDeclaration(node:t.FunctionDeclaration,scope){ constfunc=evaluateMap.FunctionExpression(node,scope); scope.$var(node.id.name,func); }, VariableDeclaration(node:t.VariableDeclaration,scope){ const{kind,declarations}=node; for(constdeclofdeclarations){ constvarName=( decl.id).name; constvalue=decl.init?evaluate(decl.init,scope):void0; if(!scope.$define(kind,varName,value)){ throw`[Error]${name}重复定义` } } }, ThisExpression(node:t.ThisExpression,scope){ const_this=scope.$find('this'); return_this?_this.$get():null; }, ArrayExpression(node:t.ArrayExpression,scope){ returnnode.elements.map(item=>evaluate(item,scope)); }, ObjectExpression(node:t.ObjectExpression,scope){ letres=Object.create(null); node.properties.forEach((prop)=>{ letkey; letvalue; if(prop.type==='ObjectProperty'){ key=prop.key.name; value=evaluate(prop.value,scope); res[key]=value; }elseif(prop.type==='ObjectMethod'){ constkind=prop.kind; key=prop.key.name; value=evaluate(prop.body,scope); if(kind==='method'){ res[key]=value; }elseif(kind==='get'){ Object.defineProperty(res,key,{get:value}); }elseif(kind==='set'){ Object.defineProperty(res,key,{set:value}); } }elseif(prop.type==='SpreadElement'){ constarg=evaluate(prop.argument,scope); res=Object.assign(res,arg); } }); returnres; }, FunctionExpression(node:t.FunctionExpression,scope){ returnfunction(...args:any){ constfuncScope=newScope('function',scope,true); node.params.forEach((param:t.Identifier,idx)=>{ const{name:paramName}=param; funcScope.$let(paramName,args[idx]); }); funcScope.$const('this',this); funcScope.$const('arguments',arguments); constres=evaluate(node.body,funcScope); if(res===RETURN){ returnres.result; } } }, ArrowFunctionExpression(node:t.ArrowFunctionExpression,scope){ return(...args)=>{ constfuncScope=newScope('function',scope,true); node.params.forEach((param:t.Identifier,idx)=>{ const{name:paramName}=param; funcScope.$let(paramName,args[idx]); }); const_this=funcScope.$find('this'); funcScope.$const('this',_this?_this.$get():null); funcScope.$const('arguments',args); constres=evaluate(node.body,funcScope); if(res===RETURN){ returnres.result; } } }, UnaryExpression(node:t.UnaryExpression,scope){ constexpressionMap={ '~':()=>~evaluate(node.argument,scope), '+':()=>+evaluate(node.argument,scope), '-':()=>-evaluate(node.argument,scope), '!':()=>!evaluate(node.argument,scope), 'void':()=>voidevaluate(node.argument,scope), 'typeof':()=>{ if(node.argument.type==='Identifier'){ const$var=scope.$find(node.argument.name); constvalue=$var?$var.$get():void0; returntypeofvalue; } returntypeofevaluate(node.argument,scope); }, 'delete':()=>{ if(node.argument.type==='MemberExpression'){ const{object,property,computed}=node.argument; constobj=evaluate(object,scope); letprop; if(computed){ prop=evaluate(property,scope); }else{ prop=property.name; } returndeleteobj[prop]; }else{ throw'[Error]出现错误' } }, } returnexpressionMap[node.operator](); }, UpdateExpression(node:t.UpdateExpression,scope){ const{prefix,argument,operator}=node; let$var:IVariable; if(argument.type==='Identifier'){ $var=scope.$find(argument.name); if(!$var)throw`${argument.name}未定义`; }elseif(argument.type==='MemberExpression'){ constobj=evaluate(argument.object,scope); letprop; if(argument.computed){ prop=evaluate(argument.property,scope); }else{ prop=argument.property.name; } $var={ $set(value:any){ obj[prop]=value; returntrue; }, $get(){ returnobj[prop]; } } }else{ throw'[Error]出现错误' } constexpressionMap={ '++':v=>{ $var.$set(v+1); returnprefix?++v:v++ }, '--':v=>{ $var.$set(v-1); returnprefix?--v:v-- }, } returnexpressionMap[operator]($var.$get()); }, BinaryExpression(node:t.BinaryExpression,scope){ const{left,operator,right}=node; constexpressionMap={ '==':(a,b)=>a==b, '===':(a,b)=>a===b, '>':(a,b)=>a>b, '<':(a,b)=>aa!=b, '!==':(a,b)=>a!==b, '>=':(a,b)=>a>=b, '<=':(a,b)=>a<=b, '<<':(a,b)=>a<>':(a,b)=>a>>b, '>>>':(a,b)=>a>>>b, '+':(a,b)=>a+b, '-':(a,b)=>a-b, '*':(a,b)=>a*b, '/':(a,b)=>a/b, '&':(a,b)=>a&b, '%':(a,b)=>a%b, '|':(a,b)=>a|b, '^':(a,b)=>a^b, 'in':(a,b)=>ainb, 'instanceof':(a,b)=>ainstanceofb, } returnexpressionMap[operator](evaluate(left,scope),evaluate(right,scope)); }, AssignmentExpression(node:t.AssignmentExpression,scope){ const{left,right,operator}=node; let$var:IVariable; if(left.type==='Identifier'){ $var=scope.$find(left.name); if(!$var)throw`${left.name}未定义`; }elseif(left.type==='MemberExpression'){ constobj=evaluate(left.object,scope); letprop; if(left.computed){ prop=evaluate(left.property,scope); }else{ prop=left.property.name; } $var={ $set(value:any){ obj[prop]=value; returntrue; }, $get(){ returnobj[prop]; } } }else{ throw'[Error]出现错误' } constexpressionMap={ '=':v=>{$var.$set(v);return$var.$get()}, '+=':v=>{$var.$set($var.$get()+v);return$var.$get()}, '-=':v=>{$var.$set($var.$get()-v);return$var.$get()}, '*=':v=>{$var.$set($var.$get()*v);return$var.$get()}, '/=':v=>{$var.$set($var.$get()/v);return$var.$get()}, '%=':v=>{$var.$set($var.$get()%v);return$var.$get()}, '<<=':v=>{$var.$set($var.$get()< >=':v=>{$var.$set($var.$get()>>v);return$var.$get()}, '>>>=':v=>{$var.$set($var.$get()>>>v);return$var.$get()}, '|=':v=>{$var.$set($var.$get()|v);return$var.$get()}, '&=':v=>{$var.$set($var.$get()&v);return$var.$get()}, '^=':v=>{$var.$set($var.$get()^v);return$var.$get()}, } returnexpressionMap[operator](evaluate(right,scope)); }, LogicalExpression(node:t.LogicalExpression,scope){ const{left,right,operator}=node; constexpressionMap={ '&&':()=>evaluate(left,scope)&&evaluate(right,scope), '||':()=>evaluate(left,scope)||evaluate(right,scope), } returnexpressionMap[operator](); }, MemberExpression(node:t.MemberExpression,scope){ const{object,property,computed}=node; constobj=evaluate(object,scope); letprop; if(computed){ prop=evaluate(property,scope); }else{ prop=property.name; } returnobj[prop]; }, ConditionalExpression(node:t.ConditionalExpression,scope){ const{test,consequent,alternate}=node; returnevaluate(test,scope)?evaluate(consequent,scope):evaluate(alternate,scope); }, CallExpression(node:t.CallExpression,scope){ constfunc=evaluate(node.callee,scope); constargs=node.arguments.map(arg=>evaluate(arg,scope)); let_this; if(node.callee.type==='MemberExpression'){ _this=evaluate(node.callee.object,scope); }else{ const$var=scope.$find('this'); _this=$var?$var.$get():null; } returnfunc.apply(_this,args); }, NewExpression(node:t.NewExpression,scope){ constfunc=evaluate(node.callee,scope); constargs=node.arguments.map(arg=>evaluate(arg,scope)); returnnew(func.bind(func,...args)); }, SequenceExpression(node:t.SequenceExpression,scope){ letlast; node.expressions.forEach(expr=>{ last=evaluate(expr,scope); }) returnlast; }, }
以上,evaluate-apply这个过程就完了。
scope
我们再来看下scope该如何实现。
classScopeimplementsIScope{ publicreadonlyvariables:EmptyObj=Object.create(null); constructor( privatereadonlyscopeType:ScopeType, privateparent:Scope=null, publicreadonlyshared=false, ){} }
我们构造一个类来模拟scope。可以看到,Scope类包含了以下4个属性:
- variables:当前环境下存在的变量
- scopeType:当前环境的type
- parent:当前环境的父环境
- shared:有些时候不需要重复构造子环境,故用此标识
接下来我们看下该如何在环境中声明变量
首先构造一个类来模拟变量
classVariableimplementsIVariable{ constructor( privatekind:Kind, privatevalue:any ){} $get(){ returnthis.value } $set(value:any){ if(this.kind==='const'){ returnfalse } this.value=value; returntrue; } }
这个类中有两个属性和两个方法
- kind用于标识该变量是通过var、let还是const声明
- value表示该变量的值
- $get和$set分别用于获取和设置该变量的值
有了Variable类之后,我们就可以编写Scope类中的声明变量的方法了。
let和const的声明方式基本一样
$const(varName:string,value:any){ constvariable=this.variables[varName]; if(!variable){ this.variables[varName]=newVariable('const',value); returntrue; } returnfalse; } $let(varName:string,value:any){ constvariable=this.variables[varName]; if(!variable){ this.variables[varName]=newVariable('let',value); returntrue; } returnfalse; }
var的声明方式稍微有一点差异,因为js中,除了在function中,用var声明的变量是会被声明到父级作用域的(js的历史遗留坑)。我们看下代码
$var(varName:string,value:any){ letscope:Scope=this; while(!!scope.parent&&scope.scopeType!=='function'){ scope=scope.parent; } constvariable=scope.variables[varName]; if(!variable){ scope.variables[varName]=newVariable('var',value); }else{ scope.variables[varName]=variable.$set(value); } returntrue }
除了声明,我们还需要一个寻找变量的方法,该方法会从当前环境开始,一直沿着作用域链,找到最外层的环境为止。因此,代码实现如下
$find(varName:string):null|IVariable{ if(Reflect.has(this.variables,varName)){ returnReflect.get(this.variables,varName); } if(this.parent){ returnthis.parent.$find(varName); } returnnull; }
以上,一个基本的javascript元循环求值器就完成了
最后
大家可以在codesandbox在线体验一下。
完整的项目地址是:nvwajs,欢迎鞭策,欢迎star。
参考
《SICP》
微信小程序也要强行热更代码,鹅厂不服你来肛我呀
到此这篇关于编写一个javascript元循环求值器的方法的文章就介绍到这了,更多相关javascript元循环求值器内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!