編寫一個(gè)javascript元循環(huán)求值器的方法
在上一篇文章中,我們通過AST完成了微信小程序組件的多端編譯,在這篇文章中,讓我們更深入一點(diǎn),通過AST完成一個(gè)javascript元循環(huán)求值器
結(jié)構(gòu)
一個(gè)元循環(huán)求值器,完整的應(yīng)該包含以下內(nèi)容:
- tokenizer:對(duì)代碼文本進(jìn)行詞法和語法分析,將代碼分割成若干個(gè)token
- parser:根據(jù)token,生成AST樹
- evaluate:根據(jù)AST樹節(jié)點(diǎn)的type,執(zhí)行對(duì)應(yīng)的apply方法
- apply:根據(jù)環(huán)境,執(zhí)行實(shí)際的求值計(jì)算
- scope:當(dāng)前代碼執(zhí)行的環(huán)境
代碼目錄
根據(jù)結(jié)構(gòu)看,我將代碼目錄大致拆分為以下幾個(gè)文件
- parser
- eval
- scope
tokenizer和parser這兩個(gè)過程不是本文的重點(diǎn),我統(tǒng)一放在了parser中,交由 @babel/parser 來處理。
evaluate和apply這兩個(gè)過程我統(tǒng)一放在了eval文件中處理,一會(huì)我們重點(diǎn)看下這部分。
scope則放入scope文件。
evaluate-apply
這其實(shí)是一個(gè)遞歸計(jì)算的過程。
首先,evaluate 接收兩個(gè)參數(shù),node 當(dāng)前遍歷的AST樹節(jié)點(diǎn)和 scope 當(dāng)前環(huán)境。然后,evaluate去根據(jù) node 的 type 屬性,判斷該節(jié)點(diǎn)是什么類型。判斷出類型后,執(zhí)行 apply 去求值這個(gè)節(jié)點(diǎn)所代表的表達(dá)式。apply 中會(huì)再次遞歸的執(zhí)行 evaluate 去計(jì)算當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)。最終,執(zhí)行完整顆AST樹。
我們來看下具體代碼吧
const evaluate = (node: t.Node, scope) => { const evalFunc = evaluateMap[node.type]; if (!evalFunc) { throw `${node.loc} ${node.type} 還未實(shí)現(xiàn)`; } return evalFunc(node, scope); }
以上就是evaluate具體做的事。
其中,evaluateMap 是目前實(shí)現(xiàn)的內(nèi)容集合,我們來看下具體的代碼
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, scope) { return node.value; }, BooleanLiteral(node: t.BooleanLiteral, scope) { return node.value; }, 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, scope); }, 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, scope); }, TryStatement(node: t.TryStatement, scope) { try { return evaluate(node.block, scope); } catch (error) { if (node.handler) { const catchScope = new Scope('block', scope, 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, scope); }, WhileStatement(node: t.WhileStatement, scope) { while (evaluate(node.test, scope)) { const whileScope = new Scope('loop', scope, 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', scope, true); scope.$define(kind, name, value); const res = evaluate(node.body, forScope); if (res === CONTINUE) continue; if (res === BREAK) break; if (res === RETURN) return res; } }, 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, scope)) { const forScope = new Scope('loop', scope, true); scope.$define(kind, name, value); const res = evaluate(node.body, forScope); if (res === CONTINUE) continue; if (res === BREAK) break; if (res === RETURN) return res; } }, 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} 重復(fù)定義` } } }, 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, key, { 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', 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); const res = evaluate(node.body, funcScope); if (res === RETURN) { return res.result; } } }, ArrowFunctionExpression(node: t.ArrowFunctionExpression, scope) { return (...args) => { const funcScope = new Scope('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); const res = evaluate(node.body, funcScope); if (res === RETURN) { return res.result; } } }, UnaryExpression(node: t.UnaryExpression, scope) { const expressionMap = { '~': () => ~evaluate(node.argument, scope), '+': () => +evaluate(node.argument, scope), '-': () => -evaluate(node.argument, scope), '!': () => !evaluate(node.argument, scope), 'void': () => void evaluate(node.argument, scope), '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] 出現(xiàn)錯(cuò)誤' } }, } 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] 出現(xiàn)錯(cuò)誤' } 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, 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} 未定義`; } 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] 出現(xiàn)錯(cuò)誤' } 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, scope)); }, LogicalExpression(node: t.LogicalExpression, scope) { const { left, right, operator } = node; const expressionMap = { '&&': () => evaluate(left, scope) && evaluate(right, scope), '||': () => evaluate(left, scope) || evaluate(right, scope), } return expressionMap[operator](); }, MemberExpression(node: t.MemberExpression, scope) { const { object, property, 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, scope); }, 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) { const func = evaluate(node.callee, scope); const args = node.arguments.map(arg => evaluate(arg, 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 這個(gè)過程就完了。
scope
我們?cè)賮砜聪?scope 該如何實(shí)現(xiàn)。
class Scope implements IScope { public readonly variables: EmptyObj = Object.create(null); constructor( private readonly scopeType: ScopeType, private parent: Scope = null, public readonly shared = false, ) { } }
我們構(gòu)造一個(gè)類來模擬 scope??梢钥吹?,Scope 類包含了以下4個(gè)屬性:
- variables:當(dāng)前環(huán)境下存在的變量
- scopeType:當(dāng)前環(huán)境的type
- parent:當(dāng)前環(huán)境的父環(huán)境
- shared:有些時(shí)候不需要重復(fù)構(gòu)造子環(huán)境,故用此標(biāo)識(shí)
接下來我們看下該如何在環(huán)境中聲明變量
首先構(gòu)造一個(gè)類來模擬變量
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; } }
這個(gè)類中有兩個(gè)屬性和兩個(gè)方法
- kind 用于標(biāo)識(shí)該變量是通過 var、let 還是 const 聲明
- value 表示該變量的值
- $get 和 $set 分別用于獲取和設(shè)置該變量的值
有了 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 的聲明方式稍微有一點(diǎn)差異,因?yàn)閖s中,除了在 function 中,用var 聲明的變量是會(huì)被聲明到父級(jí)作用域的(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 }
除了聲明,我們還需要一個(gè)尋找變量的方法,該方法會(huì)從當(dāng)前環(huán)境開始,一直沿著作用域鏈,找到最外層的環(huán)境為止。因此,代碼實(shí)現(xiàn)如下
$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; }
以上,一個(gè)基本的javascript元循環(huán)求值器就完成了
最后
大家可以在 codesandbox 在線體驗(yàn)一下。
完整的項(xiàng)目地址是:nvwajs,歡迎鞭策,歡迎star。
參考
《SICP》
微信小程序也要強(qiáng)行熱更代碼,鵝廠不服你來肛我呀
到此這篇關(guān)于編寫一個(gè)javascript元循環(huán)求值器的方法的文章就介紹到這了,更多相關(guān)javascript元循環(huán)求值器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入理解JavaScript系列(19):求值策略(Evaluation strategy)詳解
- JavaScript惰性求值的一種實(shí)現(xiàn)方法示例
- JS實(shí)現(xiàn)可針對(duì)算術(shù)表達(dá)式求值的計(jì)算器功能示例
- JavaScript數(shù)據(jù)結(jié)構(gòu)中棧的應(yīng)用之表達(dá)式求值問題詳解
- JS閉包與延遲求值用法示例
- javascript 運(yùn)算數(shù)的求值順序
- JavaScript+HTML實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)
- JavaScript中的this指向問題詳解
- 帶你徹底理解JavaScript中的原型對(duì)象
- 詳細(xì)討論JavaScript中的求值策略
相關(guān)文章
GitHub上一些實(shí)用的JavaScript的文件壓縮解壓縮庫推薦
這篇文章主要介紹了GitHub上一些實(shí)用的JavaScript的文件壓縮解壓縮庫推薦,推薦的這幾個(gè)都是支持zip格式的,需要的朋友可以參考下2016-03-03javascript鼠標(biāo)滑過顯示二級(jí)菜單特效
這篇文章主要為大家詳細(xì)介紹了javascript鼠標(biāo)滑過顯示二級(jí)菜單特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07JavaScript實(shí)現(xiàn)復(fù)選框全選功能
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)復(fù)選框全選功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04使用js實(shí)現(xiàn)按鈕控制文本框加1減1應(yīng)用于小時(shí)+分鐘
正如標(biāo)題所言使用js實(shí)現(xiàn)按鈕控制文本框加1減1,此類主要應(yīng)用于小時(shí)+分鐘,下面有個(gè)不錯(cuò)的示例,喜歡的朋友可以參考下2013-12-12Three.js實(shí)現(xiàn)繪制字體模型示例代碼
最近在學(xué)習(xí)three.js,這篇文章屬于系列文章,下面這篇文章主要給大家介紹了關(guān)于Three.js如何繪制字體模型的相關(guān)資料,通過文中介紹的方法實(shí)現(xiàn)的效果非常的贊,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09使用JavaScript實(shí)現(xiàn)一個(gè)錄屏插件
不知道大家平時(shí)都是使用什么錄屏軟件呢,有沒有想過只用JavaScript我們也可以快速實(shí)現(xiàn)一個(gè)錄屏插件呢,感興趣的小伙伴就跟隨小編一起學(xué)習(xí)一下吧2024-10-10