編寫一個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樹。
我們來看下具體程式碼吧
const evaluate = (node: t.Node,scope) => { const evalFunc = evaluateMap[node.type]; if (!evalFunc) { throw `${node.loc} ${node.type} 還未實現`; } return evalFunc(node,scope); }
以上就是evaluate具體做的事。
其中,evaluateMap 是目前實現的內容集合,我們來看下具體的程式碼
const evaluateMap: EvaluateMap = { File(node: t.File,scope) { evaluate(node.program,scope); },Program(node: t.Program,scope) { for (const n of node.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) { return node.value; },NumericLiteral(node: t.NumericLiteral,BooleanLiteral(node: t.BooleanLiteral,NullLiteral(node: t.NullLiteral,scope) { return null; },BlockStatement(block: t.BlockStatement,scope) { const blockScope = scope.shared ? scope : new Scope('block',scope); for (const node of block.body) { const res = evaluate(node,blockScope); if (res === BREAK || res === CONTINUE || res === RETURN) { return res; } } },DebuggerStatement(node: t.DebuggerStatement,scope) { debugger; },ExpressionStatement(node: t.ExpressionStatement,scope) { evaluate(node.expression,ReturnStatement(node: t.ReturnStatement,scope) { RETURN.result = (node.argument ? evaluate(node.argument,scope) : void 0); return RETURN; },BreakStatement(node: t.BreakStatement,scope) { return BREAK; },ContinueStatement(node: t.ContinueStatement,scope) { return CONTINUE; },IfStatement(node: t.IfStatement,scope) { if (evaluate(node.test,scope)) { return evaluate(node.consequent,scope); } if (node.alternate) { const ifScope = new Scope('block',scope,true); return evaluate(node.alternate,ifScope) } },SwitchStatement(node: t.SwitchStatement,scope) { const discriminant = evaluate(node.discriminant,scope); const switchScope = new Scope('switch',scope); for (const ca of node.cases){ if (ca.test === null || evaluate(ca.test,switchScope) === discriminant) { const res = evaluate(ca,switchScope); if (res === BREAK) { break; } else if (res === RETURN) { return res; } } } },SwitchCase(node: t.SwitchCase,scope) { for (const item of node.consequent) { const res = evaluate(item,scope); if (res === BREAK || res === RETURN) { return res; } } },ThrowStatement(node: t.ThrowStatement,scope) { throw evaluate(node.argument,TryStatement(node: t.TryStatement,scope) { try { return evaluate(node.block,scope); } catch (error) { if (node.handler) { const catchScope = new Scope('block',true); catchScope.$let((<t.Identifier>node.handler.param).name,error); return evaluate(node.handler,catchScope); } else { throw error; } } finally { if (node.finalizer) { return evaluate(node.finalizer,scope); } } },CatchClause(node: t.CatchClause,scope) { return evaluate(node.body,WhileStatement(node: t.WhileStatement,scope) { while (evaluate(node.test,scope)) { const whileScope = new Scope('loop',true); const res = evaluate(node.body,whileScope); if (res === CONTINUE) continue; if (res === BREAK) break; if (res === RETURN) return res; } },ForStatement(node: t.ForStatement,scope) { for ( const forScope = new Scope('loop',scope),initVal = evaluate(node.init,forScope); evaluate(node.test,forScope); evaluate(node.update,forScope) ) { const res = evaluate(node.body,forScope); if (res === CONTINUE) continue; if (res === BREAK) break; if (res === RETURN) return res; } },ForInStatement(node: t.ForInStatement,scope) { const kind = (<t.VariableDeclaration>node.left).kind; const decl = (<t.VariableDeclaration>node.left).declarations[0]; const name = (<t.Identifier>decl.id).name; for (const value in evaluate(node.right,scope)) { const forScope = new Scope('loop',true); scope.$define(kind,name,value); const res = evaluate(node.body,ForOfStatement(node: t.ForOfStatement,scope) { const kind = (<t.VariableDeclaration>node.left).kind; const decl = (<t.VariableDeclaration>node.left).declarations[0]; const name = (<t.Identifier>decl.id).name; for (const value of evaluate(node.right,FunctionDeclaration(node: t.FunctionDeclaration,scope) { const func = evaluateMap.FunctionExpression(node,scope); scope.$var(node.id.name,func); },VariableDeclaration(node: t.VariableDeclaration,scope) { const { kind,declarations } = node; for (const decl of declarations) { const varName = (<t.Identifier>decl.id).name; const value = decl.init ? evaluate(decl.init,scope) : void 0; 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) { return node.elements.map(item => evaluate(item,scope)); },ObjectExpression(node: t.ObjectExpression,scope) { let res = Object.create(null); node.properties.forEach((prop) => { let key; let value; if(prop.type === 'ObjectProperty'){ key = prop.key.name; value = evaluate(prop.value,scope); res[key] = value; }else if (prop.type === 'ObjectMethod'){ const kind = prop.kind; key = prop.key.name; value = evaluate(prop.body,scope); if(kind === 'method') { res[key] = value; }else if(kind === 'get') { Object.defineProperty(res,key,{ get: value }); }else if(kind === 'set') { Object.defineProperty(res,{ set: value }); } }else if(prop.type === 'SpreadElement'){ const arg = evaluate(prop.argument,scope); res = Object.assign(res,arg); } }); return res; },FunctionExpression(node: t.FunctionExpression,scope) { return function (...args: any) { const funcScope = new Scope('function',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); const res = evaluate(node.body,funcScope); if (res === RETURN) { return res.result; } } },ArrowFunctionExpression(node: t.ArrowFunctionExpression,scope) { return (...args) => { const funcScope = new Scope('function',args[idx]); }); const _this = funcScope.$find('this'); funcScope.$const('this',_this ? _this.$get() : null); funcScope.$const('arguments',args); const res = evaluate(node.body,UnaryExpression(node: t.UnaryExpression,scope) { const expressionMap = { '~': () => ~evaluate(node.argument,'+': () => +evaluate(node.argument,'-': () => -evaluate(node.argument,'!': () => !evaluate(node.argument,'void': () => void evaluate(node.argument,'typeof': () => { if (node.argument.type === 'Identifier') { const $var = scope.$find(node.argument.name); const value = $var ? $var.$get() : void 0; return typeof value; } return typeof evaluate(node.argument,scope); },'delete': () => { if (node.argument.type === 'MemberExpression') { const { object,property,computed } = node.argument; const obj = evaluate(object,scope); let prop; if (computed) { prop = evaluate(property,scope); } else { prop = property.name; } return delete obj[prop]; } else { throw '[Error] 出現錯誤' } },} return expressionMap[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} 未定義`; } else if (argument.type === 'MemberExpression') { const obj = evaluate(argument.object,scope); let prop; if (argument.computed) { prop = evaluate(argument.property,scope); } else { prop = argument.property.name; } $var = { $set(value: any) { obj[prop] = value; return true; },$get() { return obj[prop]; } } } else { throw '[Error] 出現錯誤' } const expressionMap = { '++': v => { $var.$set(v + 1); return prefix ? ++v : v++ },'--': v => { $var.$set(v - 1); return prefix ? --v : v-- },} return expressionMap[operator]($var.$get()); },BinaryExpression(node: t.BinaryExpression,scope) { const { left,operator,right } = node; const expressionMap = { '==': (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,'>>>': (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) => a in b,'instanceof': (a,b) => a instanceof b,} return expressionMap[operator](evaluate(left,evaluate(right,AssignmentExpression(node: t.AssignmentExpression,right,operator } = node; let $var: IVariable; if (left.type === 'Identifier') { $var = scope.$find(left.name); if(!$var) throw `${left.name} 未定義`; } else if (left.type === 'MemberExpression') { const obj = evaluate(left.object,scope); let prop; if (left.computed) { prop = evaluate(left.property,scope); } else { prop = left.property.name; } $var = { $set(value: any) { obj[prop] = value; return true; },$get() { return obj[prop]; } } } else { throw '[Error] 出現錯誤' } const expressionMap = { '=': 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); 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() },} return expressionMap[operator](evaluate(right,LogicalExpression(node: t.LogicalExpression,operator } = node; const expressionMap = { '&&': () => evaluate(left,scope) && evaluate(right,'||': () => evaluate(left,scope) || evaluate(right,} return expressionMap[operator](); },MemberExpression(node: t.MemberExpression,scope) { const { object,computed } = node; const obj = evaluate(object,scope); let prop; if (computed) { prop = evaluate(property,scope); } else { prop = property.name; } return obj[prop]; },ConditionalExpression(node: t.ConditionalExpression,scope) { const { test,consequent,alternate } = node; return evaluate(test,scope) ? evaluate(consequent,scope) : evaluate(alternate,CallExpression(node: t.CallExpression,scope) { const func = evaluate(node.callee,scope); const args = 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; } return func.apply(_this,args); },NewExpression(node: t.NewExpression,scope)); return new (func.bind(func,...args)); },SequenceExpression(node: t.SequenceExpression,scope) { let last; node.expressions.forEach(expr => { last = evaluate(expr,scope); }) return last; },}
以上,evaluate-apply 這個過程就完了。
scope
我們再來看下 scope 該如何實現。
class Scope implements IScope { public readonly variables: EmptyObj = Object.create(null); constructor( private readonly scopeType: ScopeType,private parent: Scope = null,public readonly shared = false,) { } }
我們構造一個類來模擬 scope。可以看到,Scope 類包含了以下4個屬性:
- variables:當前環境下存在的變數
- scopeType:當前環境的type
- parent:當前環境的父環境
- shared:有些時候不需要重複構造子環境,故用此標識
接下來我們看下該如何在環境中宣告變數
首先構造一個類來模擬變數
class Variable implements IVariable { constructor( private kind: Kind,private value: any ){ } $get() { return this.value } $set(value: any) { if (this.kind === 'const') { return false } this.value = value; return true; } }
這個類中有兩個屬性和兩個方法
- kind 用於標識該變數是通過 var、let 還是 const 宣告
- value 表示該變數的值
- $get 和 $set 分別用於獲取和設定該變數的值
有了 Variable 類之後,我們就可以編寫 Scope 類中的宣告變數的方法了。
let 和 const 的宣告方式基本一樣
$const(varName: string,value: any) { const variable = this.variables[varName]; if (!variable) { this.variables[varName] = new Variable('const',value); return true; } return false; } $let(varName: string,value: any) { const variable = this.variables[varName]; if (!variable) { this.variables[varName] = new Variable('let',value); return true; } return false; }
var 的宣告方式稍微有一點差異,因為js中,除了在 function 中,用var 宣告的變數是會被宣告到父級作用域的(js的歷史遺留坑)。我們看下程式碼
$var(varName: string,value: any) { let scope: Scope = this; while (!!scope.parent && scope.scopeType !== 'function') { scope = scope.parent; } const variable = scope.variables[varName]; if (!variable) { scope.variables[varName] = new Variable('var',value); } else { scope.variables[varName] = variable.$set(value); } return true }
除了宣告,我們還需要一個尋找變數的方法,該方法會從當前環境開始,一直沿著作用域鏈,找到最外層的環境為止。因此,程式碼實現如下
$find(varName: string): null | IVariable { if (Reflect.has(this.variables,varName)) { return Reflect.get(this.variables,varName); } if (this.parent) { return this.parent.$find(varName); } return null; }
以上,一個基本的javascript元迴圈求值器就完成了
最後
大家可以在 codesandbox 線上體驗一下。
完整的專案地址是:nvwajs,歡迎鞭策,歡迎star。
參考
《SICP》
微信小程式也要強行熱更程式碼,鵝廠不服你來肛我呀
到此這篇關於編寫一個javascript元迴圈求值器的方法的文章就介紹到這了,更多相關javascript元迴圈求值器內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!