switch to vanilla regenerator

This commit is contained in:
Sebastian McKenzie 2014-12-22 21:34:09 +11:00
parent 41f072d112
commit bd255257f1
12 changed files with 3 additions and 2262 deletions

View File

@ -0,0 +1 @@
module.exports = require("regenerator").transform;

View File

@ -1,204 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var assert = require("assert");
var loc = require("../util").loc;
var t = require("../../../../types");
exports.ParenthesizedExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(this.explodeExpression(path.get("expression")));
};
exports.MemberExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.memberExpression(
this.explodeExpression(path.get("object")),
expr.computed ? explodeViaTempVar(null, path.get("property")) : expr.property,
expr.computed
));
};
exports.CallExpression = function (expr, path, explodeViaTempVar, finish) {
var oldCalleePath = path.get("callee");
var newCallee = this.explodeExpression(oldCalleePath);
// If the callee was not previously a MemberExpression, then the
// CallExpression was "unqualified," meaning its `this` object should
// be the global object. If the exploded expression has become a
// MemberExpression, then we need to force it to be unqualified by
// using the (0, object.property)(...) trick; otherwise, it will
// receive the object of the MemberExpression as its `this` object.
if (!t.isMemberExpression(oldCalleePath.node) && t.isMemberExpression(newCallee)) {
newCallee = t.sequenceExpression([
t.literal(0),
newCallee
]);
}
return finish(t.callExpression(
newCallee,
path.get("arguments").map(function (argPath) {
return explodeViaTempVar(null, argPath);
})
));
};
exports.NewExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.newExpression(
explodeViaTempVar(null, path.get("callee")),
path.get("arguments").map(function (argPath) {
return explodeViaTempVar(null, argPath);
})
));
};
exports.ObjectExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.objectExpression(
path.get("properties").map(function (propPath) {
return t.property(
propPath.value.kind,
propPath.value.key,
explodeViaTempVar(null, propPath.get("value"))
);
})
));
};
exports.ArrayExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.arrayExpression(
path.get("elements").map(function (elemPath) {
return explodeViaTempVar(null, elemPath);
})
));
};
exports.SequenceExpression = function (expr, path, explodeViaTempVar, finish, ignoreResult) {
var lastIndex = expr.expressions.length - 1;
var self = this;
var result;
path.get("expressions").each(function (exprPath) {
if (exprPath.name === lastIndex) {
result = self.explodeExpression(exprPath, ignoreResult);
} else {
self.explodeExpression(exprPath, true);
}
});
return result;
};
exports.LogicalExpression = function (expr, path, explodeViaTempVar, finish, ignoreResult) {
var after = loc();
var result;
if (!ignoreResult) {
result = this.makeTempVar();
}
var left = explodeViaTempVar(result, path.get("left"));
if (expr.operator === "&&") {
this.jumpIfNot(left, after);
} else {
assert.strictEqual(expr.operator, "||");
this.jumpIf(left, after);
}
explodeViaTempVar(result, path.get("right"), ignoreResult);
this.mark(after);
return result;
};
exports.ConditionalExpression = function (expr, path, explodeViaTempVar, finish, ignoreResult) {
var elseLoc = loc();
var after = loc();
var test = this.explodeExpression(path.get("test"));
var result;
this.jumpIfNot(test, elseLoc);
if (!ignoreResult) {
result = this.makeTempVar();
}
explodeViaTempVar(result, path.get("consequent"), ignoreResult);
this.jump(after);
this.mark(elseLoc);
explodeViaTempVar(result, path.get("alternate"), ignoreResult);
this.mark(after);
return result;
};
exports.UnaryExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.unaryExpression(
expr.operator,
// Can't (and don't need to) break up the syntax of the argument.
// Think about delete a[b].
this.explodeExpression(path.get("argument")),
!!expr.prefix
));
};
exports.BinaryExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.binaryExpression(
expr.operator,
explodeViaTempVar(null, path.get("left")),
explodeViaTempVar(null, path.get("right"))
));
};
exports.AssignmentExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.assignmentExpression(
expr.operator,
this.explodeExpression(path.get("left")),
this.explodeExpression(path.get("right"))
));
};
exports.UpdateExpression = function (expr, path, explodeViaTempVar, finish) {
return finish(t.updateExpression(
expr.operator,
this.explodeExpression(path.get("argument")),
expr.prefix
));
};
exports.YieldExpression = function (expr, path) {
var after = loc();
var arg = expr.argument && this.explodeExpression(path.get("argument"));
var result;
if (arg && expr.delegate) {
result = this.makeTempVar();
this.emit(t.returnStatement(t.callExpression(
this.contextProperty("delegateYield"), [
arg,
t.literal(result.property.name),
after
]
)));
this.mark(after);
return result;
}
this.emitAssign(this.contextProperty("next"), after);
this.emit(t.returnStatement(arg || null));
this.mark(after);
return this.contextProperty("sent");
};

View File

