「 giao-js 」用js寫一個js解釋器

作者:null仔
來源:SegmentFault 思否社區(qū)
前言
在這篇文章中,我們將通過 JS 構(gòu)建我們自己的 JS 解釋器,用 JS 寫 JS,這聽起來很奇怪,盡管如此,這樣做我們將更熟悉 JS,也可以學(xué)習(xí) JS 引擎是如何工作的!
什么是解釋器 (Interpreter) ?
解釋器是在運(yùn)行時運(yùn)行的語言求值器,它動態(tài)地執(zhí)行程序的源代碼。
解釋器解析源代碼,從源代碼生成 AST(抽象語法樹),遍歷 AST 并逐個計(jì)算它們。
解釋器 (Interpreter) 工作原理

詞法分析 (Tokenization) 語法解析 (Parsing) 求值 (Evaluating)
詞法分析 (Tokenization)
Javascript?is?the?best?language?in?the?world
+----------------------------------------------------------+
|?Javascript?|?is?|?the?|?best?|?language?|?in?|the?|world?|
+----------------------------------------------------------+
var?a?=?1;
[
??("var":?"keyword"),
??("a":?"identifier"),
??("=":?"assignment"),
??("1":?"literal"),
??(";":?"separator"),
];
語法解析 (Parsing)
+------------------------------------------+
|?Javascript?|?is?|?the?|?best?|?language??|
+------------------------------------------+
"Javascript":?Subject
"is?the?best?language":?Predicate
"language":?Object
Subject(Noun)?->?Predicate?->?Object
{
??type:?"Program",
??body:?[
????{
??????type:?"VariableDeclaration",
??????declarations:?[
????????{
??????????type:?"VariableDeclarator",
??????????id:?{
????????????type:?"Identifier",
????????????name:?"sum"
??????????},
??????????init:?{
????????????type:?"Literal",
????????????value:?30,
????????????raw:?"30"
??????????}
????????}
??????],
??????kind:?"var"
????}
??],
}
求值階段 (Evaluating)
1?+?2
|
????|
????v
+---+??+---+
|?1?|??|?2?|
+---+??+---+
??\?????/
???\???/
????\?/
???+---+
???|?+?|
???+---+
{
????lhs:?1,
????op:?'+'.
????rhs:?2
}
{
??type:?"Program",
??body:?[
????{
??????type:?"ExpressionStatement",
??????expression:?{
????????type:?"BinaryExpression",
????????left:?{
??????????type:?"Literal",
??????????value:?1,
??????????raw:?"1"
????????},
????????operator:?"+",
????????right:?{
??????????type:?"Literal",
??????????value:?2,
??????????raw:?"2"
????????}
??????}
????}
??],
}
實(shí)踐
實(shí)踐準(zhǔn)備
Acorn.js
A tiny, fast JavaScript parser, written completely in JavaScript. 一個完全使用 javascript 實(shí)現(xiàn)的,小型且快速的 javascript 解析器
The Estree Spec
Jest
Rollup
項(xiàng)目初始化
// visitor.ts 創(chuàng)建一個Visitor類,并提供一個方法操作ES節(jié)點(diǎn)。
import?*?as?ESTree?from?"estree";
class?Visitor?{
??visitNode(node:?ESTree.Node)?{
????//?...
??}
}
export?default?Visitor;// interpreter.ts 創(chuàng)建一個Interpreter類,用于運(yùn)行ES節(jié)點(diǎn)樹。
//?創(chuàng)建一個Visitor實(shí)例,并使用該實(shí)例來運(yùn)行ESTree節(jié)點(diǎn)
import?Visitor?from?"./visitor";
import?*?as?ESTree?from?"estree";
class?Interpreter?{
??private?visitor:?Visitor;
??constructor(visitor:?Visitor)?{
????this.visitor?=?visitor;
??}
??interpret(node:?ESTree.Node)?{
????this.visitor.visitNode(node);
??}
}
export?default?Interpreter;// vm.ts 對外暴露run方法,并使用acorn code->ast后,交給Interpreter實(shí)例進(jìn)行解釋。
const?acorn?=?require("acorn");
import?Visitor?from?"./visitor";
import?Interpreter?from?"./interpreter";
const?jsInterpreter?=?new?Interpreter(new?Visitor());
export?function?run(code:?string)?{
??const?root?=?acorn.parse(code,?{
????ecmaVersion:?8,
????sourceType:?"script",
??});
??return?jsInterpreter.interpret(root);
}
實(shí)踐第 1 彈: 1+1= ?

