編寫一個javascript元循環(huán)求值器的方法
在上一篇文章中,我們通過AST完成了微信小程序組件的多端編譯,在這篇文章中,讓我們更深入一點,通過AST完成一個javascript元循環(huán)求值器
結(jié)構(gòu)
一個元循環(huán)求值器,完整的應該包含以下內(nèi)容:
- tokenizer:對代碼文本進行詞法和語法分析,將代碼分割成若干個token
- parser:根據(jù)token,生成AST樹
- evaluate:根據(jù)AST樹節(jié)點的type,執(zhí)行對應的apply方法
- apply:根據(jù)環(huán)境,執(zhí)行實際的求值計算
- scope:當前代碼執(zhí)行的環(huán)境
代碼目錄
根據(jù)結(jié)構(gòu)看,我將代碼目錄大致拆分為以下幾個文件
- parser
- eval
- scope
tokenizer和parser這兩個過程不是本文的重點,我統(tǒng)一放在了parser中,交由 @babel/parser 來處理。
evaluate和apply這兩個過程我統(tǒng)一放在了eval文件中處理,一會我們重點看下這部分。
scope則放入scope文件。
evaluate-apply
這其實是一個遞歸計算的過程。
首先,evaluate 接收兩個參數(shù),node 當前遍歷的AST樹節(jié)點和 scope 當前環(huán)境。然后,evaluate去根據(jù) node 的 type 屬性,判斷該節(jié)點是什么類型。判斷出類型后,執(zhí)行 apply 去求值這個節(jié)點所代表的表達式。apply 中會再次遞歸的執(zhí)行 evaluate 去計算當前節(jié)點的子節(jié)點。最終,執(zhí)行完整顆AST樹。
我們來看下具體代碼吧
const evaluate = (node: t.Node, scope) => {
const evalFunc = evaluateMap[node.type];
if (!evalFunc) {
throw `${node.loc} ${node.type} 還未實現(xiàn)`;
}
return evalFunc(node, scope);
}
以上就是evaluate具體做的事。
其中,evaluateMap 是目前實現(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} 重復定義`
}
}
},
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)錯誤'
}
},
}
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)錯誤'
}
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)錯誤'
}
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 該如何實現(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)造一個類來模擬 scope??梢钥吹?,Scope 類包含了以下4個屬性:
- variables:當前環(huán)境下存在的變量
- scopeType:當前環(huán)境的type
- parent:當前環(huán)境的父環(huán)境
- shared:有些時候不需要重復構(gòu)造子環(huán)境,故用此標識
接下來我們看下該如何在環(huán)境中聲明變量
首先構(gòu)造一個類來模擬變量
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 分別用于獲取和設(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 的聲明方式稍微有一點差異,因為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
}
除了聲明,我們還需要一個尋找變量的方法,該方法會從當前環(huán)境開始,一直沿著作用域鏈,找到最外層的環(huán)境為止。因此,代碼實現(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;
}
以上,一個基本的javascript元循環(huán)求值器就完成了
最后
大家可以在 codesandbox 在線體驗一下。
完整的項目地址是:nvwajs,歡迎鞭策,歡迎star。
參考
《SICP》
微信小程序也要強行熱更代碼,鵝廠不服你來肛我呀
到此這篇關(guān)于編寫一個javascript元循環(huán)求值器的方法的文章就介紹到這了,更多相關(guān)javascript元循環(huán)求值器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GitHub上一些實用的JavaScript的文件壓縮解壓縮庫推薦
這篇文章主要介紹了GitHub上一些實用的JavaScript的文件壓縮解壓縮庫推薦,推薦的這幾個都是支持zip格式的,需要的朋友可以參考下2016-03-03
使用js實現(xiàn)按鈕控制文本框加1減1應用于小時+分鐘
正如標題所言使用js實現(xiàn)按鈕控制文本框加1減1,此類主要應用于小時+分鐘,下面有個不錯的示例,喜歡的朋友可以參考下2013-12-12