@ -1,335 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var assert = require("assert");
var types = require("ast-types");
var leap = require("../leap");
var util = require("../util");
var t = require("../../../../types");
var runtimeKeysMethod = util.runtimeProperty("keys");
var loc = util.loc;
exports.ExpressionStatement = function (path) {
this.explodeExpression(path.get("expression"), true);
};
exports.LabeledStatement = function (path, stmt) {
this.explodeStatement(path.get("body"), stmt.label);
};
exports.WhileStatement = function (path, stmt, labelId) {
var before = loc();
var after = loc();
this.mark(before);
this.jumpIfNot(this.explodeExpression(path.get("test")), after);
this.leapManager.withEntry(
new leap.LoopEntry(after, before, labelId),
function () { this.explodeStatement(path.get("body")); }
);
this.jump(before);
this.mark(after);
};
exports.DoWhileStatement = function (path, stmt, labelId) {
var first = loc();
var test = loc();
var after = loc();
this.mark(first);
this.leapManager.withEntry(
new leap.LoopEntry(after, test, labelId),
function () { this.explode(path.get("body")); }
);
this.mark(test);
this.jumpIf(this.explodeExpression(path.get("test")), first);
this.mark(after);
};
exports.ForStatement = function (path, stmt, labelId) {
var head = loc();
var update = loc();
var after = 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.
this.explode(path.get("init"), true);
}
this.mark(head);
if (stmt.test) {
this.jumpIfNot(this.explodeExpression(path.get("test")), after);
} else {
// No test means continue unconditionally.
}
this.leapManager.withEntry(
new leap.LoopEntry(after, update, labelId),
function () { this.explodeStatement(path.get("body")); }
);
this.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.
this.explode(path.get("update"), true);
}
this.jump(head);
this.mark(after);
};
exports.ForInStatement = function (path, stmt, labelId) {
t.assertIdentifier(stmt.left);
var head = loc();
var after = loc();
var keyIterNextFn = this.makeTempVar();
this.emitAssign(
keyIterNextFn,
t.callExpression(
runtimeKeysMethod,
[this.explodeExpression(path.get("right"))]
)
);
this.mark(head);
var keyInfoTmpVar = this.makeTempVar();
this.jumpIf(
t.memberExpression(
t.assignmentExpression(
"=",
keyInfoTmpVar,
t.callExpression(keyIterNextFn, [])
),
t.identifier("done"),
false
),
after
);
this.emitAssign(
stmt.left,
t.memberExpression(
keyInfoTmpVar,
t.identifier("value"),
false
)
);
this.leapManager.withEntry(
new leap.LoopEntry(after, head, labelId),
function () { this.explodeStatement(path.get("body")); }
);
this.jump(head);
this.mark(after);
};
exports.BreakStatement = function (path, stmt) {
this.emitAbruptCompletion({
type: "break",
target: this.leapManager.getBreakLoc(stmt.label)
});
};
exports.ContinueStatement = function (path, stmt) {
this.emitAbruptCompletion({
type: "continue",
target: this.leapManager.getContinueLoc(stmt.label)
});
};
exports.SwitchStatement = function (path, stmt) {
// Always save the discriminant into a temporary variable in case the
// test expressions overwrite values like context.sent.
var disc = this.emitAssign(
this.makeTempVar(),
this.explodeExpression(path.get("discriminant"))
);
var after = loc();
var defaultLoc = loc();
var condition = defaultLoc;
var caseLocs = [];
var self = this;
// If there are no cases, .cases might be undefined.
var cases = stmt.cases || [];
for (var i = cases.length - 1; i >= 0; --i) {
var c = cases[i];
t.assertSwitchCase(c);
if (c.test) {
condition = t.conditionalExpression(
t.binaryExpression("===", disc, c.test),
caseLocs[i] = loc(),
condition
);
} else {
caseLocs[i] = defaultLoc;
}
}
this.jump(this.explodeExpression(
new types.NodePath(condition, path, "discriminant")
));
this.leapManager.withEntry(
new leap.SwitchEntry(after),
function () {
path.get("cases").each(function (casePath) {
var i = casePath.name;
self.mark(caseLocs[i]);
casePath.get("consequent").each(
self.explodeStatement,
self
);
});
}
);
this.mark(after);
if (defaultLoc.value === -1) {
this.mark(defaultLoc);
assert.strictEqual(after.value, defaultLoc.value);
}
};
exports.IfStatement = function (path, stmt) {
var elseLoc = stmt.alternate && loc();
var after = loc();
this.jumpIfNot(
this.explodeExpression(path.get("test")),
elseLoc || after
);
this.explodeStatement(path.get("consequent"));
if (elseLoc) {
this.jump(after);
this.mark(elseLoc);
this.explodeStatement(path.get("alternate"));
}
this.mark(after);
};
exports.ReturnStatement = function (path) {
this.emitAbruptCompletion({
type: "return",
value: this.explodeExpression(path.get("argument"))
});
};
exports.TryStatement = function (path, stmt) {
var after = loc();
var self = this;
var handler = stmt.handler;
if (!handler && stmt.handlers) {
handler = stmt.handlers[0] || null;
}
var catchLoc = handler && loc();
var catchEntry = catchLoc && new leap.CatchEntry(
catchLoc,
handler.param
);
var finallyLoc = stmt.finalizer && loc();
var finallyEntry = finallyLoc && new leap.FinallyEntry(finallyLoc);
var tryEntry = new leap.TryEntry(
this.getUnmarkedCurrentLoc(),
catchEntry,
finallyEntry
);
this.tryEntries.push(tryEntry);
this.updateContextPrevLoc(tryEntry.firstLoc);
this.leapManager.withEntry(tryEntry, function () {
this.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.
this.jump(finallyLoc);
} else {
// If there is no finally block, then we need to jump over the
// catch block to the fall-through location.
this.jump(after);
}
this.updateContextPrevLoc(self.mark(catchLoc));
var bodyPath = path.get("handler", "body");
var safeParam = this.makeTempVar();
this.clearPendingException(tryEntry.firstLoc, safeParam);
var catchScope = bodyPath.scope;
var catchParamName = handler.param.name;
t.assertCatchClause(catchScope.node);
assert.strictEqual(catchScope.lookup(catchParamName), catchScope);
types.visit(bodyPath, {
visitIdentifier: function (path) {
if (t.isReferenced(path.value, path.parentPath.node) &&
path.value.name === catchParamName &&
path.scope.lookup(catchParamName) === catchScope) {
return safeParam;
}
this.traverse(path);
}
});
this.leapManager.withEntry(catchEntry, function () {
this.explodeStatement(bodyPath);
});
}
if (finallyLoc) {
this.updateContextPrevLoc(this.mark(finallyLoc));
this.leapManager.withEntry(finallyEntry, function () {
this.explodeStatement(path.get("finalizer"));
});
this.emit(t.callExpression(
this.contextProperty("finish"),
[finallyEntry.firstLoc]
));
}
});
this.mark(after);
};
exports.ThrowStatement = function (path) {
this.emit(t.throwStatement(
this.explodeExpression(path.get("argument"))
));
};

View File