Program
interface?Program?{
??type:?"Program";
??sourceType:?"script"?|?"module";
??body:?Array;
??comments?:?Array;
}
ExpressionStatement
interface?ExpressionStatement?{
??type:?"ExpressionStatement";
??expression:?Expression;
}
BinaryExpression
本節(jié)實(shí)現(xiàn)的重點(diǎn),簡單理解,我們只要拿到 operator 操作符的類型并實(shí)現(xiàn),然后對 left,right 值進(jìn)行求值即可。
interface?BinaryExpression?{
??type:?"BinaryExpression";
??operator:?BinaryOperator;
??left:?Expression;
??right:?Expression;
}
Literal
type?Literal?=?SimpleLiteral?|?RegExpLiteral;
interface?SimpleLiteral?{
??type:?"Literal";
??value:?string?|?boolean?|?number?|?null;
??raw?:?string;
}
interface?RegExpLiteral?{
??type:?"Literal";
??value?:?RegExp?|?null;
??regex:?{
????pattern:?string;
????flags:?string;
??};
??raw?:?string;
}
//?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法
import?Scope?from?"../scope";
import?*?as?ESTree?from?"estree";
import?{?AstPath?}?from?"../types/index";
const?es5?=?{
??//?根節(jié)點(diǎn)的處理很簡單,我們只要對它的body屬性進(jìn)行遍歷,然后訪問該節(jié)點(diǎn)即可。
??Program(node:?ESTree.Program)?{
????node.body.forEach((bodyNode)?=>?this.visitNode(bodyNode));
??},
??//?表達(dá)式語句節(jié)點(diǎn)的處理,同樣訪問expression 屬性即可。
??ExpressionStatement(node:?ESTree.ExpressionStatement>)?{
????return?this.visitNode(node.expression);
??},
??//?字面量節(jié)點(diǎn)處理直接求值,這里對正則表達(dá)式類型進(jìn)行了特殊處理,其他類型直接返回value值即可。
??Literal(node:?ESTree.Literal>)?{
????if?((node).regex)?{
??????const?{?pattern,?flags?}?=?(node).regex;
??????return?new?RegExp(pattern,?flags);
????}?else?return?node.value;
??},
??//?二元運(yùn)算表達(dá)式節(jié)點(diǎn)處理
??//?對left/node兩個節(jié)點(diǎn)(Literal)進(jìn)行求值,然后實(shí)現(xiàn)operator類型運(yùn)算,返回結(jié)果。
??BinaryExpression(node:?ESTree.BinaryExpression>)?{
????const?leftNode?=?this.visitNode(node.left);
????const?operator?=?node.operator;
????const?rightNode?=?this.visitNode(node.right);
????return?{
??????"+":?(l,?r)?=>?l?+?r,
??????"-":?(l,?r)?=>?l?-?r,
??????"*":?(l,?r)?=>?l?*?r,
??????"/":?(l,?r)?=>?l?/?r,
??????"%":?(l,?r)?=>?l?%?r,
??????"<":?(l,?r)?=>?l???????">":?(l,?r)?=>?l?>?r,
??????"<=":?(l,?r)?=>?l?<=?r,
??????">=":?(l,?r)?=>?l?>=?r,
??????"==":?(l,?r)?=>?l?==?r,
??????"===":?(l,?r)?=>?l?===?r,
??????"!=":?(l,?r)?=>?l?!=?r,
??????"!==":?(l,?r)?=>?l?!==?r,
????}[operator](leftNode,?rightNode);
??},
};
export?default?es5;
//?visitor.ts
import?Scope?from?"./scope";
import?*?as?ESTree?from?"estree";
import?es5?from?"./standard/es5";
const?VISITOR?=?{
??...es5,
};
class?Visitor?{
??//?實(shí)現(xiàn)訪問節(jié)點(diǎn)方法,通過節(jié)點(diǎn)類型訪問對應(yīng)的節(jié)點(diǎn)方法
??visitNode(node:?ESTree.Node)?{
????return?{
??????visitNode:?this.visitNode,
??????...VISITOR,
????}[node.type](node);
??}
}
export?default?Visitor;
//?variable.ts
export?enum?Kind?{
??var?=?"var",
??let?=?"let",
??const?=?"const",
}
export?type?KindType?=?"var"?|?"let"?|?"const";
export?class?Variable?{
??private?_value:?any;
??constructor(public?kind:?Kind,?val:?any)?{
????this._value?=?val;
??}
??get?value()?{
????return?this._value;
??}
??set?value(val:?any)?{
????this._value?=?val;
??}
}
import?{?Variable,?Kind,?KindType?}?from?"./variable";
class?Scope?{
??//?父作用域
??private?parent:?Scope?|?null;
??//?當(dāng)前作用域
??private?targetScope:?{?[key:?string]:?any?};
??constructor(public?readonly?type,?parent?:?Scope)?{
????this.parent?=?parent?||?null;
????this.targetScope?=?new?Map();
??}
??//?是否已定義
??private?hasDefinition(rawName:?string):?boolean?{
????return?Boolean(this.search(rawName));
??}
??//?var類型變量定義
??public?defineVar(rawName:?string,?value:?any)?{
????let?scope:?Scope?=?this;
????//?如果不是全局作用域且不是函數(shù)作用域,找到全局作用域,存儲變量
????//?這里就是我們常說的Hoisting?(變量提升)
????while?(scope.parent?&&?scope.type?!==?"function")?{
??????scope?=?scope.parent;
????}
????//?存儲變量
????scope.targetScope.set(rawName,?new?Variable(Kind.var,?value));
??}
??//?let類型變量定義
??public?defineLet(rawName:?string,?value:?any)?{
????this.targetScope.set(rawName,?new?Variable(Kind.let,?value));
??}
??//?const類型變量定義
??public?defineConst(rawName:?string,?value:?any)?{
????this.targetScope.set(rawName,?new?Variable(Kind.const,?value));
??}
??//?作用域鏈實(shí)現(xiàn),向上查找標(biāo)識符
??public?search(rawName:?string):?Variable?|?null?{
????if?(this.targetScope.get(rawName))?{
??????return?this.targetScope.get(rawName);
????}?else?if?(this.parent)?{
??????return?this.parent.search(rawName);
????}?else?{
??????return?null;
????}
??}
??//?變量聲明方法,變量已定義則拋出語法錯誤異常
??public?declare(kind:?Kind?|?KindType,?rawName:?string,?value:?any)?{
????if?(this.hasDefinition(rawName))?{
??????console.error(
????????`Uncaught?SyntaxError:?Identifier?'${rawName}'?has?already?been?declared`
??????);
??????return?true;
????}
????return?{
??????[Kind.var]:?()?=>?this.defineVar(rawName,?value),
??????[Kind.let]:?()?=>?this.defineLet(rawName,?value),
??????[Kind.const]:?()?=>?this.defineConst(rawName,?value),
????}[kind]();
??}
}
export?default?Scope;
實(shí)踐第 3 彈: var age = 18

