in packages/transform/src/emit.js [388:776]
Ep.explodeStatement = function(path, labelId) {
const t = util.getTypes();
let stmt = path.node;
let self = this;
let before, after, head;
t.assertStatement(stmt);
if (labelId) {
t.assertIdentifier(labelId);
} else {
labelId = null;
}
// Explode BlockStatement nodes even if they do not contain a yield,
// because we don't want or need the curly braces.
if (t.isBlockStatement(stmt)) {
path.get("body").forEach(function (path) {
self.explodeStatement(path);
});
return;
}
if (!meta.containsLeap(stmt)) {
// Technically we should be able to avoid emitting the statement
// altogether if !meta.hasSideEffects(stmt), but that leads to
// confusing generated code (for instance, `while (true) {}` just
// disappears) and is probably a more appropriate job for a dedicated
// dead code elimination pass.
self.emit(stmt);
return;
}
switch (stmt.type) {
case "ExpressionStatement":
self.explodeExpression(path.get("expression"), true);
break;
case "LabeledStatement":
after = this.loc();
// Did you know you can break from any labeled block statement or
// control structure? Well, you can! Note: when a labeled loop is
// encountered, the leap.LabeledEntry created here will immediately
// enclose a leap.LoopEntry on the leap manager's stack, and both
// entries will have the same label. Though this works just fine, it
// may seem a bit redundant. In theory, we could check here to
// determine if stmt knows how to handle its own label; for example,
// stmt happens to be a WhileStatement and so we know it's going to
// establish its own LoopEntry when we explode it (below). Then this
// LabeledEntry would be unnecessary. Alternatively, we might be
// tempted not to pass stmt.label down into self.explodeStatement,
// because we've handled the label here, but that's a mistake because
// labeled loops may contain labeled continue statements, which is not
// something we can handle in this generic case. All in all, I think a
// little redundancy greatly simplifies the logic of this case, since
// it's clear that we handle all possible LabeledStatements correctly
// here, regardless of whether they interact with the leap manager
// themselves. Also remember that labels and break/continue-to-label
// statements are rare, and all of this logic happens at transform
// time, so it has no additional runtime cost.
self.leapManager.withEntry(
new leap.LabeledEntry(after, stmt.label),
function() {
self.explodeStatement(path.get("body"), stmt.label);
}
);
self.mark(after);
break;
case "WhileStatement":
before = this.loc();
after = this.loc();
self.mark(before);
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
self.leapManager.withEntry(
new leap.LoopEntry(after, before, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.jump(before);
self.mark(after);
break;
case "DoWhileStatement":
let first = this.loc();
let test = this.loc();
after = this.loc();
self.mark(first);
self.leapManager.withEntry(
new leap.LoopEntry(after, test, labelId),
function() { self.explode(path.get("body")); }
);
self.mark(test);
self.jumpIf(self.explodeExpression(path.get("test")), first);
self.mark(after);
break;
case "ForStatement":
head = this.loc();
let update = this.loc();
after = this.loc();
if (stmt.init) {
// We pass true here to indicate that if stmt.init is an expression
// then we do not care about its result.
self.explode(path.get("init"), true);
}
self.mark(head);
if (stmt.test) {
self.jumpIfNot(self.explodeExpression(path.get("test")), after);
} else {
// No test means continue unconditionally.
}
self.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.mark(update);
if (stmt.update) {
// We pass true here to indicate that if stmt.update is an
// expression then we do not care about its result.
self.explode(path.get("update"), true);
}
self.jump(head);
self.mark(after);
break;
case "TypeCastExpression":
return self.explodeExpression(path.get("expression"));
case "ForInStatement":
head = this.loc();
after = this.loc();
let keyIterNextFn = self.makeTempVar();
self.emitAssign(
keyIterNextFn,
t.callExpression(
util.runtimeProperty("keys"),
[self.explodeExpression(path.get("right"))]
)
);
self.mark(head);
let keyInfoTmpVar = self.makeTempVar();
self.jumpIf(
t.memberExpression(
t.assignmentExpression(
"=",
keyInfoTmpVar,
t.callExpression(t.cloneDeep(keyIterNextFn), [])
),
t.identifier("done"),
false
),
after
);
self.emitAssign(
stmt.left,
t.memberExpression(
t.cloneDeep(keyInfoTmpVar),
t.identifier("value"),
false
)
);
self.leapManager.withEntry(
new leap.LoopEntry(after, head, labelId),
function() { self.explodeStatement(path.get("body")); }
);
self.jump(head);
self.mark(after);
break;
case "BreakStatement":
self.emitAbruptCompletion({
type: "break",
target: self.leapManager.getBreakLoc(stmt.label)
});
break;
case "ContinueStatement":
self.emitAbruptCompletion({
type: "continue",
target: self.leapManager.getContinueLoc(stmt.label)
});
break;
case "SwitchStatement":
// Always save the discriminant into a temporary variable in case the
// test expressions overwrite values like context.sent.
let disc = self.emitAssign(
self.makeTempVar(),
self.explodeExpression(path.get("discriminant"))
);
after = this.loc();
let defaultLoc = this.loc();
let condition = defaultLoc;
let caseLocs = [];
// If there are no cases, .cases might be undefined.
let cases = stmt.cases || [];
for (let i = cases.length - 1; i >= 0; --i) {
let c = cases[i];
t.assertSwitchCase(c);
if (c.test) {
condition = t.conditionalExpression(
t.binaryExpression("===", t.cloneDeep(disc), c.test),
caseLocs[i] = this.loc(),
condition
);
} else {
caseLocs[i] = defaultLoc;
}
}
let discriminant = path.get("discriminant");
util.replaceWithOrRemove(discriminant, condition);
self.jump(self.explodeExpression(discriminant));
self.leapManager.withEntry(
new leap.SwitchEntry(after),
function() {
path.get("cases").forEach(function(casePath) {
let i = casePath.key;
self.mark(caseLocs[i]);
casePath.get("consequent").forEach(function (path) {
self.explodeStatement(path);
});
});
}
);
self.mark(after);
if (defaultLoc.value === -1) {
self.mark(defaultLoc);
assert.strictEqual(after.value, defaultLoc.value);
}
break;
case "IfStatement":
let elseLoc = stmt.alternate && this.loc();
after = this.loc();
self.jumpIfNot(
self.explodeExpression(path.get("test")),
elseLoc || after
);
self.explodeStatement(path.get("consequent"));
if (elseLoc) {
self.jump(after);
self.mark(elseLoc);
self.explodeStatement(path.get("alternate"));
}
self.mark(after);
break;
case "ReturnStatement":
self.emitAbruptCompletion({
type: "return",
value: self.explodeExpression(path.get("argument"))
});
break;
case "WithStatement":
throw new Error("WithStatement not supported in generator functions.");
case "TryStatement":
after = this.loc();
let handler = stmt.handler;
let catchLoc = handler && this.loc();
let catchEntry = catchLoc && new leap.CatchEntry(
catchLoc,
handler.param
);
let finallyLoc = stmt.finalizer && this.loc();
let finallyEntry = finallyLoc &&
new leap.FinallyEntry(finallyLoc, after);
let tryEntry = new leap.TryEntry(
self.getUnmarkedCurrentLoc(),
catchEntry,
finallyEntry
);
self.tryEntries.push(tryEntry);
self.updateContextPrevLoc(tryEntry.firstLoc);
self.leapManager.withEntry(tryEntry, function() {
self.explodeStatement(path.get("block"));
if (catchLoc) {
if (finallyLoc) {
// If we have both a catch block and a finally block, then
// because we emit the catch block first, we need to jump over
// it to the finally block.
self.jump(finallyLoc);
} else {
// If there is no finally block, then we need to jump over the
// catch block to the fall-through location.
self.jump(after);
}
self.updateContextPrevLoc(self.mark(catchLoc));
let bodyPath = path.get("handler.body");
let safeParam = self.makeTempVar();
self.clearPendingException(tryEntry.firstLoc, safeParam);
bodyPath.traverse(catchParamVisitor, {
getSafeParam: () => t.cloneDeep(safeParam),
catchParamName: handler.param.name
});
self.leapManager.withEntry(catchEntry, function() {
self.explodeStatement(bodyPath);
});
}
if (finallyLoc) {
self.updateContextPrevLoc(self.mark(finallyLoc));
self.leapManager.withEntry(finallyEntry, function() {
self.explodeStatement(path.get("finalizer"));
});
self.emit(t.returnStatement(t.callExpression(
self.contextProperty("finish"),
[finallyEntry.firstLoc]
)));
}
});
self.mark(after);
break;
case "ThrowStatement":
self.emit(t.throwStatement(
self.explodeExpression(path.get("argument"))
));
break;
case "ClassDeclaration":
self.emit(self.explodeClass(path));
break;
default:
throw new Error(
"unknown Statement of type " +
JSON.stringify(stmt.type));
}
};