@ -1,611 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
exports.Emitter = Emitter;
var explodeExpressions = require("./explode-expressions");
var explodeStatements = require("./explode-statements");
var assert = require("assert");
var types = require("ast-types");
var leap = require("../leap");
var meta = require("../meta");
var util = require("../util");
var t = require("../../../../types");
var _ = require("lodash");
var loc = util.loc;
var n = types.namedTypes;
function Emitter(contextId) {
assert.ok(this instanceof Emitter);
t.assertIdentifier(contextId);
// In order to make sure the context object does not collide with
// anything in the local scope, we might have to rename it, so we
// refer to it symbolically instead of just assuming that it will be
// called "context".
this.contextId = contextId;
// An append-only list of Statements that grows each time this.emit is
// called.
this.listing = [];
// A sparse array whose keys correspond to locations in this.listing
// that have been marked as branch/jump targets.
this.marked = [true];
// The last location will be marked when this.getDispatchLoop is
// called.
this.finalLoc = loc();
// A list of all leap.TryEntry statements emitted.
this.tryEntries = [];
// Each time we evaluate the body of a loop, we tell this.leapManager
// to enter a nested loop context that determines the meaning of break
// and continue statements therein.
this.leapManager = new leap.LeapManager(this);
}
// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Emitter.prototype.mark = function (loc) {
t.assertLiteral(loc);
var index = this.listing.length;
if (loc.value === -1) {
loc.value = index;
} else {
// Locations can be marked redundantly, but their values cannot change
// once set the first time.
assert.strictEqual(loc.value, index);
}
this.marked[index] = true;
return loc;
};
Emitter.prototype.emit = function (node) {
if (t.isExpression(node)) node = t.expressionStatement(node);
t.assertStatement(node);
this.listing.push(node);
};
// Shorthand for emitting assignment statements. This will come in handy
// for assignments to temporary variables.
Emitter.prototype.emitAssign = function (lhs, rhs) {
this.emit(this.assign(lhs, rhs));
return lhs;
};
// Shorthand for an assignment statement.
Emitter.prototype.assign = function (lhs, rhs) {
return t.expressionStatement(
t.assignmentExpression("=", lhs, rhs));
};
// Convenience function for generating expressions like context.next,
// context.sent, and context.rval.
Emitter.prototype.contextProperty = function (name, computed) {
return t.memberExpression(
this.contextId,
computed ? t.literal(name) : t.identifier(name),
!!computed
);
};
var volatileContextPropertyNames = {
prev: true,
next: true,
sent: true,
rval: true
};
// A "volatile" context property is a MemberExpression like context.sent
// that should probably be stored in a temporary variable when there's a
// possibility the property will get overwritten.
Emitter.prototype.isVolatileContextProperty = function (expr) {
if (t.isMemberExpression(expr)) {
if (expr.computed) {
// If it's a computed property such as context[couldBeAnything],
// assume the worst in terms of volatility.
return true;
}
if (t.isIdentifier(expr.object) &&
t.isIdentifier(expr.property) &&
expr.object.name === this.contextId.name &&
_.has(volatileContextPropertyNames, expr.property.name)) {
return true;
}
}
return false;
};
// Shorthand for setting context.rval and jumping to `context.stop()`.
Emitter.prototype.stop = function (rval) {
if (rval) {
this.setReturnValue(rval);
}
this.jump(this.finalLoc);
};
Emitter.prototype.setReturnValue = function (valuePath) {
t.assertExpression(valuePath.value);
this.emitAssign(
this.contextProperty("rval"),
this.explodeExpression(valuePath)
);
};
Emitter.prototype.clearPendingException = function (tryLoc, assignee) {
t.assertLiteral(tryLoc);
var catchCall = t.callExpression(
this.contextProperty("catch", true),
[tryLoc]
);
if (assignee) {
this.emitAssign(assignee, catchCall);
} else {
this.emit(catchCall);
}
};
// Emits code for an unconditional jump to the given location, even if the
// exact value of the location is not yet known.
Emitter.prototype.jump = function (toLoc) {
this.emitAssign(this.contextProperty("next"), toLoc);
this.emit(t.breakStatement());
};
// Conditional jump.
Emitter.prototype.jumpIf = function (test, toLoc) {
t.assertExpression(test);
t.assertLiteral(toLoc);
this.emit(t.ifStatement(
test,
t.blockStatement([
this.assign(this.contextProperty("next"), toLoc),
t.breakStatement()
])
));
};
// Conditional jump, with the condition negated.
Emitter.prototype.jumpIfNot = function (test, toLoc) {
t.assertExpression(test);
t.assertLiteral(toLoc);
var negatedTest;
if (t.isUnaryExpression(test) && test.operator === "!") {
// Avoid double negation.
negatedTest = test.argument;
} else {
negatedTest = t.unaryExpression("!", test);
}
this.emit(t.ifStatement(
negatedTest,
t.blockStatement([
this.assign(this.contextProperty("next"), toLoc),
t.breakStatement()
])
));
};
// Returns a unique MemberExpression that can be used to store and
// retrieve temporary values. Since the object of the member expression is
// the context object, which is presumed to coexist peacefully with all
// other local variables, and since we just increment `nextTempId`
// monotonically, uniqueness is assured.
var nextTempId = 0;
Emitter.prototype.makeTempVar = function () {
return this.contextProperty("t" + nextTempId++);
};
Emitter.prototype.getContextFunction = function (id) {
var node = t.functionExpression(
id || null,
[this.contextId],
t.blockStatement([this.getDispatchLoop()]),
false, // Not a generator anymore!
false // Nor an expression.
);
node._aliasFunction = true;
return node;
};
// Turns this.listing into a loop of the form
//
// while (1) switch (context.next) {
// case 0:
// ...
// case n:
// return context.stop();
// }
//
// Each marked location in this.listing will correspond to one generated
// case statement.
Emitter.prototype.getDispatchLoop = function () {
var self = this;
var cases = [];
var current;
// If we encounter a break, continue, or return statement in a switch
// case, we can skip the rest of the statements until the next case.
var alreadyEnded = false;
self.listing.forEach(function (stmt, i) {
if (self.marked.hasOwnProperty(i)) {
cases.push(t.switchCase(t.literal(i), current = []));
alreadyEnded = false;
}
if (!alreadyEnded) {
current.push(stmt);
if (isSwitchCaseEnder(stmt))
alreadyEnded = true;
}
});
// Now that we know how many statements there will be in this.listing,
// we can finally resolve this.finalLoc.value.
this.finalLoc.value = this.listing.length;
cases.push(
t.switchCase(this.finalLoc, [
// Intentionally fall through to the "end" case...
]),
// So that the runtime can jump to the final location without having
// to know its offset, we provide the "end" case as a synonym.
t.switchCase(t.literal("end"), [
// This will check/clear both context.thrown and context.rval.
t.returnStatement(
t.callExpression(this.contextProperty("stop"), [])
)
])
);
return t.whileStatement(
t.literal(true),
t.switchStatement(
t.assignmentExpression(
"=",
this.contextProperty("prev"),
this.contextProperty("next")
),
cases
)
);
};
// See comment above re: alreadyEnded.
function isSwitchCaseEnder(stmt) {
return t.isBreakStatement(stmt) ||
t.isContinueStatement(stmt) ||
t.isReturnStatement(stmt) ||
t.isThrowStatement(stmt);
}
Emitter.prototype.getTryEntryList = function () {
if (this.tryEntries.length === 0) {
// To avoid adding a needless [] to the majority of runtime.wrap
// argument lists, force the caller to handle this case specially.
return null;
}
var lastLocValue = 0;
return t.arrayExpression(
this.tryEntries.map(function (tryEntry) {
var thisLocValue = tryEntry.firstLoc.value;
assert.ok(thisLocValue >= lastLocValue, "try entries out of order");
lastLocValue = thisLocValue;
var ce = tryEntry.catchEntry;
var fe = tryEntry.finallyEntry;
var triple = [
tryEntry.firstLoc,
// The null here makes a hole in the array.
ce ? ce.firstLoc : null
];
if (fe) {
triple[2] = fe.firstLoc;
}
return t.arrayExpression(triple);
})
);
};
// All side effects must be realized in order.
// If any subexpression harbors a leap, all subexpressions must be
// neutered of side effects.
// No destructive modification of AST nodes.
Emitter.prototype.explode = function (path, ignoreResult) {
assert.ok(path instanceof types.NodePath);
var node = path.value;
var self = this;
n.Node.check(node);
if (t.isStatement(node))
return self.explodeStatement(path);
if (t.isExpression(node))
return self.explodeExpression(path, ignoreResult);
if (t.isDeclaration(node))
throw getDeclError(node);
switch (node.type) {
case "Program":
return path.get("body").map(self.explodeStatement, self);
case "VariableDeclarator":
throw getDeclError(node);
// These node types should be handled by their parent nodes
// (ObjectExpression, SwitchStatement, and TryStatement, respectively).
case "Property":
case "SwitchCase":
case "CatchClause":
throw new Error(node.type + " nodes should be handled by their parents");
default:
throw new Error("unknown Node of type " + JSON.stringify(node.type));
}
};
function getDeclError(node) {
return new Error(
"all declarations should have been transformed into " +
"assignments before the Exploder began its work: " +
JSON.stringify(node));
}
Emitter.prototype.explodeStatement = function (path, labelId) {
assert.ok(path instanceof types.NodePath);
var stmt = path.value;
var self = this;
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)) {
return path.get("body").each(
self.explodeStatement,
self
);
}
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;
}
var fn = explodeStatements[stmt.type];
if (fn) {
fn.call(this, path, stmt, labelId);
} else {
throw new Error("unknown Statement of type " + JSON.stringify(stmt.type));
}
};
Emitter.prototype.emitAbruptCompletion = function (record) {
if (!isValidCompletion(record)) {
assert.ok(
false,
"invalid completion record: " + JSON.stringify(record)
);
}
assert.notStrictEqual(
record.type, "normal",
"normal completions are not abrupt"
);
var abruptArgs = [t.literal(record.type)];
if (record.type === "break" || record.type === "continue") {
t.assertLiteral(record.target);
abruptArgs[1] = record.target;
} else if (record.type === "return" || record.type === "throw") {
if (record.value) {
t.assertExpression(record.value);
abruptArgs[1] = record.value;
}
}
this.emit(
t.returnStatement(
t.callExpression(
this.contextProperty("abrupt"),
abruptArgs
)
)
);
};
function isValidCompletion(record) {
var type = record.type;
if (type === "normal") {
return !_.has(record, "target");
}
if (type === "break" || type === "continue") {
return !_.has(record, "value") && t.isLiteral(record.target);
}
if (type === "return" || type === "throw") {
return _.has(record, "value") && !_.has(record, "target");
}
return false;
}
// Not all offsets into emitter.listing are potential jump targets. For
// example, execution typically falls into the beginning of a try block
// without jumping directly there. This method returns the current offset
// without marking it, so that a switch case will not necessarily be
// generated for this offset (I say "not necessarily" because the same
// location might end up being marked in the process of emitting other
// statements). There's no logical harm in marking such locations as jump
// targets, but minimizing the number of switch cases keeps the generated
// code shorter.
Emitter.prototype.getUnmarkedCurrentLoc = function () {
return t.literal(this.listing.length);
};
// The context.prev property takes the value of context.next whenever we
// evaluate the switch statement discriminant, which is generally good
// enough for tracking the last location we jumped to, but sometimes
// context.prev needs to be more precise, such as when we fall
// successfully out of a try block and into a finally block without
// jumping. This method exists to update context.prev to the freshest
// available location. If we were implementing a full interpreter, we
// would know the location of the current instruction with complete
// precision at all times, but we don't have that luxury here, as it would
// be costly and verbose to set context.prev before every statement.
Emitter.prototype.updateContextPrevLoc = function (loc) {
if (loc) {
t.assertLiteral(loc);
if (loc.value === -1) {
// If an uninitialized location literal was passed in, set its value
// to the current this.listing.length.
loc.value = this.listing.length;
} else {
// Otherwise assert that the location matches the current offset.
assert.strictEqual(loc.value, this.listing.length);
}
} else {
loc = this.getUnmarkedCurrentLoc();
}
// Make sure context.prev is up to date in case we fell into this try
// statement without jumping to it. TODO Consider avoiding this
// assignment when we know control must have jumped here.
this.emitAssign(this.contextProperty("prev"), loc);
};
Emitter.prototype.explodeExpression = function (path, ignoreResult) {
assert.ok(path instanceof types.NodePath);
var expr = path.value;
if (expr) {
t.assertExpression(expr);
} else {
return expr;
}
var self = this;
function finish(expr) {
t.assertExpression(expr);
if (ignoreResult) {
self.emit(expr);
} else {
return expr;
}
}
// If the expression does not contain a leap, then we either emit the
// expression as a standalone statement or return it whole.
if (!meta.containsLeap(expr)) {
return finish(expr);
}
// If any child contains a leap (such as a yield or labeled continue or
// break statement), then any sibling subexpressions will almost
// certainly have to be exploded in order to maintain the order of their
// side effects relative to the leaping child(ren).
var hasLeapingChildren = meta.containsLeap.onlyChildren(expr);
// In order to save the rest of explodeExpression from a combinatorial
// trainwreck of special cases, explodeViaTempVar is responsible for
// deciding when a subexpression needs to be "exploded," which is my
// very technical term for emitting the subexpression as an assignment
// to a temporary variable and the substituting the temporary variable
// for the original subexpression. Think of exploded view diagrams, not
// Michael Bay movies. The point of exploding subexpressions is to
// control the precise order in which the generated code realizes the
// side effects of those subexpressions.
function explodeViaTempVar(tempVar, childPath, ignoreChildResult) {
assert.ok(childPath instanceof types.NodePath);
assert.ok(
!ignoreChildResult || !tempVar,
"Ignoring the result of a child expression but forcing it to " +
"be assigned to a temporary variable?"
);
var result = self.explodeExpression(childPath, ignoreChildResult);
if (ignoreChildResult) {
// Side effects already emitted above.
} else if (tempVar || (hasLeapingChildren &&
(self.isVolatileContextProperty(result) ||
meta.hasSideEffects(result)))) {
// If tempVar was provided, then the result will always be assigned
// to it, even if the result does not otherwise need to be assigned
// to a temporary variable. When no tempVar is provided, we have
// the flexibility to decide whether a temporary variable is really
// necessary. In general, temporary assignment is required only
// when some other child contains a leap and the child in question
// is a context property like $ctx.sent that might get overwritten
// or an expression with side effects that need to occur in proper
// sequence relative to the leap.
result = self.emitAssign(
tempVar || self.makeTempVar(),
result
);
}
return result;
}
// If ignoreResult is true, then we must take full responsibility for
// emitting the expression with all its side effects, and we should not
// return a result.
var fn = explodeExpressions[expr.type];
if (fn) {
return fn.call(this, expr, path, explodeViaTempVar, finish, ignoreResult);
} else {
throw new Error("unknown Expression of type " + JSON.stringify(expr.type));
}
};