VariableDeclaration
declarations 表示聲明的多個描述,因?yàn)槲覀兛梢赃@樣:let a = 1, b = 2;。
interface?VariableDeclaration?{
??type:?"VariableDeclaration";
??declarations:?Array;
??kind:?"var"?|?"let"?|?"const";
}
VariableDeclarator
interface?VariableDeclarator?{
??type:?"VariableDeclarator";
??id:?Pattern;
??init?:?Expression?|?null;
}
Identifier
interface?Identifier?{
??type:?"Identifier";
??name:?string;
}
//?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法
import?Scope?from?"../scope";
import?*?as?ESTree?from?"estree";
type?AstPath?=?{
??node:?T;
??scope:?Scope;
};
const?es5?=?{
??//?...
??//?這里我們定義了astPath,新增了scope作用域參數(shù)
??VariableDeclaration(astPath:?AstPath)?{
????const?{?node,?scope?}?=?astPath;
????const?{?declarations,?kind?}?=?node;
????//?上面提到,生聲明可能存在多個描述(let?a?=?1,?b?=?2;),所以我們這里對它進(jìn)行遍歷:
????//?這里遍歷出來的每個item是VariableDeclarator節(jié)點(diǎn)
????declarations.forEach((declar)?=>?{
??????const?{?id,?init?}?=?declar;
??????//?變量名稱節(jié)點(diǎn),這里拿到的是age
??????const?key?=?(id).name;
??????//?判斷變量是否進(jìn)行了初始化???查找init節(jié)點(diǎn)值(Literal類型直接返回值:18)?:?置為undefined;
??????const?value?=?init???this.visitNode(init,?scope)?:?undefined;
??????//?根據(jù)不同的kind(var/const/let)聲明進(jìn)行定義,即var?age?=?18
??????scope.declare(kind,?key,?value);
????});
??},
??//?標(biāo)識符節(jié)點(diǎn),我們只要通過訪問作用域,訪問該值即可。
??Identifier(astPath:?AstPath)?{
????const?{?node,?scope?}?=?astPath;
????const?name?=?node.name;
????//?walk?identifier
????//?這個例子中查找的是age變量
????const?variable?=?scope.search(name);
????//?返回的是定義的變量對象(age)的值,即18
????if?(variable)?return?variable.value;
??},
};
export?default?es5;
實(shí)踐第 4 彈: module.exports = 6

