regenerator spring cleaning

This commit is contained in:
Sebastian McKenzie
2014-11-17 17:04:04 +11:00
parent b9a6cf35b7
commit 8e115ef3ed
7 changed files with 118 additions and 178 deletions

View File

@@ -8,57 +8,50 @@
* the same directory.
*/
var assert = require("assert");
var types = require("ast-types");
var b = types.builders;
var n = types.namedTypes;
var leap = require("./leap");
var meta = require("./meta");
exports.Emitter = Emitter;
var runtimeProperty = require("./util").runtimeProperty;
var assert = require("assert");
var types = require("ast-types");
var leap = require("./leap");
var meta = require("./meta");
var _ = require("lodash");
var runtimeKeysMethod = runtimeProperty("keys");
var hasOwn = Object.prototype.hasOwnProperty;
var b = types.builders;
var n = types.namedTypes;
function Emitter(contextId) {
assert.ok(this instanceof Emitter);
n.Identifier.assert(contextId);
Object.defineProperties(this, {
// 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".
contextId: { value: 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.
listing: { value: [] },
// 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.
marked: { value: [true] },
// 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.
finalLoc: { value: loc() },
// The last location will be marked when this.getDispatchLoop is
// called.
this.finalLoc = loc();
// A list of all leap.TryEntry statements emitted.
tryEntries: { value: [] }
});
// A list of all leap.TryEntry statements emitted.
this.tryEntries = [];
// The .leapManager property needs to be defined by a separate
// defineProperties call so that .finalLoc will be visible to the
// leap.LeapManager constructor.
Object.defineProperties(this, {
// 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.
leapManager: { value: new leap.LeapManager(this) }
});
// 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);
}
var Ep = Emitter.prototype;
exports.Emitter = Emitter;
// 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
@@ -70,7 +63,7 @@ function loc() {
// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Ep.mark = function(loc) {
Emitter.prototype.mark = function(loc) {
n.Literal.assert(loc);
var index = this.listing.length;
if (loc.value === -1) {
@@ -84,29 +77,28 @@ Ep.mark = function(loc) {
return loc;
};
Ep.emit = function(node) {
if (n.Expression.check(node))
node = b.expressionStatement(node);
Emitter.prototype.emit = function(node) {
if (n.Expression.check(node)) node = b.expressionStatement(node);
n.Statement.assert(node);
this.listing.push(node);
};
// Shorthand for emitting assignment statements. This will come in handy
// for assignments to temporary variables.
Ep.emitAssign = function(lhs, rhs) {
Emitter.prototype.emitAssign = function(lhs, rhs) {
this.emit(this.assign(lhs, rhs));
return lhs;
};
// Shorthand for an assignment statement.
Ep.assign = function(lhs, rhs) {
Emitter.prototype.assign = function(lhs, rhs) {
return b.expressionStatement(
b.assignmentExpression("=", lhs, rhs));
};
// Convenience function for generating expressions like context.next,
// context.sent, and context.rval.
Ep.contextProperty = function(name, computed) {
Emitter.prototype.contextProperty = function(name, computed) {
return b.memberExpression(
this.contextId,
computed ? b.literal(name) : b.identifier(name),
@@ -124,7 +116,7 @@ var volatileContextPropertyNames = {
// 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.
Ep.isVolatileContextProperty = function(expr) {
Emitter.prototype.isVolatileContextProperty = function(expr) {
if (n.MemberExpression.check(expr)) {
if (expr.computed) {
// If it's a computed property such as context[couldBeAnything],
@@ -135,8 +127,7 @@ Ep.isVolatileContextProperty = function(expr) {
if (n.Identifier.check(expr.object) &&
n.Identifier.check(expr.property) &&
expr.object.name === this.contextId.name &&
hasOwn.call(volatileContextPropertyNames,
expr.property.name)) {
_.has(volatileContextPropertyNames, expr.property.name)) {
return true;
}
}
@@ -145,7 +136,7 @@ Ep.isVolatileContextProperty = function(expr) {
};
// Shorthand for setting context.rval and jumping to `context.stop()`.
Ep.stop = function(rval) {
Emitter.prototype.stop = function(rval) {
if (rval) {
this.setReturnValue(rval);
}
@@ -153,7 +144,7 @@ Ep.stop = function(rval) {
this.jump(this.finalLoc);
};
Ep.setReturnValue = function(valuePath) {
Emitter.prototype.setReturnValue = function(valuePath) {
n.Expression.assert(valuePath.value);
this.emitAssign(
@@ -162,7 +153,7 @@ Ep.setReturnValue = function(valuePath) {
);
};
Ep.clearPendingException = function(tryLoc, assignee) {
Emitter.prototype.clearPendingException = function(tryLoc, assignee) {
n.Literal.assert(tryLoc);
var catchCall = b.callExpression(
@@ -179,13 +170,13 @@ Ep.clearPendingException = function(tryLoc, assignee) {
// Emits code for an unconditional jump to the given location, even if the
// exact value of the location is not yet known.
Ep.jump = function(toLoc) {
Emitter.prototype.jump = function(toLoc) {
this.emitAssign(this.contextProperty("next"), toLoc);
this.emit(b.breakStatement());
};
// Conditional jump.
Ep.jumpIf = function(test, toLoc) {
Emitter.prototype.jumpIf = function(test, toLoc) {
n.Expression.assert(test);
n.Literal.assert(toLoc);
@@ -199,13 +190,12 @@ Ep.jumpIf = function(test, toLoc) {
};
// Conditional jump, with the condition negated.
Ep.jumpIfNot = function(test, toLoc) {
Emitter.prototype.jumpIfNot = function(test, toLoc) {
n.Expression.assert(test);
n.Literal.assert(toLoc);
var negatedTest;
if (n.UnaryExpression.check(test) &&
test.operator === "!") {
if (n.UnaryExpression.check(test) && test.operator === "!") {
// Avoid double negation.
negatedTest = test.argument;
} else {
@@ -227,13 +217,13 @@ Ep.jumpIfNot = function(test, toLoc) {
// other local variables, and since we just increment `nextTempId`
// monotonically, uniqueness is assured.
var nextTempId = 0;
Ep.makeTempVar = function() {
Emitter.prototype.makeTempVar = function() {
return this.contextProperty("t" + nextTempId++);
};
Ep.getContextFunction = function(id) {
Emitter.prototype.getContextFunction = function(id) {
var node = b.functionExpression(
id || null/*Anonymous*/,
id || null,
[this.contextId],
b.blockStatement([this.getDispatchLoop()]),
false, // Not a generator anymore!
@@ -254,7 +244,7 @@ Ep.getContextFunction = function(id) {
//
// Each marked location in this.listing will correspond to one generated
// case statement.
Ep.getDispatchLoop = function() {
Emitter.prototype.getDispatchLoop = function() {
var self = this;
var cases = [];
var current;
@@ -265,9 +255,7 @@ Ep.getDispatchLoop = function() {
self.listing.forEach(function(stmt, i) {
if (self.marked.hasOwnProperty(i)) {
cases.push(b.switchCase(
b.literal(i),
current = []));
cases.push(b.switchCase(b.literal(i), current = []));
alreadyEnded = false;
}
@@ -318,7 +306,7 @@ function isSwitchCaseEnder(stmt) {
n.ThrowStatement.check(stmt);
}
Ep.getTryEntryList = function() {
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.
@@ -358,7 +346,7 @@ Ep.getTryEntryList = function() {
// No destructive modification of AST nodes.
Ep.explode = function(path, ignoreResult) {
Emitter.prototype.explode = function(path, ignoreResult) {
assert.ok(path instanceof types.NodePath);
var node = path.value;
@@ -377,10 +365,7 @@ Ep.explode = function(path, ignoreResult) {
switch (node.type) {
case "Program":
return path.get("body").map(
self.explodeStatement,
self
);
return path.get("body").map(self.explodeStatement, self);
case "VariableDeclarator":
throw getDeclError(node);
@@ -390,13 +375,10 @@ Ep.explode = function(path, ignoreResult) {
case "Property":
case "SwitchCase":
case "CatchClause":
throw new Error(
node.type + " nodes should be handled by their parents");
throw new Error(node.type + " nodes should be handled by their parents");
default:
throw new Error(
"unknown Node of type " +
JSON.stringify(node.type));
throw new Error("unknown Node of type " + JSON.stringify(node.type));
}
};
@@ -407,7 +389,7 @@ function getDeclError(node) {
JSON.stringify(node));
}
Ep.explodeStatement = function(path, labelId) {
Emitter.prototype.explodeStatement = function(path, labelId) {
assert.ok(path instanceof types.NodePath);
var stmt = path.value;
@@ -768,18 +750,15 @@ Ep.explodeStatement = function(path, labelId) {
break;
default:
throw new Error(
"unknown Statement of type " +
JSON.stringify(stmt.type));
throw new Error("unknown Statement of type " + JSON.stringify(stmt.type));
}
};
Ep.emitAbruptCompletion = function(record) {
Emitter.prototype.emitAbruptCompletion = function(record) {
if (!isValidCompletion(record)) {
assert.ok(
false,
"invalid completion record: " +
JSON.stringify(record)
"invalid completion record: " + JSON.stringify(record)
);
}
@@ -790,12 +769,10 @@ Ep.emitAbruptCompletion = function(record) {
var abruptArgs = [b.literal(record.type)];
if (record.type === "break" ||
record.type === "continue") {
if (record.type === "break" || record.type === "continue") {
n.Literal.assert(record.target);
abruptArgs[1] = record.target;
} else if (record.type === "return" ||
record.type === "throw") {
} else if (record.type === "return" || record.type === "throw") {
if (record.value) {
n.Expression.assert(record.value);
abruptArgs[1] = record.value;
@@ -816,15 +793,15 @@ function isValidCompletion(record) {
var type = record.type;
if (type === "normal") {
return !hasOwn.call(record, "target");
return !_.has(record, "target");
}
if (type === "break" || type === "continue") {
return !hasOwn.call(record, "value") && n.Literal.check(record.target);
return !_.has(record, "value") && n.Literal.check(record.target);
}
if (type === "return" || type === "throw") {
return hasOwn.call(record, "value") && !hasOwn.call(record, "target");
return _.has(record, "value") && !_.has(record, "target");
}
return false;
@@ -840,7 +817,7 @@ function isValidCompletion(record) {
// 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.
Ep.getUnmarkedCurrentLoc = function() {
Emitter.prototype.getUnmarkedCurrentLoc = function() {
return b.literal(this.listing.length);
};
@@ -854,7 +831,7 @@ Ep.getUnmarkedCurrentLoc = function() {
// 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.
Ep.updateContextPrevLoc = function(loc) {
Emitter.prototype.updateContextPrevLoc = function(loc) {
if (loc) {
n.Literal.assert(loc);
@@ -877,7 +854,7 @@ Ep.updateContextPrevLoc = function(loc) {
this.emitAssign(this.contextProperty("prev"), loc);
};
Ep.explodeExpression = function(path, ignoreResult) {
Emitter.prototype.explodeExpression = function(path, ignoreResult) {
assert.ok(path instanceof types.NodePath);
var expr = path.value;

View File

@@ -9,10 +9,11 @@
*/
var assert = require("assert");
var types = require("ast-types");
var types = require("ast-types");
var _ = require("lodash");
var n = types.namedTypes;
var b = types.builders;
var hasOwn = Object.prototype.hasOwnProperty;
// The hoist function takes a FunctionExpression or FunctionDeclaration
// and replaces any Declaration nodes in its body with assignments, then
@@ -139,7 +140,7 @@ exports.hoist = function(funPath) {
var declarations = [];
Object.keys(vars).forEach(function(name) {
if (!hasOwn.call(paramNames, name)) {
if (!_.has(paramNames, name)) {
declarations.push(b.variableDeclarator(vars[name], null));
}
});

View File

@@ -8,10 +8,20 @@
* 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 types = require("ast-types");
var n = types.namedTypes;
var inherits = require("util").inherits;
var types = require("ast-types");
var util = require("util");
var inherits = util.inherits;
var n = types.namedTypes;
function Entry() {
assert.ok(this instanceof Entry);
@@ -22,13 +32,10 @@ function FunctionEntry(returnLoc) {
n.Literal.assert(returnLoc);
Object.defineProperties(this, {
returnLoc: { value: returnLoc }
});
this.returnLoc = returnLoc;
}
inherits(FunctionEntry, Entry);
exports.FunctionEntry = FunctionEntry;
function LoopEntry(breakLoc, continueLoc, label) {
Entry.call(this);
@@ -42,28 +49,22 @@ function LoopEntry(breakLoc, continueLoc, label) {
label = null;
}
Object.defineProperties(this, {
breakLoc: { value: breakLoc },
continueLoc: { value: continueLoc },
label: { value: label }
});
this.breakLoc = breakLoc;
this.continueLoc = continueLoc;
this.label = label;
}
inherits(LoopEntry, Entry);
exports.LoopEntry = LoopEntry;
function SwitchEntry(breakLoc) {
Entry.call(this);
n.Literal.assert(breakLoc);
Object.defineProperties(this, {
breakLoc: { value: breakLoc }
});
this.breakLoc = breakLoc;
}
inherits(SwitchEntry, Entry);
exports.SwitchEntry = SwitchEntry;
function TryEntry(firstLoc, catchEntry, finallyEntry) {
Entry.call(this);
@@ -85,15 +86,12 @@ function TryEntry(firstLoc, catchEntry, finallyEntry) {
// Have to have one or the other (or both).
assert.ok(catchEntry || finallyEntry);
Object.defineProperties(this, {
firstLoc: { value: firstLoc },
catchEntry: { value: catchEntry },
finallyEntry: { value: finallyEntry }
});
this.firstLoc = firstLoc;
this.catchEntry = catchEntry;
this.finallyEntry = finallyEntry;
}
inherits(TryEntry, Entry);
exports.TryEntry = TryEntry;
function CatchEntry(firstLoc, paramId) {
Entry.call(this);
@@ -101,27 +99,21 @@ function CatchEntry(firstLoc, paramId) {
n.Literal.assert(firstLoc);
n.Identifier.assert(paramId);
Object.defineProperties(this, {
firstLoc: { value: firstLoc },
paramId: { value: paramId }
});
this.firstLoc = firstLoc;
this.paramId = paramId;
}
inherits(CatchEntry, Entry);
exports.CatchEntry = CatchEntry;
function FinallyEntry(firstLoc) {
Entry.call(this);
n.Literal.assert(firstLoc);
Object.defineProperties(this, {
firstLoc: { value: firstLoc }
});
this.firstLoc = firstLoc;
}
inherits(FinallyEntry, Entry);
exports.FinallyEntry = FinallyEntry;
function LeapManager(emitter) {
assert.ok(this instanceof LeapManager);
@@ -129,18 +121,11 @@ function LeapManager(emitter) {
var Emitter = require("./emit").Emitter;
assert.ok(emitter instanceof Emitter);
Object.defineProperties(this, {
emitter: { value: emitter },
entryStack: {
value: [new FunctionEntry(emitter.finalLoc)]
}
});
this.emitter = emitter;
this.entryStack = [new FunctionEntry(emitter.finalLoc)];
}
var LMp = LeapManager.prototype;
exports.LeapManager = LeapManager;
LMp.withEntry = function(entry, callback) {
LeapManager.prototype.withEntry = function(entry, callback) {
assert.ok(entry instanceof Entry);
this.entryStack.push(entry);
try {
@@ -151,7 +136,7 @@ LMp.withEntry = function(entry, callback) {
}
};
LMp._findLeapLocation = function(property, label) {
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];
@@ -170,10 +155,10 @@ LMp._findLeapLocation = function(property, label) {
return null;
};
LMp.getBreakLoc = function(label) {
LeapManager.prototype.getBreakLoc = function(label) {
return this._findLeapLocation("breakLoc", label);
};
LMp.getContinueLoc = function(label) {
LeapManager.prototype.getContinueLoc = function(label) {
return this._findLeapLocation("continueLoc", label);
};

View File

@@ -9,11 +9,12 @@
*/
var assert = require("assert");
var types = require("ast-types");
var m = require("private").makeAccessor();
var types = require("ast-types");
var m = require("private").makeAccessor();
var _ = require("lodash");
var isArray = types.builtInTypes.array;
var n = types.namedTypes;
var hasOwn = Object.prototype.hasOwnProperty;
var n = types.namedTypes;
function makePredicate(propertyName, knownTypes) {
function onlyChildren(node) {
@@ -45,16 +46,13 @@ function makePredicate(propertyName, knownTypes) {
n.Node.assert(node);
var meta = m(node);
if (hasOwn.call(meta, propertyName))
return meta[propertyName];
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 (hasOwn.call(opaqueTypes, node.type))
return meta[propertyName] = false;
if (_.has(opaqueTypes, node.type)) return meta[propertyName] = false;
if (hasOwn.call(knownTypes, node.type))
return meta[propertyName] = true;
if (_.has(knownTypes, node.type)) return meta[propertyName] = true;
return meta[propertyName] = onlyChildren(node);
}
@@ -91,7 +89,7 @@ var leapTypes = {
// All leap types are also side effect types.
for (var type in leapTypes) {
if (hasOwn.call(leapTypes, type)) {
if (_.has(leapTypes, type)) {
sideEffectTypes[type] = leapTypes[type];
}
}

View File

@@ -12,9 +12,9 @@ if (typeof global.regeneratorRuntime === "object") {
return;
}
var hasOwn = Object.prototype.hasOwnProperty;
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 || []);
@@ -412,8 +412,7 @@ Context.prototype = {
throw record.arg;
}
if (record.type === "break" ||
record.type === "continue") {
if (record.type === "break" || record.type === "continue") {
this.next = record.arg;
} else if (record.type === "return") {
this.rval = record.arg;

View File

@@ -8,30 +8,11 @@
* the same directory.
*/
var b = require("ast-types").builders;
var hasOwn = Object.prototype.hasOwnProperty;
var t = require("../../../types");
exports.defaults = function(obj) {
var len = arguments.length;
var extension;
for (var i = 1; i < len; ++i) {
if ((extension = arguments[i])) {
for (var key in extension) {
if (hasOwn.call(extension, key) && !hasOwn.call(obj, key)) {
obj[key] = extension[key];
}
}
}
}
return obj;
};
exports.runtimeProperty = function(name) {
return b.memberExpression(
b.identifier("regeneratorRuntime"),
b.identifier(name),
false
exports.runtimeProperty = function (name) {
return t.memberExpression(
t.identifier("regeneratorRuntime"),
t.identifier(name)
);
};

View File

@@ -200,8 +200,7 @@ function shouldNotHoistAbove(stmtPath) {
for (var i = 0; i < value.declarations.length; ++i) {
var decl = value.declarations[i];
if (n.CallExpression.check(decl.init) &&
types.astNodesAreEquivalent(decl.init.callee,
runtimeMarkMethod)) {
types.astNodesAreEquivalent(decl.init.callee, runtimeMarkMethod)) {
return true;
}
}