View File

@ -1,151 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.githut.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var assert = require("assert");
var types = require("ast-types");
var t = require("../../../types");
var _ = require("lodash");
// The hoist function takes a FunctionExpression or FunctionDeclaration
// and replaces any Declaration nodes in its body with assignments, then
// returns a VariableDeclaration containing just the names of the removed
// declarations.
exports.hoist = function (funPath) {
assert.ok(funPath instanceof types.NodePath);
t.assertFunction(funPath.value);
var vars = {};
function varDeclToExpr(vdec, includeIdentifiers) {
t.assertVariableDeclaration(vdec);
var exprs = [];
vdec.declarations.forEach(function (dec) {
vars[dec.id.name] = dec.id;
if (dec.init) {
exprs.push(t.assignmentExpression(
"=", dec.id, dec.init
));
} else if (includeIdentifiers) {
exprs.push(dec.id);
}
});
if (exprs.length === 0)
return null;
if (exprs.length === 1)
return exprs[0];
return t.sequenceExpression(exprs);
}
types.visit(funPath.get("body"), {
visitVariableDeclaration: function (path) {
var expr = varDeclToExpr(path.value, false);
if (expr === null) {
path.replace();
} else {
// We don't need to traverse this expression any further because
// there can't be any new declarations inside an expression.
return t.expressionStatement(expr);
}
// Since the original node has been either removed or replaced,
// avoid traversing it any further.
return false;
},
visitForStatement: function (path) {
var init = path.value.init;
if (t.isVariableDeclaration(init)) {
path.get("init").replace(varDeclToExpr(init, false));
}
this.traverse(path);
},
visitForInStatement: function (path) {
var left = path.value.left;
if (t.isVariableDeclaration(left)) {
path.get("left").replace(varDeclToExpr(left, true));
}
this.traverse(path);
},
visitFunctionDeclaration: function (path) {
var node = path.value;
vars[node.id.name] = node.id;
var assignment = t.expressionStatement(
t.assignmentExpression(
"=",
node.id,
t.functionExpression(
node.id,
node.params,
node.body,
node.generator,
node.expression
)
)
);
if (t.isBlockStatement(path.parent.node)) {
// Insert the assignment form before the first statement in the
// enclosing block.
path.parent.get("body").unshift(assignment);
// Remove the function declaration now that we've inserted the
// equivalent assignment form at the beginning of the block.
path.replace();
} else {
// If the parent node is not a block statement, then we can just
// replace the declaration with the equivalent assignment form
// without worrying about hoisting it.
path.replace(assignment);
}
// Don't hoist variables out of inner functions.
return false;
},
visitFunctionExpression: function () {
// Don't descend into nested function expressions.
return false;
}
});
var paramNames = {};
funPath.get("params").each(function (paramPath) {
var param = paramPath.value;
if (t.isIdentifier(param)) {
paramNames[param.name] = param;
} else {
// Variables declared by destructuring parameter patterns will be
// harmlessly re-declared.
}
});
var declarations = [];
Object.keys(vars).forEach(function (name) {
if (!_.has(paramNames, name)) {
declarations.push(t.variableDeclarator(vars[name], null));
}
});
if (declarations.length === 0) {
return null; // Be sure to handle this case!
}
return t.variableDeclaration("var", declarations);
};

