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

Commit

Permalink
feat: support for-in loop label
Browse files Browse the repository at this point in the history
  • Loading branch information
axetroy committed Mar 28, 2018
1 parent a2a6b6e commit 8a6feb9
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 25 deletions.
49 changes: 32 additions & 17 deletions src/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,16 +447,16 @@ const visitors: EvaluateMap = {
if (signal.value === labelName) {
break;
}
return new Signal("break", signal.value);
return signal;
} else if (Signal.isContinue(signal)) {
if (signal.value) {
if (signal.value === labelName) {
update();
continue;
}
return new Signal("continue", signal.value);
if (!signal.value) {
continue;
}
continue;
if (signal.value === labelName) {
update();
continue;
}
return signal;
} else if (Signal.isReturn(signal)) {
return signal;
}
Expand Down Expand Up @@ -508,27 +508,42 @@ const visitors: EvaluateMap = {
}
},
ForInStatement(path) {
const { node, scope } = path;
const { node, scope, ctx } = path;
const kind = (node.left as types.VariableDeclaration).kind;
const decl = (node.left as types.VariableDeclaration).declarations[0];
const name = (decl.id as types.Identifier).name;

const labelName: string = ctx.labelName;

const right = evaluate(path.createChild(node.right));

for (const value in right) {
if (Object.hasOwnProperty.call(right, value)) {
const forInScope = scope.createChild("forIn");
forInScope.invasive = true;
forInScope.declare(kind, name, value);
forInScope.isolated = false;
forInScope.declare(kind, name, value);

const result = evaluate(path.createChild(node.body, forInScope));
if (Signal.isBreak(result)) {
break;
} else if (Signal.isContinue(result)) {
continue;
} else if (Signal.isReturn(result)) {
return result;
const signal = evaluate(path.createChild(node.body, forInScope));

if (Signal.isBreak(signal)) {
if (!signal.value) {
break;
}
if (signal.value === labelName) {
break;
}
return signal;
} else if (Signal.isContinue(signal)) {
if (!signal.value) {
continue;
}
if (signal.value === labelName) {
continue;
}
return signal;
} else if (Signal.isReturn(signal)) {
return signal;
}
}
}
Expand Down
29 changes: 21 additions & 8 deletions src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,30 +123,43 @@ export class Scope {

public var(varName: string, value: any): boolean {
// tslint:disable-next-line
let scope: Scope = this;
let targetScope: Scope = this;

while (
scope.parent !== null &&
(scope.type !== "function" && scope.type !== "constructor")
targetScope.parent !== null &&
// function and constructor has own scope
(targetScope.type !== "function" && targetScope.type !== "constructor")
) {
scope = scope.parent;
targetScope = targetScope.parent;
}

const $var = scope.content[varName];
const $var = targetScope.content[varName];
if ($var) {
if ($var.kind !== "var") {
// only cover var with var, not const and let
throw ErrDuplicateDeclard(varName);
} else {
if (this.isTopLevel && this.context[varName]) {
if (targetScope.isTopLevel && targetScope.context[varName]) {
// top level context can not be cover
// here we do nothing
} else {
this.content[varName] = new Var("var", varName, value, this);
// new var cover the old var
targetScope.content[varName] = new Var(
"var",
varName,
value,
targetScope
);
}
}
} else {
this.content[varName] = new Var("var", varName, value, this);
// set the new var
targetScope.content[varName] = new Var(
"var",
varName,
value,
targetScope
);
}
return true;
}
Expand Down
102 changes: 102 additions & 0 deletions test/ecma5/for-in/label.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import test from "ava";
import vm from "../../../src/vm";

test("break with label", t => {
const sandbox: any = vm.createContext({});

const obj: any = vm.runInContext(
`
var obj = {
1: false,
2: false,
3: false
};
loop1:
for (var attr in obj) {
obj[attr] = true;
if (attr % 2 === 0){
break loop1;
}
}
module.exports = obj;
`,
sandbox
);

t.deepEqual(obj, {
1: true,
2: true,
3: false
});
});

test("break with label", t => {
const sandbox: any = vm.createContext({});

const { attr, index }: any = vm.runInContext(
`
var obj = {
1: false,
2: false,
3: false
};
loop1:
for (var attr in obj) {
obj[attr] = true;
loop2:
for (var index in [1,2,3,4]){
if ((index + 1)%3 === 0){
break loop1;
}
}
}
module.exports = {attr, index};
`,
sandbox
);

t.deepEqual(attr, "1");
t.deepEqual(index, "2");
});

test("continue with label", t => {
const sandbox: any = vm.createContext({});

const { attr, index, m }: any = vm.runInContext(
`
var obj = {
1: false,
2: false,
3: false
};
loop1:
for (var attr in obj) {
obj[attr] = true;
loop2:
for (var index in [1,2,3,4]){
if ((index + 1)%3 === 0){
break loop1;
}
loop3:
for (var m in [1, 2, 3, 4]){
if ((m + 1) % 2 === 0){
continue loop2;
}
}
}
}
module.exports = {attr, index,m};
`,
sandbox
);

t.deepEqual(attr, "1");
t.deepEqual(index, "2");
t.deepEqual(m, "3");
});

0 comments on commit 8a6feb9

Please sign in to comment.