| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192 |
- /**
- * 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 isArray = types.builtInTypes.array;
- var b = types.builders;
- var n = types.namedTypes;
- var leap = require("./leap");
- var meta = require("./meta");
- var util = require("./util");
- var hasOwn = Object.prototype.hasOwnProperty;
- 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 },
- // An append-only list of Statements that grows each time this.emit is
- // called.
- listing: { value: [] },
- // A sparse array whose keys correspond to locations in this.listing
- // that have been marked as branch/jump targets.
- marked: { value: [true] },
- // The last location will be marked when this.getDispatchLoop is
- // called.
- finalLoc: { value: loc() },
- // A list of all leap.TryEntry statements emitted.
- tryEntries: { value: [] }
- });
- // 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) }
- });
- }
- 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
- // location to be determined at any time, even after generating code that
- // refers to the location.
- function loc() {
- return b.literal(-1);
- }
- // Sets the exact value of the given location to the offset of the next
- // Statement emitted.
- Ep.mark = function(loc) {
- n.Literal.assert(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;
- };
- Ep.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) {
- this.emit(this.assign(lhs, rhs));
- return lhs;
- };
- // Shorthand for an assignment statement.
- Ep.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) {
- return b.memberExpression(
- this.contextId,
- computed ? b.literal(name) : b.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.
- Ep.isVolatileContextProperty = function(expr) {
- if (n.MemberExpression.check(expr)) {
- if (expr.computed) {
- // If it's a computed property such as context[couldBeAnything],
- // assume the worst in terms of volatility.
- return true;
- }
- if (n.Identifier.check(expr.object) &&
- n.Identifier.check(expr.property) &&
- expr.object.name === this.contextId.name &&
- hasOwn.call(volatileContextPropertyNames,
- expr.property.name)) {
- return true;
- }
- }
- return false;
- };
- // Shorthand for setting context.rval and jumping to `context.stop()`.
- Ep.stop = function(rval) {
- if (rval) {
- this.setReturnValue(rval);
- }
- this.jump(this.finalLoc);
- };
- Ep.setReturnValue = function(valuePath) {
- n.Expression.assert(valuePath.value);
- this.emitAssign(
- this.contextProperty("rval"),
- this.explodeExpression(valuePath)
- );
- };
- Ep.clearPendingException = function(tryLoc, assignee) {
- n.Literal.assert(tryLoc);
- var catchCall = b.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.
- Ep.jump = function(toLoc) {
- this.emitAssign(this.contextProperty("next"), toLoc);
- this.emit(b.breakStatement());
- };
- // Conditional jump.
- Ep.jumpIf = function(test, toLoc) {
- n.Expression.assert(test);
- n.Literal.assert(toLoc);
- this.emit(b.ifStatement(
- test,
- b.blockStatement([
- this.assign(this.contextProperty("next"), toLoc),
- b.breakStatement()
- ])
- ));
- };
- // Conditional jump, with the condition negated.
- Ep.jumpIfNot = function(test, toLoc) {
- n.Expression.assert(test);
- n.Literal.assert(toLoc);
- var negatedTest;
- if (n.UnaryExpression.check(test) &&
- test.operator === "!") {
- // Avoid double negation.
- negatedTest = test.argument;
- } else {
- negatedTest = b.unaryExpression("!", test);
- }
- this.emit(b.ifStatement(
- negatedTest,
- b.blockStatement([
- this.assign(this.contextProperty("next"), toLoc),
- b.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;
- Ep.makeTempVar = function() {
- return this.contextProperty("t" + nextTempId++);
- };
- Ep.getContextFunction = function(id) {
- var func = b.functionExpression(
- id || null/*Anonymous*/,
- [this.contextId],
- b.blockStatement([this.getDispatchLoop()]),
- false, // Not a generator anymore!
- false // Nor an expression.
- );
- func._aliasFunction = true;
- return func;
- };
- // 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.
- Ep.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(b.switchCase(
- b.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(
- b.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.
- b.switchCase(b.literal("end"), [
- // This will check/clear both context.thrown and context.rval.
- b.returnStatement(
- b.callExpression(this.contextProperty("stop"), [])
- )
- ])
- );
- return b.whileStatement(
- b.literal(1),
- b.switchStatement(
- b.assignmentExpression(
- "=",
- this.contextProperty("prev"),
- this.contextProperty("next")
- ),
- cases
- )
- );
- };
- // See comment above re: alreadyEnded.
- function isSwitchCaseEnder(stmt) {
- return n.BreakStatement.check(stmt)
- || n.ContinueStatement.check(stmt)
- || n.ReturnStatement.check(stmt)
- || n.ThrowStatement.check(stmt);
- }
- Ep.getTryLocsList = 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 b.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 locs = [
- tryEntry.firstLoc,
- // The null here makes a hole in the array.
- ce ? ce.firstLoc : null
- ];
- if (fe) {
- locs[2] = fe.firstLoc;
- locs[3] = fe.afterLoc;
- }
- return b.arrayExpression(locs);
- })
- );
- };
- // 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.
- Ep.explode = function(path, ignoreResult) {
- assert.ok(path instanceof types.NodePath);
- var node = path.value;
- var self = this;
- n.Node.assert(node);
- if (n.Statement.check(node))
- return self.explodeStatement(path);
- if (n.Expression.check(node))
- return self.explodeExpression(path, ignoreResult);
- if (n.Declaration.check(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));
- }
- Ep.explodeStatement = function(path, labelId) {
- assert.ok(path instanceof types.NodePath);
- var stmt = path.value;
- var self = this;
- n.Statement.assert(stmt);
- if (labelId) {
- n.Identifier.assert(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 (n.BlockStatement.check(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;
- }
- switch (stmt.type) {
- case "ExpressionStatement":
- self.explodeExpression(path.get("expression"), true);
- break;
- case "LabeledStatement":
- var after = loc();
- // Did you know you can break from any labeled block statement or
- // control structure? Well, you can! Note: when a labeled loop is
- // encountered, the leap.LabeledEntry created here will immediately
- // enclose a leap.LoopEntry on the leap manager's stack, and both
- // entries will have the same label. Though this works just fine, it
- // may seem a bit redundant. In theory, we could check here to
- // determine if stmt knows how to handle its own label; for example,
- // stmt happens to be a WhileStatement and so we know it's going to
- // establish its own LoopEntry when we explode it (below). Then this
- // LabeledEntry would be unnecessary. Alternatively, we might be
- // tempted not to pass stmt.label down into self.explodeStatement,
- // because we've handled the label here, but that's a mistake because
- // labeled loops may contain labeled continue statements, which is not
- // something we can handle in this generic case. All in all, I think a
- // little redundancy greatly simplifies the logic of this case, since
- // it's clear that we handle all possible LabeledStatements correctly
- // here, regardless of whether they interact with the leap manager
- // themselves. Also remember that labels and break/continue-to-label
- // statements are rare, and all of this logic happens at transform
- // time, so it has no additional runtime cost.
- self.leapManager.withEntry(
- new leap.LabeledEntry(after, stmt.label),
- function() {
- self.explodeStatement(path.get("body"), stmt.label);
- }
- );
- self.mark(after);
- break;
- case "WhileStatement":
- var before = loc();
- var 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();
- var 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":
- 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.
- 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":
- n.Identifier.assert(stmt.left);
- var head = loc();
- var after = loc();
- var keyIterNextFn = self.makeTempVar();
- self.emitAssign(
- keyIterNextFn,
- b.callExpression(
- util.runtimeProperty("keys"),
- [self.explodeExpression(path.get("right"))]
- )
- );
- self.mark(head);
- var keyInfoTmpVar = self.makeTempVar();
- self.jumpIf(
- b.memberExpression(
- b.assignmentExpression(
- "=",
- keyInfoTmpVar,
- b.callExpression(keyIterNextFn, [])
- ),
- b.identifier("done"),
- false
- ),
- after
- );
- self.emitAssign(
- stmt.left,
- b.memberExpression(
- keyInfoTmpVar,
- b.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"))
- );
- var 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];
- n.SwitchCase.assert(c);
- if (c.test) {
- condition = b.conditionalExpression(
- b.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 c = casePath.value;
- 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();
- var 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 "WithStatement":
- throw new Error(
- node.type + " not supported in generator functions.");
- case "TryStatement":
- var 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, after);
- 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;
- n.CatchClause.assert(catchScope.node);
- assert.strictEqual(catchScope.lookup(catchParamName), catchScope);
- types.visit(bodyPath, {
- visitIdentifier: function(path) {
- if (util.isReference(path, catchParamName) &&
- path.scope.lookup(catchParamName) === catchScope) {
- return safeParam;
- }
- this.traverse(path);
- },
- visitFunction: function(path) {
- if (path.scope.declares(catchParamName)) {
- // Don't descend into nested scopes that shadow the catch
- // parameter with their own declarations. This isn't
- // logically necessary because of the path.scope.lookup we
- // do in visitIdentifier, but it saves time.
- return false;
- }
- 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(b.returnStatement(b.callExpression(
- self.contextProperty("finish"),
- [finallyEntry.firstLoc]
- )));
- }
- });
- self.mark(after);
- break;
- case "ThrowStatement":
- self.emit(b.throwStatement(
- self.explodeExpression(path.get("argument"))
- ));
- break;
- default:
- throw new Error(
- "unknown Statement of type " +
- JSON.stringify(stmt.type));
- }
- };
- Ep.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 = [b.literal(record.type)];
- if (record.type === "break" ||
- record.type === "continue") {
- n.Literal.assert(record.target);
- abruptArgs[1] = record.target;
- } else if (record.type === "return" ||
- record.type === "throw") {
- if (record.value) {
- n.Expression.assert(record.value);
- abruptArgs[1] = record.value;
- }
- }
- this.emit(
- b.returnStatement(
- b.callExpression(
- this.contextProperty("abrupt"),
- abruptArgs
- )
- )
- );
- };
- function isValidCompletion(record) {
- var type = record.type;
- if (type === "normal") {
- return !hasOwn.call(record, "target");
- }
- if (type === "break" ||
- type === "continue") {
- return !hasOwn.call(record, "value")
- && n.Literal.check(record.target);
- }
- if (type === "return" ||
- type === "throw") {
- return hasOwn.call(record, "value")
- && !hasOwn.call(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.
- Ep.getUnmarkedCurrentLoc = function() {
- return b.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.
- Ep.updateContextPrevLoc = function(loc) {
- if (loc) {
- n.Literal.assert(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);
- };
- Ep.explodeExpression = function(path, ignoreResult) {
- assert.ok(path instanceof types.NodePath);
- var expr = path.value;
- if (expr) {
- n.Expression.assert(expr);
- } else {
- return expr;
- }
- var self = this;
- var result; // Used optionally by several cases below.
- function finish(expr) {
- n.Expression.assert(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.
- switch (expr.type) {
- case "MemberExpression":
- return finish(b.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 (!n.MemberExpression.check(oldCalleePath.node) &&
- n.MemberExpression.check(newCallee)) {
- newCallee = b.sequenceExpression([
- b.literal(0),
- newCallee
- ]);
- }
- return finish(b.callExpression(
- newCallee,
- path.get("arguments").map(function(argPath) {
- return explodeViaTempVar(null, argPath);
- })
- ));
- case "NewExpression":
- return finish(b.newExpression(
- explodeViaTempVar(null, path.get("callee")),
- path.get("arguments").map(function(argPath) {
- return explodeViaTempVar(null, argPath);
- })
- ));
- case "ObjectExpression":
- return finish(b.objectExpression(
- path.get("properties").map(function(propPath) {
- return b.property(
- propPath.value.kind,
- propPath.value.key,
- explodeViaTempVar(null, propPath.get("value"))
- );
- })
- ));
- case "ArrayExpression":
- return finish(b.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":
- var 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();
- var 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(b.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(b.binaryExpression(
- expr.operator,
- explodeViaTempVar(null, path.get("left")),
- explodeViaTempVar(null, path.get("right"))
- ));
- case "AssignmentExpression":
- return finish(b.assignmentExpression(
- expr.operator,
- self.explodeExpression(path.get("left")),
- self.explodeExpression(path.get("right"))
- ));
- case "UpdateExpression":
- return finish(b.updateExpression(
- expr.operator,
- self.explodeExpression(path.get("argument")),
- expr.prefix
- ));
- case "YieldExpression":
- var after = loc();
- var arg = expr.argument && self.explodeExpression(path.get("argument"));
- if (arg && expr.delegate) {
- var result = self.makeTempVar();
- self.emit(b.returnStatement(b.callExpression(
- self.contextProperty("delegateYield"), [
- arg,
- b.literal(result.property.name),
- after
- ]
- )));
- self.mark(after);
- return result;
- }
- self.emitAssign(self.contextProperty("next"), after);
- self.emit(b.returnStatement(arg || null));
- self.mark(after);
- return self.contextProperty("sent");
- default:
- throw new Error(
- "unknown Expression of type " +
- JSON.stringify(expr.type));
- }
- };
|