View File

@ -1,3 +0,0 @@
var t = require("../../../types");
exports.ast = require("./visit").transform;

View File

@ -1,163 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
exports.FunctionEntry = FunctionEntry;
exports.FinallyEntry = FinallyEntry;
exports.SwitchEntry = SwitchEntry;
exports.LeapManager = LeapManager;
exports.CatchEntry = CatchEntry;
exports.LoopEntry = LoopEntry;
exports.TryEntry = TryEntry;
var assert = require("assert");
var util = require("util");
var t = require("../../../types");
var inherits = util.inherits;
function Entry() {
assert.ok(this instanceof Entry);
}
function FunctionEntry(returnLoc) {
Entry.call(this);
t.assertLiteral(returnLoc);
this.returnLoc = returnLoc;
}
inherits(FunctionEntry, Entry);
function LoopEntry(breakLoc, continueLoc, label) {
Entry.call(this);
t.assertLiteral(breakLoc);
t.assertLiteral(continueLoc);
if (label) {
t.assertIdentifier(label);
} else {
label = null;
}
this.breakLoc = breakLoc;
this.continueLoc = continueLoc;
this.label = label;
}
inherits(LoopEntry, Entry);
function SwitchEntry(breakLoc) {
Entry.call(this);
t.assertLiteral(breakLoc);
this.breakLoc = breakLoc;
}
inherits(SwitchEntry, Entry);
function TryEntry(firstLoc, catchEntry, finallyEntry) {
Entry.call(this);
t.assertLiteral(firstLoc);
if (catchEntry) {
assert.ok(catchEntry instanceof CatchEntry);
} else {
catchEntry = null;
}
if (finallyEntry) {
assert.ok(finallyEntry instanceof FinallyEntry);
} else {
finallyEntry = null;
}
// Have to have one or the other (or both).
assert.ok(catchEntry || finallyEntry);
this.firstLoc = firstLoc;
this.catchEntry = catchEntry;
this.finallyEntry = finallyEntry;
}
inherits(TryEntry, Entry);
function CatchEntry(firstLoc, paramId) {
Entry.call(this);
t.assertLiteral(firstLoc);
t.assertIdentifier(paramId);
this.firstLoc = firstLoc;
this.paramId = paramId;
}
inherits(CatchEntry, Entry);
function FinallyEntry(firstLoc) {
Entry.call(this);
t.assertLiteral(firstLoc);
this.firstLoc = firstLoc;
}
inherits(FinallyEntry, Entry);
function LeapManager(emitter) {
assert.ok(this instanceof LeapManager);
var Emitter = require("./emit").Emitter;
assert.ok(emitter instanceof Emitter);
this.emitter = emitter;
this.entryStack = [new FunctionEntry(emitter.finalLoc)];
}
LeapManager.prototype.withEntry = function (entry, callback) {
assert.ok(entry instanceof Entry);
this.entryStack.push(entry);
try {
callback.call(this.emitter);
} finally {
var popped = this.entryStack.pop();
assert.strictEqual(popped, entry);
}
};
LeapManager.prototype._findLeapLocation = function (property, label) {
for (var i = this.entryStack.length - 1; i >= 0; --i) {
var entry = this.entryStack[i];
var loc = entry[property];
if (loc) {
if (label) {
if (entry.label &&
entry.label.name === label.name) {
return loc;
}
} else {
return loc;
}
}
}
return null;
};
LeapManager.prototype.getBreakLoc = function (label) {
return this._findLeapLocation("breakLoc", label);
};
LeapManager.prototype.getContinueLoc = function (label) {
return this._findLeapLocation("continueLoc", label);
};

