diff --git a/lib/6to5/transformation/transformers/generators/emit/explode-expressions.js b/lib/6to5/transformation/transformers/generators/emit/explode-expressions.js new file mode 100644 index 0000000000..fe28897340 --- /dev/null +++ b/lib/6to5/transformation/transformers/generators/emit/explode-expressions.js @@ -0,0 +1,204 @@ +/** + * 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"); +}; diff --git a/lib/6to5/transformation/transformers/generators/emit/explode-statements.js b/lib/6to5/transformation/transformers/generators/emit/explode-statements.js new file mode 100644 index 0000000000..9f73681883 --- /dev/null +++ b/lib/6to5/transformation/transformers/generators/emit/explode-statements.js @@ -0,0 +1,334 @@ +/** + * 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 (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")) + )); +}; diff --git a/lib/6to5/transformation/transformers/generators/emit.js b/lib/6to5/transformation/transformers/generators/emit/index.js similarity index 57% rename from lib/6to5/transformation/transformers/generators/emit.js rename to lib/6to5/transformation/transformers/generators/emit/index.js index 888065dee0..8df1576721 100644 --- a/lib/6to5/transformation/transformers/generators/emit.js +++ b/lib/6to5/transformation/transformers/generators/emit/index.js @@ -10,16 +10,18 @@ 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 t = require("../../../types"); -var _ = require("lodash"); +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 runtimeKeysMethod = runtimeProperty("keys"); -var n = types.namedTypes; +var loc = util.loc; +var n = types.namedTypes; function Emitter(contextId) { assert.ok(this instanceof Emitter); @@ -52,15 +54,6 @@ function Emitter(contextId) { this.leapManager = new leap.LeapManager(this); } -// 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. -function loc() { - return t.literal(-1); -} - // Sets the exact value of the given location to the offset of the next // Statement emitted. Emitter.prototype.mark = function (loc) { @@ -394,7 +387,6 @@ Emitter.prototype.explodeStatement = function (path, labelId) { var stmt = path.value; var self = this; - var after, head; t.assertStatement(stmt); @@ -423,333 +415,10 @@ Emitter.prototype.explodeStatement = function (path, labelId) { return; } - switch (stmt.type) { - case "ExpressionStatement": - self.explodeExpression(path.get("expression"), true); - break; - - case "LabeledStatement": - self.explodeStatement(path.get("body"), stmt.label); - break; - - case "WhileStatement": - var before = loc(); - after = 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": - var first = loc(); - var test = loc(); - after = 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 = loc(); - var update = loc(); - 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. - 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 "ForInStatement": - t.assertIdentifier(stmt.left); - - head = loc(); - after = loc(); - - var keyIterNextFn = self.makeTempVar(); - self.emitAssign( - keyIterNextFn, - t.callExpression( - runtimeKeysMethod, - [self.explodeExpression(path.get("right"))] - ) - ); - - self.mark(head); - - var keyInfoTmpVar = self.makeTempVar(); - self.jumpIf( - t.memberExpression( - t.assignmentExpression( - "=", - keyInfoTmpVar, - t.callExpression(keyIterNextFn, []) - ), - t.identifier("done"), - false - ), - after - ); - - self.emitAssign( - stmt.left, - t.memberExpression( - 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. - var disc = self.emitAssign( - self.makeTempVar(), - self.explodeExpression(path.get("discriminant")) - ); - - after = loc(); - var defaultLoc = loc(); - var condition = defaultLoc; - var caseLocs = []; - - // 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; - } - } - - self.jump(self.explodeExpression( - new types.NodePath(condition, path, "discriminant") - )); - - self.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 - ); - }); - } - ); - - self.mark(after); - if (defaultLoc.value === -1) { - self.mark(defaultLoc); - assert.strictEqual(after.value, defaultLoc.value); - } - - break; - - case "IfStatement": - var elseLoc = stmt.alternate && loc(); - after = 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 "TryStatement": - after = loc(); - - 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( - 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)); - - var bodyPath = path.get("handler", "body"); - var safeParam = self.makeTempVar(); - self.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 (path.value.name === catchParamName && - path.scope.lookup(catchParamName) === catchScope) { - return safeParam; - } - this.traverse(path); - } - }); - - 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.callExpression( - self.contextProperty("finish"), - [finallyEntry.firstLoc] - )); - } - }); - - self.mark(after); - - break; - - case "ThrowStatement": - self.emit(t.throwStatement( - self.explodeExpression(path.get("argument")) - )); - - break; - - default: + 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)); } }; @@ -864,7 +533,6 @@ Emitter.prototype.explodeExpression = function (path, ignoreResult) { } var self = this; - var result, after; // Used optionally by several cases below. function finish(expr) { t.assertExpression(expr); @@ -934,180 +602,10 @@ Emitter.prototype.explodeExpression = function (path, ignoreResult) { // emitting the expression with all its side effects, and we should not // return a result. - switch (expr.type) { - case "ParenthesizedExpression": - return finish(self.explodeExpression(path.get("expression"))); - - case "MemberExpression": - return finish(t.memberExpression( - self.explodeExpression(path.get("object")), - expr.computed ? explodeViaTempVar(null, path.get("property")) : expr.property, - expr.computed - )); - - case "CallExpression": - var oldCalleePath = path.get("callee"); - var newCallee = self.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); - }) - )); - - case "NewExpression": - return finish(t.newExpression( - explodeViaTempVar(null, path.get("callee")), - path.get("arguments").map(function (argPath) { - return explodeViaTempVar(null, argPath); - }) - )); - - case "ObjectExpression": - return finish(t.objectExpression( - path.get("properties").map(function (propPath) { - return t.property( - propPath.value.kind, - propPath.value.key, - explodeViaTempVar(null, propPath.get("value")) - ); - }) - )); - - case "ArrayExpression": - return finish(t.arrayExpression( - path.get("elements").map(function (elemPath) { - return explodeViaTempVar(null, elemPath); - }) - )); - - case "SequenceExpression": - var lastIndex = expr.expressions.length - 1; - - path.get("expressions").each(function (exprPath) { - if (exprPath.name === lastIndex) { - result = self.explodeExpression(exprPath, ignoreResult); - } else { - self.explodeExpression(exprPath, true); - } - }); - - return result; - - case "LogicalExpression": - after = loc(); - - if (!ignoreResult) { - result = self.makeTempVar(); - } - - var left = explodeViaTempVar(result, path.get("left")); - - if (expr.operator === "&&") { - self.jumpIfNot(left, after); - } else { - assert.strictEqual(expr.operator, "||"); - self.jumpIf(left, after); - } - - explodeViaTempVar(result, path.get("right"), ignoreResult); - - self.mark(after); - - return result; - - case "ConditionalExpression": - var elseLoc = loc(); - after = loc(); - var test = self.explodeExpression(path.get("test")); - - self.jumpIfNot(test, elseLoc); - - if (!ignoreResult) { - result = self.makeTempVar(); - } - - explodeViaTempVar(result, path.get("consequent"), ignoreResult); - self.jump(after); - - self.mark(elseLoc); - explodeViaTempVar(result, path.get("alternate"), ignoreResult); - - self.mark(after); - - return result; - - case "UnaryExpression": - 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]. - self.explodeExpression(path.get("argument")), - !!expr.prefix - )); - - case "BinaryExpression": - return finish(t.binaryExpression( - expr.operator, - explodeViaTempVar(null, path.get("left")), - explodeViaTempVar(null, path.get("right")) - )); - - case "AssignmentExpression": - return finish(t.assignmentExpression( - expr.operator, - self.explodeExpression(path.get("left")), - self.explodeExpression(path.get("right")) - )); - - case "UpdateExpression": - return finish(t.updateExpression( - expr.operator, - self.explodeExpression(path.get("argument")), - expr.prefix - )); - - case "YieldExpression": - after = loc(); - var arg = expr.argument && self.explodeExpression(path.get("argument")); - - if (arg && expr.delegate) { - result = self.makeTempVar(); - - self.emit(t.returnStatement(t.callExpression( - self.contextProperty("delegateYield"), [ - arg, - t.literal(result.property.name), - after - ] - ))); - - self.mark(after); - - return result; - } - - self.emitAssign(self.contextProperty("next"), after); - self.emit(t.returnStatement(arg || null)); - self.mark(after); - - return self.contextProperty("sent"); - - default: + 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)); } }; diff --git a/lib/6to5/transformation/transformers/generators/util.js b/lib/6to5/transformation/transformers/generators/util.js index a6b5dc165f..99e7072a19 100644 --- a/lib/6to5/transformation/transformers/generators/util.js +++ b/lib/6to5/transformation/transformers/generators/util.js @@ -16,3 +16,13 @@ exports.runtimeProperty = function (name) { 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); +};