AssignmentExpression
interface?AssignmentExpression?{
??type:?"AssignmentExpression";
??operator:?AssignmentOperator;
??left:?Pattern?|?MemberExpression;
??right:?Expression;
}
MemberExpression
interface?MemberExpression?{
??type:?"MemberExpression";
??object:?Expression?|?Super;
??property:?Expression;
??computed:?boolean;
??optional:?boolean;
}
import?Scope?from?"./scope";
import?Visitor?from?"./visitor";
import?*?as?ESTree?from?"estree";
class?Interpreter?{
??private?scope:?Scope;
??private?visitor:?Visitor;
??constructor(visitor:?Visitor)?{
????this.visitor?=?visitor;
??}
??interpret(node:?ESTree.Node)?{
????this.createScope();
????this.visitor.visitNode(node,?this.scope);
????return?this.exportResult();
??}
??createScope()?{
????//?創(chuàng)建全局作用域
????this.scope?=?new?Scope("root");
????//?定義module.exports
????const?$exports?=?{};
????const?$module?=?{?exports:?$exports?};
????this.scope.defineConst("module",?$module);
????this.scope.defineVar("exports",?$exports);
??}
??//?模擬commonjs,對外暴露結(jié)果
??exportResult()?{
????//?查找module變量
????const?moduleExport?=?this.scope.search("module");
????//?返回module.exports值
????return?moduleExport???moduleExport.value.exports?:?null;
??}
}
export?default?Interpreter;
//?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法
import?Scope?from?"../scope";
import?*?as?ESTree?from?"estree";
type?AstPath?=?{
??node:?T;
??scope:?Scope;
};
const?es5?=?{
??//?...
??//?這里我們定義了astPath,新增了scope作用域參數(shù)
??MemberExpression(astPath:?AstPath)?{
????const?{?node,?scope?}?=?astPath;
????const?{?object,?property,?computed?}?=?node;
????//?property?是表示屬性名稱,computed?如果為?false,property?應(yīng)該為一個?Identifier?節(jié)點(diǎn),如果?computed?屬性為?true,即?property?是一個?Expression?節(jié)點(diǎn)
????//?這里我們拿到的是exports這個key值,即屬性名稱
????const?prop?=?computed
????????this.visitNode(property,?scope)
??????:?(property).name;
????//?object?表示對象,這里為module,對module進(jìn)行節(jié)點(diǎn)訪問
????const?obj?=?this.visitNode(object,?scope);
????//?訪問module.exports值
????return?obj[prop];
??},
??//?賦值表達(dá)式節(jié)點(diǎn)
??(astPath:?AstPath)?{
????const?{?node,?scope?}?=?astPath;
????const?{?left,?operator,?right?}?=?node;
????let?assignVar;
????//?LHS?處理
????if?(left.type?===?"Identifier")?{
??????//?標(biāo)識符類型?直接查找
??????const?value?=?scope.search(left.name);
??????assignVar?=?value;
????}?else?if?(left.type?===?"MemberExpression")?{
??????//?成員表達(dá)式類型,處理方式跟上面差不多,不同的是這邊需要自定義一個變量對象的實(shí)現(xiàn)
??????const?{?object,?property,?computed?}?=?left;
??????const?obj?=?this.visitNode(object,?scope);
??????const?key?=?computed
??????????this.visitNode(property,?scope)
????????:?(property).name;
??????assignVar?=?{
????????get?value()?{
??????????return?obj[key];
????????},
????????set?value(v)?{
??????????obj[key]?=?v;
????????},
??????};
????}
????//?RHS
????//?不同操作符處理,查詢到right節(jié)點(diǎn)值,對left節(jié)點(diǎn)進(jìn)行賦值。
????return?{
??????"=":?(v)?=>?{
????????assignVar.value?=?v;
????????return?v;
??????},
??????"+=":?(v)?=>?{
????????const?value?=?assignVar.value;
????????assignVar.value?=?v?+?value;
????????return?assignVar.value;
??????},
??????"-=":?(v)?=>?{
????????const?value?=?assignVar.value;
????????assignVar.value?=?value?-?v;
????????return?assignVar.value;
??????},
??????"*=":?(v)?=>?{
????????const?value?=?assignVar.value;
????????assignVar.value?=?v?*?value;
????????return?assignVar.value;
??????},
??????"/=":?(v)?=>?{
????????const?value?=?assignVar.value;
????????assignVar.value?=?value?/?v;
????????return?assignVar.value;
??????},
??????"%=":?(v)?=>?{
????????const?value?=?assignVar.value;
????????assignVar.value?=?value?%?v;
????????return?assignVar.value;
??????},
????}[operator](this.visitNode(right,?scope));
??},
};
export?default?es5;
//?__test__/es5.test.ts
import?{?run?}?from?"../src/vm";
describe("giao-js?es5",?()?=>?{
??test("assign",?()?=>?{
????expect(
??????run(`
??????module.exports?=?6;
????`)
????).toBe(6);
??});
}

實(shí)踐第 5 彈: for 循環(huán)
var?result?=?0;
for?(var?i?=?0;?i?5;?i++)?{
??result?+=?2;
}
module.exports?=?result;

ForStatement
這三個屬性都可以為 null,即 for(;;){}。
interface?ForStatement?{
??type:?"ForStatement";
??init?:?VariableDeclaration?|?Expression?|?null;
??test?:?Expression?|?null;
??update?:?Expression?|?null;
??body:?Statement;
}
UpdateExpression
interface?UpdateExpression?{
??type:?"UpdateExpression";
??operator:?UpdateOperator;
??argument:?Expression;
??prefix:?boolean;
}
BlockStatement
interface?BlockStatement?{
??0;
??type:?"BlockStatement";
??body:?Array;
??innerComments?:?Array;
}
//?standard/es5.ts?實(shí)現(xiàn)以上節(jié)點(diǎn)方法
import?Scope?from?"../scope";
import?*?as?ESTree?from?"estree";
type?AstPath?=?{
??node:?T;
??scope:?Scope;
};
const?es5?=?{
??//?...
??//?for?循環(huán)語句節(jié)點(diǎn)
??ForStatement(astPath:?AstPath)?{
????const?{?node,?scope?}?=?astPath;
????const?{?init,?test,?update,?body?}?=?node;
????//?這里需要注意的是需要模擬創(chuàng)建一個塊級作用域
????//?前面Scope類實(shí)現(xiàn),var聲明在塊作用域中會被提升,const/let不會
????const?forScope?=?new?Scope("block",?scope);
????for?(
??????//?初始化值
??????//?VariableDeclaration
??????init???this.visitNode(init,?forScope)?:?null;
??????//?循環(huán)判斷條件(BinaryExpression)
??????//?二元運(yùn)算表達(dá)式,之前已實(shí)現(xiàn),這里不再細(xì)說
??????test???this.visitNode(test,?forScope)?:?true;
??????//?變量更新語句(UpdateExpression)
??????update???this.visitNode(update,?forScope)?:?null
????)?{
??????//?BlockStatement
??????this.visitNode(body,?forScope);
????}
??},
??//?update?運(yùn)算表達(dá)式節(jié)點(diǎn)
??// update 運(yùn)算表達(dá)式節(jié)點(diǎn),即?++/--,和一元運(yùn)算符類似,只是 operator 指向的節(jié)點(diǎn)對象類型不同,這里是 update 運(yùn)算符。
??UpdateExpression(astPath:?AstPath)?{
????const?{?node,?scope?}?=?astPath;
????// update 運(yùn)算符,值為?++?或?--,配合 update 表達(dá)式節(jié)點(diǎn)的 prefix 屬性來表示前后。
????const?{?prefix,?argument,?operator?}?=?node;
????let?updateVar;
????//?這里需要考慮參數(shù)類型還有一種情況是成員表達(dá)式節(jié)點(diǎn)
????//?例:?for?(var?query={count:0};?query.count?8;?query.count++)
????//?LHS查找
????if?(argument.type?===?"Identifier")?{
??????//?標(biāo)識符類型?直接查找
??????const?value?=?scope.search(argument.name);
??????updateVar?=?value;
????}?else?if?(argument.type?===?"MemberExpression")?{
??????//?成員表達(dá)式的實(shí)現(xiàn)在前面實(shí)現(xiàn)過,這里不再細(xì)說,一樣的套路~
??????const?{?object,?property,?computed?}?=?argument;
??????const?obj?=?this.visitNode(object,?scope);
??????const?key?=?computed
??????????this.visitNode(property,?scope)
????????:?(property).name;
??????updateVar?=?{
????????get?value()?{
??????????return?obj[key];
????????},
????????set?value(v)?{
??????????obj[key]?=?v;
????????},
??????};
????}
????return?{
??????"++":?(v)?=>?{
????????const?result?=?v.value;
????????v.value?=?result?+?1;
????????//?preifx??++i:?i++;
????????return?prefix???v.value?:?result;
??????},
??????"--":?(v)?=>?{
????????const?result?=?v.value;
????????v.value?=?result?-?1;
????????//?preifx??--i:?i--;
????????return?prefix???v.value?:?result;
??????},
????}[operator](updateVar);
??},
??//?塊語句節(jié)點(diǎn)
??//?塊語句的實(shí)現(xiàn)很簡單,模擬創(chuàng)建一個塊作用域,然后遍歷body屬性進(jìn)行訪問即可。
??BlockStatement(astPath:?AstPath)?{
????const?{?node,?scope?}?=?astPath;
????const?blockScope?=?new?Scope("block",?scope);
????const?{?body?}?=?node;
????body.forEach((bodyNode)?=>?{
??????this.visitNode(bodyNode,?blockScope);
????});
??},
};
export?default?es5;
test("test?for?loop",?()?=>?{
??expect(
????run(`
??????var?result?=?0;
??????for?(var?i?=?0;?i?5;?i++)?{
????????result?+=?2;
??????}
??????module.exports?=?result;
????`)
??).toBe(10);
});

var?result?=?0;
for?(var?i?=?0;?i?5;?i++)?{
??result?+=?2;
??break;?//?break,continue,return
}
module.exports?=?result;
結(jié)語

評論
圖片
表情