View File

@ -1,98 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var assert = require("assert");
var types = require("ast-types");
var m = require("private").makeAccessor();
var _ = require("lodash");
var isArray = types.builtInTypes.array;
var n = types.namedTypes;
function makePredicate(propertyName, knownTypes) {
function onlyChildren(node) {
n.Node.check(node);
// Assume no side effects until we find out otherwise.
var result = false;
function check(child) {
if (result) {
// Do nothing.
} else if (isArray.check(child)) {
child.some(check);
} else if (n.Node.check(child)) {
assert.strictEqual(result, false);
result = predicate(child);
}
return result;
}
types.eachField(node, function (name, child) {
check(child);
});
return result;
}
function predicate(node) {
n.Node.check(node);
var meta = m(node);
if (_.has(meta, propertyName)) return meta[propertyName];
// Certain types are "opaque," which means they have no side
// effects or leaps and we don't care about their subexpressions.
if (_.has(opaqueTypes, node.type)) return meta[propertyName] = false;
if (_.has(knownTypes, node.type)) return meta[propertyName] = true;
return meta[propertyName] = onlyChildren(node);
}
predicate.onlyChildren = onlyChildren;
return predicate;
}
var opaqueTypes = {
FunctionExpression: true
};
// These types potentially have side effects regardless of what side
// effects their subexpressions have.
var sideEffectTypes = {
CallExpression: true, // Anything could happen!
ForInStatement: true, // Modifies the key variable.
UnaryExpression: true, // Think delete.
BinaryExpression: true, // Might invoke .toString() or .valueOf().
AssignmentExpression: true, // Side-effecting by definition.
UpdateExpression: true, // Updates are essentially assignments.
NewExpression: true // Similar to CallExpression.
};
// These types are the direct cause of all leaps in control flow.
var leapTypes = {
YieldExpression: true,
BreakStatement: true,
ContinueStatement: true,
ReturnStatement: true,
ThrowStatement: true
};
// All leap types are also side effect types.
for (var type in leapTypes) {
if (_.has(leapTypes, type)) {
sideEffectTypes[type] = leapTypes[type];
}
}
exports.hasSideEffects = makePredicate("hasSideEffects", sideEffectTypes);
exports.containsLeap = makePredicate("containsLeap", leapTypes);

View File

