编写一个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, 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} 重复定义` } } }, 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] 出现错误' } }, } 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, 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] 出现错误' } 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 这个过程就完了。
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元循环求值器内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!