编写一个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元循环求值器内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!