@ -1,447 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var iteratorSymbol = typeof Symbol === "function" && Symbol.iterator || "@@iterator";
var runtime = global.regeneratorRuntime = exports;
var hasOwn = Object.prototype.hasOwnProperty;
var wrap = runtime.wrap = function wrap(innerFn, outerFn, self, tryList) {
return new Generator(innerFn, outerFn, self || null, tryList || []);
};
var GenStateSuspendedStart = "suspendedStart";
var GenStateSuspendedYield = "suspendedYield";
var GenStateExecuting = "executing";
var GenStateCompleted = "completed";
// Returning this object from the innerFn has the same effect as
// breaking out of the dispatch switch statement.
var ContinueSentinel = {};
// Dummy constructor that we use as the .constructor property for
// functions that return Generator objects.
function GeneratorFunction() {}
var GFp = function GeneratorFunctionPrototype() {};
var Gp = GFp.prototype = Generator.prototype;
(GFp.constructor = GeneratorFunction).prototype = Gp.constructor = GFp;
runtime.isGeneratorFunction = function (genFun) {
var ctor = genFun && genFun.constructor;
return ctor ? GeneratorFunction.name === ctor.name : false;
};
runtime.mark = function (genFun) {
genFun.__proto__ = GFp;
genFun.prototype = Object.create(Gp);
return genFun;
};
runtime.async = function (innerFn, outerFn, self, tryList) {
return new Promise(function (resolve, reject) {
var generator = wrap(innerFn, outerFn, self, tryList);
var callNext = step.bind(generator.next);
var callThrow = step.bind(generator["throw"]);
function step(arg) {
var info;
var value;
try {
info = this(arg);
value = info.value;
} catch (error) {
return reject(error);
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(callNext, callThrow);
}
}
callNext();
});
};
function Generator(innerFn, outerFn, self, tryList) {
var generator = outerFn ? Object.create(outerFn.prototype) : this;
var context = new Context(tryList);
var state = GenStateSuspendedStart;
function invoke(method, arg) {
if (state === GenStateExecuting) {
throw new Error("Generator is already running");
}
var alreadyFinished = state === GenStateCompleted;
while (true) {
var delegate = context.delegate;
var info;
if (delegate) {
try {
info = delegate.iterator[method](arg);
// Delegate generator ran and handled its own exceptions so
// regardless of what the method was, we continue as if it is
// "next" with an undefined arg.
method = "next";
arg = undefined;
} catch (uncaught) {
context.delegate = null;
// Like returning generator.throw(uncaught), but without the
// overhead of an extra function call.
method = "throw";
arg = uncaught;
continue;
}
if (info.done) {
context[delegate.resultName] = info.value;
context.next = delegate.nextLoc;
} else {
state = GenStateSuspendedYield;
return info;
}
context.delegate = null;
}
if (method === "next") {
if (state === GenStateSuspendedStart &&
typeof arg !== "undefined") {
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
throw new TypeError(
"attempt to send " + JSON.stringify(arg) + " to newborn generator"
);
}
if (state === GenStateSuspendedYield) {
context.sent = arg;
} else {
delete context.sent;
}
} else if (method === "throw") {
if (state === GenStateSuspendedStart) {
state = GenStateCompleted;
throw arg;
}
if (context.dispatchException(arg)) {
// If the dispatched exception was caught by a catch block,
// then let that catch block handle the exception normally.
method = "next";
arg = undefined;
}
} else if (method === "return") {
context.abrupt("return", arg);
}
state = GenStateExecuting;
try {
var value;
if (!alreadyFinished) value = innerFn.call(self, context);
// If an exception is thrown from innerFn, we leave state ===
// GenStateExecuting and loop back for another invocation.
state = context.done ? GenStateCompleted : GenStateSuspendedYield;
info = {
value: value,
done: context.done
};
if (value === ContinueSentinel) {
if (context.delegate && method === "next") {
// Deliberately forget the last sent value so that we don't
// accidentally pass it on to the delegate.
arg = undefined;
}
} else {
return info;
}
} catch (thrown) {
state = GenStateCompleted;
if (method === "next") {
context.dispatchException(thrown);
} else {
arg = thrown;
}
}
}
}
generator.next = invoke.bind(generator, "next");
generator["throw"] = invoke.bind(generator, "throw");
generator["return"] = invoke.bind(generator, "return");
return generator;
}
Gp[iteratorSymbol] = function () {
return this;
};
Gp.toString = function () {
return "[object Generator]";
};
function pushTryEntry(triple) {
var entry = { tryLoc: triple[0] };
if (1 in triple) {
entry.catchLoc = triple[1];
}
if (2 in triple) {
entry.finallyLoc = triple[2];
}
this.tryEntries.push(entry);
}
function resetTryEntry(entry, i) {
var record = entry.completion || {};
record.type = i === 0 ? "normal" : "return";
delete record.arg;
entry.completion = record;
}
function Context(tryList) {
// The root entry object (effectively a try statement without a catch
// or a finally block) gives us a place to store values thrown from
// locations where there is no enclosing try statement.
this.tryEntries = [{ tryLoc: "root" }];
tryList.forEach(pushTryEntry, this);
this.reset();
}
runtime.keys = function (object) {
var keys = [];
for (var key in object) {
keys.push(key);
}
keys.reverse();
// Rather than returning an object with a next method, we keep
// things simple and return the next function itself.
return function next() {
while (keys.length) {
var key = keys.pop();
if (key in object) {
next.value = key;
next.done = false;
return next;
}
}
// To avoid creating an additional object, we just hang the .value
// and .done properties off the next function object itself. This
// also ensures that the minifier will not anonymize the function.
next.done = true;
return next;
};
};
function values(iterable) {
var iterator = iterable;
if (iterable[iteratorSymbol]) {
iterator = iterable[iteratorSymbol]();
} else if (!isNaN(iterable.length)) {
var i = -1;
iterator = function next() {
while (++i < iterable.length) {
if (i in iterable) {
next.value = iterable[i];
next.done = false;
return next;
}
}
next.done = true;
return next;
};
iterator.next = iterator;
}
return iterator;
}
runtime.values = values;
Context.prototype = {
constructor: Context,
reset: function () {
this.prev = 0;
this.next = 0;
this.sent = undefined;
this.done = false;
this.delegate = null;
this.tryEntries.forEach(resetTryEntry);
// Pre-initialize at least 20 temporary variables to enable hidden
// class optimizations for simple generators.
for (var tempIndex = 0, tempName;
hasOwn.call(this, tempName = "t" + tempIndex) || tempIndex < 20;
++tempIndex) {
this[tempName] = null;
}
},
stop: function () {
this.done = true;
var rootEntry = this.tryEntries[0];
var rootRecord = rootEntry.completion;
if (rootRecord.type === "throw") {
throw rootRecord.arg;
}
return this.rval;
},
dispatchException: function (exception) {
if (this.done) {
throw exception;
}
var context = this;
function handle(loc, caught) {
record.type = "throw";
record.arg = exception;
context.next = loc;
return !!caught;
}
for (var i = this.tryEntries.length - 1; i >= 0; --i) {
var entry = this.tryEntries[i];
var record = entry.completion;
if (entry.tryLoc === "root") {
// Exception thrown outside of any try block that could handle
// it, so set the completion value of the entire function to
// throw the exception.
return handle("end");
}
if (entry.tryLoc <= this.prev) {
var hasCatch = hasOwn.call(entry, "catchLoc");
var hasFinally = hasOwn.call(entry, "finallyLoc");
if (hasCatch && hasFinally) {
if (this.prev < entry.catchLoc) {
return handle(entry.catchLoc, true);
} else if (this.prev < entry.finallyLoc) {
return handle(entry.finallyLoc);
}
} else if (hasCatch) {
if (this.prev < entry.catchLoc) {
return handle(entry.catchLoc, true);
}
} else if (hasFinally) {
if (this.prev < entry.finallyLoc) {
return handle(entry.finallyLoc);
}
} else {
throw new Error("try statement without catch or finally");
}
}
}
},
_findFinallyEntry: function (finallyLoc) {
for (var i = this.tryEntries.length - 1; i >= 0; --i) {
var entry = this.tryEntries[i];
if (entry.tryLoc <= this.prev &&
hasOwn.call(entry, "finallyLoc") && (
entry.finallyLoc === finallyLoc ||
this.prev < entry.finallyLoc)) {
return entry;
}
}
},
abrupt: function (type, arg) {
var entry = this._findFinallyEntry();
var record = entry ? entry.completion : {};
record.type = type;
record.arg = arg;
if (entry) {
this.next = entry.finallyLoc;
} else {
this.complete(record);
}
return ContinueSentinel;
},
complete: function (record) {
if (record.type === "throw") {
throw record.arg;
}
if (record.type === "break" || record.type === "continue") {
this.next = record.arg;
} else if (record.type === "return") {
this.rval = record.arg;
this.next = "end";
}
return ContinueSentinel;
},
finish: function (finallyLoc) {
var entry = this._findFinallyEntry(finallyLoc);
return this.complete(entry.completion);
},
"catch": function (tryLoc) {
for (var i = this.tryEntries.length - 1; i >= 0; --i) {
var entry = this.tryEntries[i];
if (entry.tryLoc === tryLoc) {
var record = entry.completion;
var thrown;
if (record.type === "throw") {
thrown = record.arg;
resetTryEntry(entry, i);
}
return thrown;
}
}
// The context.catch method must only be called with a location
// argument that corresponds to a known catch block.
throw new Error("illegal catch attempt");
},
delegateYield: function (iterable, resultName, nextLoc) {
this.delegate = {
iterator: values(iterable),
resultName: resultName,
nextLoc: nextLoc
};
return ContinueSentinel;
}
};

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var t = require("../../../types");
exports.runtimeProperty = function (name) {
return t.memberExpression(
t.identifier("regeneratorRuntime"),
t.identifier(name)
);
};
// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes. This representation has
// the amazingly convenient benefit of allowing the exact value of the
// location to be determined at any time, even after generating code that
// refers to the location.
exports.loc = function () {
return t.literal(-1);
};

