Skip to content
This repository has been archived by the owner on Sep 10, 2023. It is now read-only.

Commit

Permalink
fix: fix let/const do not have block scope
Browse files Browse the repository at this point in the history
  • Loading branch information
axetroy committed Mar 26, 2018
1 parent c0db7f0 commit e174064
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 78 deletions.
148 changes: 92 additions & 56 deletions src/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,13 @@ const visitors: EvaluateMap = {
return null;
},
IfStatement(path) {
const newScope = path.scope.createChild("if");
newScope.invasive = true;
if (evaluate(path.createChild(path.node.test, newScope))) {
return evaluate(path.createChild(path.node.consequent, newScope));
const ifScope = path.scope.createChild("if");
ifScope.invasive = true;
ifScope.isolated = false;
if (evaluate(path.createChild(path.node.test, ifScope))) {
return evaluate(path.createChild(path.node.consequent, ifScope));
} else if (path.node.alternate) {
return evaluate(path.createChild(path.node.alternate, newScope));
return evaluate(path.createChild(path.node.alternate, ifScope));
}
},
EmptyStatement(path) {
Expand All @@ -112,22 +113,48 @@ const visitors: EvaluateMap = {
BlockStatement(path) {
const { node: block, scope } = path;

let blockScope: Scope = !scope.isolated
? scope
: scope.createChild("block");

if (scope.isolated) {
blockScope = scope.createChild("block");
blockScope.invasive = true;
} else {
blockScope = scope;
}

blockScope.isolated = true;

// hoisting
for (const node of block.body) {
if (isFunctionDeclaration(node)) {
evaluate(path.createChild(node));
} else if (isVariableDeclaration(node)) {
for (const declaration of node.declarations) {
if (node.kind === "var") {
scope.var((declaration.id as types.Identifier).name, undefined);
if (!scope.isolated && scope.invasive) {
if (scope.parent) {
scope.parent.var(
(declaration.id as types.Identifier).name,
undefined
);
} else {
scope.var((declaration.id as types.Identifier).name, undefined);
}
} else {
scope.var((declaration.id as types.Identifier).name, undefined);
}
}
}
}
}

let tempResult;
for (const node of block.body) {
const result = (tempResult = evaluate(path.createChild(node, scope)));
const result = (tempResult = evaluate(
path.createChild(node, blockScope)
));
if (result instanceof Signal) {
return result;
}
Expand Down Expand Up @@ -303,11 +330,11 @@ const visitors: EvaluateMap = {
const func = visitors.FunctionExpression(path.createChild(node as any));

Object.defineProperties(func, {
length: { value: node.params.length },
length: { value: node.params.length || 0 },
name: { value: functionName }
});

// function declartion can be duplicate
// Function can repeat declaration
scope.var(functionName, func);
}
},
Expand All @@ -334,6 +361,8 @@ const visitors: EvaluateMap = {
break;
}

loopScope.isolated = false;

const result = evaluate(path.createChild(node.body, loopScope));
if (Signal.isBreak(result)) {
break;
Expand Down Expand Up @@ -369,10 +398,11 @@ const visitors: EvaluateMap = {
const declarator: types.VariableDeclarator = node.left.declarations[0];
const varName = (declarator.id as types.Identifier).name;
for (const value of entity) {
const newScope = scope.createChild("forOf");
newScope.invasive = true;
newScope.declare(node.left.kind, varName, value); // define in current scope
evaluate(path.createChild(node.body, newScope));
const forOfScope = scope.createChild("forOf");
forOfScope.invasive = true;
forOfScope.isolated = false;
forOfScope.declare(node.left.kind, varName, value); // define in current scope
evaluate(path.createChild(node.body, forOfScope));
}
} else if (isIdentifier(node.left)) {
/**
Expand All @@ -382,10 +412,10 @@ const visitors: EvaluateMap = {
*/
const varName = node.left.name;
for (const value of entity) {
const newScope = scope.createChild("forOf");
newScope.invasive = true;
const forOfScope = scope.createChild("forOf");
forOfScope.invasive = true;
scope.var(varName, value); // define in parent scope
evaluate(path.createChild(node.body, newScope));
evaluate(path.createChild(node.body, forOfScope));
}
}
},
Expand All @@ -399,12 +429,12 @@ const visitors: EvaluateMap = {

for (const value in right) {
if (Object.hasOwnProperty.call(right, value)) {
const newScope = scope.createChild("forIn");
newScope.invasive = true;

newScope.declare(kind, name, value);
const forInScope = scope.createChild("forIn");
forInScope.invasive = true;
forInScope.declare(kind, name, value);
forInScope.isolated = false;

const result = evaluate(path.createChild(node.body, newScope));
const result = evaluate(path.createChild(node.body, forInScope));
if (Signal.isBreak(result)) {
break;
} else if (Signal.isContinue(result)) {
Expand All @@ -419,9 +449,10 @@ const visitors: EvaluateMap = {
const { node, scope } = path;
// do while don't have his own scope
do {
const newScope = scope.createChild("doWhile");
newScope.invasive = true; // do while循环具有侵入性,定义var的时候,是覆盖父级变量
const result = evaluate(path.createChild(node.body, newScope)); // 先把do的执行一遍
const doWhileScope = scope.createChild("doWhile");
doWhileScope.invasive = true; // do while循环具有侵入性,定义var的时候,是覆盖父级变量
doWhileScope.isolated = false;
const result = evaluate(path.createChild(node.body, doWhileScope)); // 先把do的执行一遍
if (Signal.isBreak(result)) {
break;
} else if (Signal.isContinue(result)) {
Expand All @@ -433,11 +464,12 @@ const visitors: EvaluateMap = {
},
WhileStatement(path) {
const { node, scope } = path;
while (evaluate(path.createChild(node.test))) {
const newScope = scope.createChild("while");
newScope.invasive = true;
const result = evaluate(path.createChild(node.body, newScope));

while (evaluate(path.createChild(node.test))) {
const whileScope = scope.createChild("while");
whileScope.invasive = true;
whileScope.isolated = false;
const result = evaluate(path.createChild(node.body, whileScope)); // 先把do的执行一遍
if (Signal.isBreak(result)) {
break;
} else if (Signal.isContinue(result)) {
Expand All @@ -456,46 +488,50 @@ const visitors: EvaluateMap = {
TryStatement(path) {
const { node, scope } = path;
try {
const newScope = scope.createChild("try");
newScope.invasive = true;
return evaluate(path.createChild(node.block, newScope));
const tryScope = scope.createChild("try");
tryScope.invasive = true;
tryScope.isolated = false;
return evaluate(path.createChild(node.block, tryScope));
} catch (err) {
if (node.handler) {
const param = node.handler.param as types.Identifier;
const newScope = scope.createChild("catch");
newScope.invasive = true;
newScope.const(param.name, err);
return evaluate(path.createChild(node.handler, newScope));
const catchScope = scope.createChild("catch");
catchScope.invasive = true;
catchScope.isolated = false;
catchScope.const(param.name, err);
return evaluate(path.createChild(node.handler, catchScope));
} else {
throw err;
}
} finally {
if (node.finalizer) {
const newScope = scope.createChild("finally");
newScope.invasive = true;
evaluate(path.createChild(node.finalizer, newScope));
const finallyScope = scope.createChild("finally");
finallyScope.invasive = true;
finallyScope.isolated = false;
evaluate(path.createChild(node.finalizer, finallyScope));
}
}
},
SwitchStatement(path) {
const { node, scope } = path;
const discriminant = evaluate(path.createChild(node.discriminant)); // switch的条件
const newScope = scope.createChild("switch");
newScope.invasive = true;
const switchScope = scope.createChild("switch");
switchScope.invasive = true;
switchScope.isolated = false;

let matched = false;
for (const $case of node.cases) {
// 进行匹配相应的 case
if (
!matched &&
(!$case.test ||
discriminant === evaluate(path.createChild($case.test, newScope)))
discriminant === evaluate(path.createChild($case.test, switchScope)))
) {
matched = true;
}

if (matched) {
const result = evaluate(path.createChild($case, newScope));
const result = evaluate(path.createChild($case, switchScope));

if (Signal.isBreak(result)) {
break;
Expand Down Expand Up @@ -663,22 +699,26 @@ const visitors: EvaluateMap = {
FunctionExpression(path) {
const { node, scope } = path;
const func = function functionDeclaration(...args) {
const newScope = scope.createChild("function");
const funcScope = scope.createChild("function");
for (let i = 0; i < node.params.length; i++) {
const param = node.params[i];
if (isIdentifier(param)) {
newScope.const(param.name, args[i]);
funcScope.const(param.name, args[i]);
} else if (isAssignmentPattern(param)) {
// @es2015 default parameters
evaluate(path.createChild(param, newScope, { value: args[i] }));
evaluate(path.createChild(param, funcScope, { value: args[i] }));
} else if (isRestElement(param)) {
// @es2015 rest parameters
evaluate(path.createChild(param, newScope, { value: args.slice(i) }));
evaluate(
path.createChild(param, funcScope, { value: args.slice(i) })
);
}
}
newScope.const("this", this);
newScope.const("arguments", arguments);
const result = evaluate(path.createChild(node.body, newScope));
funcScope.const("this", this);
funcScope.const("arguments", arguments);
funcScope.isolated = false;

const result = evaluate(path.createChild(node.body, funcScope));
if (result instanceof Signal) {
return result.value;
} else {
Expand All @@ -687,12 +727,8 @@ const visitors: EvaluateMap = {
};

Object.defineProperties(func, {
length: {
value: node.params.length
},
name: {
value: node.id ? node.id.name : "" // Anonymous function
}
length: { value: node.params.length },
name: { value: node.id ? node.id.name : "" } // Anonymous function
});

return func;
Expand Down Expand Up @@ -1122,7 +1158,7 @@ const visitors: EvaluateMap = {
const { ctx } = path;
const { SuperClass, ClassConstructor, ClassEntity } = ctx;
const classScope: Scope = ctx.classScope;
const ClassBodyPath = path.$findParent("ClassBody");
const ClassBodyPath = path.findParent("ClassBody");
// make sure it include in ClassDeclaration
if (!ClassBodyPath) {
throw new Error("super() only can use in ClassDeclaration");
Expand Down
4 changes: 2 additions & 2 deletions src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ export class Path<T extends Node> {
* @returns {(Path<Node> | null)}
* @memberof Path
*/
public $findParent(type: string): Path<Node> | null {
public findParent(type: string): Path<Node> | null {
if (this.parent) {
return this.parent.node.type === type
? this.parent
: this.parent.$findParent(type);
: this.parent.findParent(type);
} else {
return null;
}
Expand Down
2 changes: 2 additions & 0 deletions src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class Scope {
// scope context
public context: Context;

public isolated: boolean = true; // 孤立的作用域,表示在BlockStatement不会创建新的作用域,默认会创建

// scope var
private content: { [key: string]: Var<any> } = {};

Expand Down
3 changes: 2 additions & 1 deletion src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export type ScopeType =
| "try"
| "catch"
| "finally"
| "class";
| "class"
| "block";

export type Kind = "const" | "var" | "let";

Expand Down
1 change: 1 addition & 0 deletions src/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Scope } from "./scope";
export function runInContext(code: string, context: Context) {
const scope = new Scope("root", null);
scope.isTopLevel = true;
scope.invasive = true;
scope.const("this", this);
scope.setContext(context);

Expand Down
File renamed without changes.
Loading

0 comments on commit e174064

Please sign in to comment.