View File

@ -1,221 +0,0 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.githut.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var runtimeProperty = require("./util").runtimeProperty;
var Emitter = require("./emit").Emitter;
var hoist = require("./hoist").hoist;
var types = require("ast-types");
var t = require("../../../types");
var runtimeAsyncMethod = runtimeProperty("async");
var runtimeWrapMethod = runtimeProperty("wrap");
var runtimeMarkMethod = runtimeProperty("mark");
exports.transform = function transform(node, file) {
return types.visit(node, {
visitFunction: function (path) {
return visitor.call(this, path, file);
}
});
};
var visitor = function (path, file) {
// Calling this.traverse(path) first makes for a post-order traversal.
this.traverse(path);
var node = path.value;
var scope; // we need to actually get the current scope
if (!node.generator && !node.async) {
return;
}
node.generator = false;
if (node.expression) {
// Transform expression lambdas into normal functions.
node.expression = false;
node.body = t.blockStatement([
t.returnStatement(node.body)
]);
}
if (node.async) {
awaitVisitor.visit(path.get("body"));
}
var outerFnId = node.id || (
node.id = file.generateUidIdentifier("callee", scope)
);
var innerFnId = t.identifier(node.id.name + "$");
var contextId = file.generateUidIdentifier("context", scope);
var vars = hoist(path);
var emitter = new Emitter(contextId);
emitter.explode(path.get("body"));
var outerBody = [];
if (vars && vars.declarations.length > 0) {
outerBody.push(vars);
}
var wrapArgs = [
emitter.getContextFunction(innerFnId),
// Async functions don't care about the outer function because they
// don't need it to be marked and don't inherit from its .prototype.
node.async ? t.literal(null) : outerFnId,
t.thisExpression()
];
var tryEntryList = emitter.getTryEntryList();
if (tryEntryList) {
wrapArgs.push(tryEntryList);
}
var wrapCall = t.callExpression(
node.async ? runtimeAsyncMethod : runtimeWrapMethod,
wrapArgs
);
outerBody.push(t.returnStatement(wrapCall));
node.body = t.blockStatement(outerBody);
if (node.async) {
node.async = false;
return;
}
if (t.isFunctionDeclaration(node)) {
var pp = path.parent;
while (pp && !(t.isBlockStatement(pp.value) || t.isProgram(pp.value))) {
pp = pp.parent;
}
if (!pp) {
return;
}
// Here we turn the FunctionDeclaration into a named
// FunctionExpression that will be assigned to a variable of the
// same name at the top of the enclosing block. This is important
// for a very subtle reason: named function expressions can refer to
// themselves by name without fear that the binding may change due
// to code executing outside the function, whereas function
// declarations are vulnerable to the following rebinding:
//
// function f() { return f }
// var g = f;
// f = "asdf";
// g(); // "asdf"
//
// One way to prevent the problem illustrated above is to transform
// the function declaration thus:
//
// var f = function f() { return f };
// var g = f;
// f = "asdf";
// g(); // f
// g()()()()(); // f
//
// In the code below, we transform generator function declarations
// in the following way:
//
// gen().next(); // { value: gen, done: true }
// function *gen() {
// return gen;
// }
//
// becomes something like
//
// var gen = runtime.mark(function *gen() {
// return gen;
// });
// gen().next(); // { value: gen, done: true }
//
// which ensures that the generator body can always reliably refer
// to gen by name.
// Remove the FunctionDeclaration so that we can add it back as a
// FunctionExpression passed to runtime.mark.
path.replace();
// Change the type of the function to be an expression instead of a
// declaration. Note that all the other fields are the same.
node.type = "FunctionExpression";
var varDecl = t.variableDeclaration("var", [
t.variableDeclarator(
node.id,
t.callExpression(runtimeMarkMethod, [node])
)
]);
// Copy any comments preceding the function declaration to the
// variable declaration, to avoid weird formatting consequences.
t.inheritsComments(varDecl, node);
t.removeComments(node);
varDecl._blockHoist = true;
var bodyPath = pp.get("body");
var bodyLen = bodyPath.value.length;
for (var i = 0; i < bodyLen; ++i) {
var firstStmtPath = bodyPath.get(i);
if (!shouldNotHoistAbove(firstStmtPath)) {
firstStmtPath.insertBefore(varDecl);
return;
}
}
bodyPath.push(varDecl);
} else {
t.assertFunctionExpression(node);
return t.callExpression(runtimeMarkMethod, [node]);
}
};
function shouldNotHoistAbove(stmtPath) {
var value = stmtPath.value;
t.assertStatement(value);
// If the first statement is a "use strict" declaration, make sure to
// insert hoisted declarations afterwards.
if (t.isExpressionStatement(value) &&
t.isLiteral(value.expression) &&
value.expression.value === "use strict") {
return true;
}
if (t.isVariableDeclaration(value)) {
for (var i = 0; i < value.declarations.length; ++i) {
var decl = value.declarations[i];
if (t.isCallExpression(decl.init) && types.astNodesAreEquivalent(decl.init.callee, runtimeMarkMethod)) {
return true;
}
}
}
return false;
}
var awaitVisitor = types.PathVisitor.fromMethodsObject({
visitFunction: function () {
return false; // Don't descend into nested function scopes.
},
visitAwaitExpression: function (path) {
// Convert await expressions to yield expressions.
return t.yieldExpression(path.value.argument, false);
}
});

View File

@ -36,7 +36,7 @@
},
"dependencies": {
"acorn-6to5": "0.9.1-14",
"ast-types": "0.6.5",
"ast-types": "~0.6.1",
"chokidar": "0.11.1",
"commander": "2.5.0",
"es6-shim": "0.21.0",
@ -48,6 +48,7 @@
"lodash": "2.4.1",
"mkdirp": "0.5.0",
"private": "0.1.6",
"regenerator": "^0.8.2",
"regexpu": "0.3.0",
"roadrunner": "^1.0.4",
"source-map": "0.1.40",