UNPKG

32.8 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2014, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * https://raw.github.com/facebook/regenerator/master/LICENSE file. An
7 * additional grant of patent rights can be found in the PATENTS file in
8 * the same directory.
9 */
10
11import assert from "assert";
12import * as t from "babel-types";
13import * as leap from "./leap";
14import * as meta from "./meta";
15import * as util from "./util";
16
17let hasOwn = Object.prototype.hasOwnProperty;
18
19function Emitter(contextId) {
20 assert.ok(this instanceof Emitter);
21 t.assertIdentifier(contextId);
22
23 // Used to generate unique temporary names.
24 this.nextTempId = 0;
25
26 // In order to make sure the context object does not collide with
27 // anything in the local scope, we might have to rename it, so we
28 // refer to it symbolically instead of just assuming that it will be
29 // called "context".
30 this.contextId = contextId;
31
32 // An append-only list of Statements that grows each time this.emit is
33 // called.
34 this.listing = [];
35
36 // A sparse array whose keys correspond to locations in this.listing
37 // that have been marked as branch/jump targets.
38 this.marked = [true];
39
40 // The last location will be marked when this.getDispatchLoop is
41 // called.
42 this.finalLoc = loc();
43
44 // A list of all leap.TryEntry statements emitted.
45 this.tryEntries = [];
46
47 // Each time we evaluate the body of a loop, we tell this.leapManager
48 // to enter a nested loop context that determines the meaning of break
49 // and continue statements therein.
50 this.leapManager = new leap.LeapManager(this);
51}
52
53let Ep = Emitter.prototype;
54exports.Emitter = Emitter;
55
56// Offsets into this.listing that could be used as targets for branches or
57// jumps are represented as numeric Literal nodes. This representation has
58// the amazingly convenient benefit of allowing the exact value of the
59// location to be determined at any time, even after generating code that
60// refers to the location.
61function loc() {
62 return t.numericLiteral(-1);
63}
64
65// Sets the exact value of the given location to the offset of the next
66// Statement emitted.
67Ep.mark = function(loc) {
68 t.assertLiteral(loc);
69 let index = this.listing.length;
70 if (loc.value === -1) {
71 loc.value = index;
72 } else {
73 // Locations can be marked redundantly, but their values cannot change
74 // once set the first time.
75 assert.strictEqual(loc.value, index);
76 }
77 this.marked[index] = true;
78 return loc;
79};
80
81Ep.emit = function(node) {
82 if (t.isExpression(node)) {
83 node = t.expressionStatement(node);
84 }
85
86 t.assertStatement(node);
87 this.listing.push(node);
88};
89
90// Shorthand for emitting assignment statements. This will come in handy
91// for assignments to temporary variables.
92Ep.emitAssign = function(lhs, rhs) {
93 this.emit(this.assign(lhs, rhs));
94 return lhs;
95};
96
97// Shorthand for an assignment statement.
98Ep.assign = function(lhs, rhs) {
99 return t.expressionStatement(
100 t.assignmentExpression("=", lhs, rhs));
101};
102
103// Convenience function for generating expressions like context.next,
104// context.sent, and context.rval.
105Ep.contextProperty = function(name, computed) {
106 return t.memberExpression(
107 this.contextId,
108 computed ? t.stringLiteral(name) : t.identifier(name),
109 !!computed
110 );
111};
112
113// Shorthand for setting context.rval and jumping to `context.stop()`.
114Ep.stop = function(rval) {
115 if (rval) {
116 this.setReturnValue(rval);
117 }
118
119 this.jump(this.finalLoc);
120};
121
122Ep.setReturnValue = function(valuePath) {
123 t.assertExpression(valuePath.value);
124
125 this.emitAssign(
126 this.contextProperty("rval"),
127 this.explodeExpression(valuePath)
128 );
129};
130
131Ep.clearPendingException = function(tryLoc, assignee) {
132 t.assertLiteral(tryLoc);
133
134 let catchCall = t.callExpression(
135 this.contextProperty("catch", true),
136 [tryLoc]
137 );
138
139 if (assignee) {
140 this.emitAssign(assignee, catchCall);
141 } else {
142 this.emit(catchCall);
143 }
144};
145
146// Emits code for an unconditional jump to the given location, even if the
147// exact value of the location is not yet known.
148Ep.jump = function(toLoc) {
149 this.emitAssign(this.contextProperty("next"), toLoc);
150 this.emit(t.breakStatement());
151};
152
153// Conditional jump.
154Ep.jumpIf = function(test, toLoc) {
155 t.assertExpression(test);
156 t.assertLiteral(toLoc);
157
158 this.emit(t.ifStatement(
159 test,
160 t.blockStatement([
161 this.assign(this.contextProperty("next"), toLoc),
162 t.breakStatement()
163 ])
164 ));
165};
166
167// Conditional jump, with the condition negated.
168Ep.jumpIfNot = function(test, toLoc) {
169 t.assertExpression(test);
170 t.assertLiteral(toLoc);
171
172 let negatedTest;
173 if (t.isUnaryExpression(test) &&
174 test.operator === "!") {
175 // Avoid double negation.
176 negatedTest = test.argument;
177 } else {
178 negatedTest = t.unaryExpression("!", test);
179 }
180
181 this.emit(t.ifStatement(
182 negatedTest,
183 t.blockStatement([
184 this.assign(this.contextProperty("next"), toLoc),
185 t.breakStatement()
186 ])
187 ));
188};
189
190// Returns a unique MemberExpression that can be used to store and
191// retrieve temporary values. Since the object of the member expression is
192// the context object, which is presumed to coexist peacefully with all
193// other local variables, and since we just increment `nextTempId`
194// monotonically, uniqueness is assured.
195Ep.makeTempVar = function() {
196 return this.contextProperty("t" + this.nextTempId++);
197};
198
199Ep.getContextFunction = function(id) {
200 return t.functionExpression(
201 id || null/*Anonymous*/,
202 [this.contextId],
203 t.blockStatement([this.getDispatchLoop()]),
204 false, // Not a generator anymore!
205 false // Nor an expression.
206 );
207};
208
209// Turns this.listing into a loop of the form
210//
211// while (1) switch (context.next) {
212// case 0:
213// ...
214// case n:
215// return context.stop();
216// }
217//
218// Each marked location in this.listing will correspond to one generated
219// case statement.
220Ep.getDispatchLoop = function() {
221 let self = this;
222 let cases = [];
223 let current;
224
225 // If we encounter a break, continue, or return statement in a switch
226 // case, we can skip the rest of the statements until the next case.
227 let alreadyEnded = false;
228
229 self.listing.forEach(function(stmt, i) {
230 if (self.marked.hasOwnProperty(i)) {
231 cases.push(t.switchCase(
232 t.numericLiteral(i),
233 current = []));
234 alreadyEnded = false;
235 }
236
237 if (!alreadyEnded) {
238 current.push(stmt);
239 if (t.isCompletionStatement(stmt))
240 alreadyEnded = true;
241 }
242 });
243
244 // Now that we know how many statements there will be in this.listing,
245 // we can finally resolve this.finalLoc.value.
246 this.finalLoc.value = this.listing.length;
247
248 cases.push(
249 t.switchCase(this.finalLoc, [
250 // Intentionally fall through to the "end" case...
251 ]),
252
253 // So that the runtime can jump to the final location without having
254 // to know its offset, we provide the "end" case as a synonym.
255 t.switchCase(t.stringLiteral("end"), [
256 // This will check/clear both context.thrown and context.rval.
257 t.returnStatement(
258 t.callExpression(this.contextProperty("stop"), [])
259 )
260 ])
261 );
262
263 return t.whileStatement(
264 t.numericLiteral(1),
265 t.switchStatement(
266 t.assignmentExpression(
267 "=",
268 this.contextProperty("prev"),
269 this.contextProperty("next")
270 ),
271 cases
272 )
273 );
274};
275
276Ep.getTryLocsList = function() {
277 if (this.tryEntries.length === 0) {
278 // To avoid adding a needless [] to the majority of runtime.wrap
279 // argument lists, force the caller to handle this case specially.
280 return null;
281 }
282
283 let lastLocValue = 0;
284
285 return t.arrayExpression(
286 this.tryEntries.map(function(tryEntry) {
287 let thisLocValue = tryEntry.firstLoc.value;
288 assert.ok(thisLocValue >= lastLocValue, "try entries out of order");
289 lastLocValue = thisLocValue;
290
291 let ce = tryEntry.catchEntry;
292 let fe = tryEntry.finallyEntry;
293
294 let locs = [
295 tryEntry.firstLoc,
296 // The null here makes a hole in the array.
297 ce ? ce.firstLoc : null
298 ];
299
300 if (fe) {
301 locs[2] = fe.firstLoc;
302 locs[3] = fe.afterLoc;
303 }
304
305 return t.arrayExpression(locs);
306 })
307 );
308};
309
310// All side effects must be realized in order.
311
312// If any subexpression harbors a leap, all subexpressions must be
313// neutered of side effects.
314
315// No destructive modification of AST nodes.
316
317Ep.explode = function(path, ignoreResult) {
318 let node = path.node;
319 let self = this;
320
321 t.assertNode(node);
322
323 if (t.isDeclaration(node))
324 throw getDeclError(node);
325
326 if (t.isStatement(node))
327 return self.explodeStatement(path);
328
329 if (t.isExpression(node))
330 return self.explodeExpression(path, ignoreResult);
331
332 switch (node.type) {
333 case "Program":
334 return path.get("body").map(
335 self.explodeStatement,
336 self
337 );
338
339 case "VariableDeclarator":
340 throw getDeclError(node);
341
342 // These node types should be handled by their parent nodes
343 // (ObjectExpression, SwitchStatement, and TryStatement, respectively).
344 case "Property":
345 case "SwitchCase":
346 case "CatchClause":
347 throw new Error(
348 node.type + " nodes should be handled by their parents");
349
350 default:
351 throw new Error(
352 "unknown Node of type " +
353 JSON.stringify(node.type));
354 }
355};
356
357function getDeclError(node) {
358 return new Error(
359 "all declarations should have been transformed into " +
360 "assignments before the Exploder began its work: " +
361 JSON.stringify(node));
362}
363
364Ep.explodeStatement = function(path, labelId) {
365 let stmt = path.node;
366 let self = this;
367 let before, after, head;
368
369 t.assertStatement(stmt);
370
371 if (labelId) {
372 t.assertIdentifier(labelId);
373 } else {
374 labelId = null;
375 }
376
377 // Explode BlockStatement nodes even if they do not contain a yield,
378 // because we don't want or need the curly braces.
379 if (t.isBlockStatement(stmt)) {
380 path.get("body").forEach(function (path) {
381 self.explodeStatement(path);
382 });
383 return;
384 }
385
386 if (!meta.containsLeap(stmt)) {
387 // Technically we should be able to avoid emitting the statement
388 // altogether if !meta.hasSideEffects(stmt), but that leads to
389 // confusing generated code (for instance, `while (true) {}` just
390 // disappears) and is probably a more appropriate job for a dedicated
391 // dead code elimination pass.
392 self.emit(stmt);
393 return;
394 }
395
396 switch (stmt.type) {
397 case "ExpressionStatement":
398 self.explodeExpression(path.get("expression"), true);
399 break;
400
401 case "LabeledStatement":
402 after = loc();
403
404 // Did you know you can break from any labeled block statement or
405 // control structure? Well, you can! Note: when a labeled loop is
406 // encountered, the leap.LabeledEntry created here will immediately
407 // enclose a leap.LoopEntry on the leap manager's stack, and both
408 // entries will have the same label. Though this works just fine, it
409 // may seem a bit redundant. In theory, we could check here to
410 // determine if stmt knows how to handle its own label; for example,
411 // stmt happens to be a WhileStatement and so we know it's going to
412 // establish its own LoopEntry when we explode it (below). Then this
413 // LabeledEntry would be unnecessary. Alternatively, we might be
414 // tempted not to pass stmt.label down into self.explodeStatement,
415 // because we've handled the label here, but that's a mistake because
416 // labeled loops may contain labeled continue statements, which is not
417 // something we can handle in this generic case. All in all, I think a
418 // little redundancy greatly simplifies the logic of this case, since
419 // it's clear that we handle all possible LabeledStatements correctly
420 // here, regardless of whether they interact with the leap manager
421 // themselves. Also remember that labels and break/continue-to-label
422 // statements are rare, and all of this logic happens at transform
423 // time, so it has no additional runtime cost.
424 self.leapManager.withEntry(
425 new leap.LabeledEntry(after, stmt.label),
426 function() {
427 self.explodeStatement(path.get("body"), stmt.label);
428 }
429 );
430
431 self.mark(after);
432
433 break;
434
435 case "WhileStatement":
436 before = loc();
437 after = loc();
438
439 self.mark(before);
440 self.jumpIfNot(self.explodeExpression(path.get("test")), after);
441 self.leapManager.withEntry(
442 new leap.LoopEntry(after, before, labelId),
443 function() { self.explodeStatement(path.get("body")); }
444 );
445 self.jump(before);
446 self.mark(after);
447
448 break;
449
450 case "DoWhileStatement":
451 let first = loc();
452 let test = loc();
453 after = loc();
454
455 self.mark(first);
456 self.leapManager.withEntry(
457 new leap.LoopEntry(after, test, labelId),
458 function() { self.explode(path.get("body")); }
459 );
460 self.mark(test);
461 self.jumpIf(self.explodeExpression(path.get("test")), first);
462 self.mark(after);
463
464 break;
465
466 case "ForStatement":
467 head = loc();
468 let update = loc();
469 after = loc();
470
471 if (stmt.init) {
472 // We pass true here to indicate that if stmt.init is an expression
473 // then we do not care about its result.
474 self.explode(path.get("init"), true);
475 }
476
477 self.mark(head);
478
479 if (stmt.test) {
480 self.jumpIfNot(self.explodeExpression(path.get("test")), after);
481 } else {
482 // No test means continue unconditionally.
483 }
484
485 self.leapManager.withEntry(
486 new leap.LoopEntry(after, update, labelId),
487 function() { self.explodeStatement(path.get("body")); }
488 );
489
490 self.mark(update);
491
492 if (stmt.update) {
493 // We pass true here to indicate that if stmt.update is an
494 // expression then we do not care about its result.
495 self.explode(path.get("update"), true);
496 }
497
498 self.jump(head);
499
500 self.mark(after);
501
502 break;
503
504 case "TypeCastExpression":
505 return self.explodeExpression(path.get("expression"));
506
507 case "ForInStatement":
508 head = loc();
509 after = loc();
510
511 let keyIterNextFn = self.makeTempVar();
512 self.emitAssign(
513 keyIterNextFn,
514 t.callExpression(
515 util.runtimeProperty("keys"),
516 [self.explodeExpression(path.get("right"))]
517 )
518 );
519
520 self.mark(head);
521
522 let keyInfoTmpVar = self.makeTempVar();
523 self.jumpIf(
524 t.memberExpression(
525 t.assignmentExpression(
526 "=",
527 keyInfoTmpVar,
528 t.callExpression(keyIterNextFn, [])
529 ),
530 t.identifier("done"),
531 false
532 ),
533 after
534 );
535
536 self.emitAssign(
537 stmt.left,
538 t.memberExpression(
539 keyInfoTmpVar,
540 t.identifier("value"),
541 false
542 )
543 );
544
545 self.leapManager.withEntry(
546 new leap.LoopEntry(after, head, labelId),
547 function() { self.explodeStatement(path.get("body")); }
548 );
549
550 self.jump(head);
551
552 self.mark(after);
553
554 break;
555
556 case "BreakStatement":
557 self.emitAbruptCompletion({
558 type: "break",
559 target: self.leapManager.getBreakLoc(stmt.label)
560 });
561
562 break;
563
564 case "ContinueStatement":
565 self.emitAbruptCompletion({
566 type: "continue",
567 target: self.leapManager.getContinueLoc(stmt.label)
568 });
569
570 break;
571
572 case "SwitchStatement":
573 // Always save the discriminant into a temporary variable in case the
574 // test expressions overwrite values like context.sent.
575 let disc = self.emitAssign(
576 self.makeTempVar(),
577 self.explodeExpression(path.get("discriminant"))
578 );
579
580 after = loc();
581 let defaultLoc = loc();
582 let condition = defaultLoc;
583 let caseLocs = [];
584
585 // If there are no cases, .cases might be undefined.
586 let cases = stmt.cases || [];
587
588 for (let i = cases.length - 1; i >= 0; --i) {
589 let c = cases[i];
590 t.assertSwitchCase(c);
591
592 if (c.test) {
593 condition = t.conditionalExpression(
594 t.binaryExpression("===", disc, c.test),
595 caseLocs[i] = loc(),
596 condition
597 );
598 } else {
599 caseLocs[i] = defaultLoc;
600 }
601 }
602
603 let discriminant = path.get("discriminant");
604 discriminant.replaceWith(condition);
605 self.jump(self.explodeExpression(discriminant));
606
607 self.leapManager.withEntry(
608 new leap.SwitchEntry(after),
609 function() {
610 path.get("cases").forEach(function(casePath) {
611 let i = casePath.key;
612 self.mark(caseLocs[i]);
613
614 casePath.get("consequent").forEach(function (path) {
615 self.explodeStatement(path);
616 });
617 });
618 }
619 );
620
621 self.mark(after);
622 if (defaultLoc.value === -1) {
623 self.mark(defaultLoc);
624 assert.strictEqual(after.value, defaultLoc.value);
625 }
626
627 break;
628
629 case "IfStatement":
630 let elseLoc = stmt.alternate && loc();
631 after = loc();
632
633 self.jumpIfNot(
634 self.explodeExpression(path.get("test")),
635 elseLoc || after
636 );
637
638 self.explodeStatement(path.get("consequent"));
639
640 if (elseLoc) {
641 self.jump(after);
642 self.mark(elseLoc);
643 self.explodeStatement(path.get("alternate"));
644 }
645
646 self.mark(after);
647
648 break;
649
650 case "ReturnStatement":
651 self.emitAbruptCompletion({
652 type: "return",
653 value: self.explodeExpression(path.get("argument"))
654 });
655
656 break;
657
658 case "WithStatement":
659 throw new Error("WithStatement not supported in generator functions.");
660
661 case "TryStatement":
662 after = loc();
663
664 let handler = stmt.handler;
665
666 let catchLoc = handler && loc();
667 let catchEntry = catchLoc && new leap.CatchEntry(
668 catchLoc,
669 handler.param
670 );
671
672 let finallyLoc = stmt.finalizer && loc();
673 let finallyEntry = finallyLoc &&
674 new leap.FinallyEntry(finallyLoc, after);
675
676 let tryEntry = new leap.TryEntry(
677 self.getUnmarkedCurrentLoc(),
678 catchEntry,
679 finallyEntry
680 );
681
682 self.tryEntries.push(tryEntry);
683 self.updateContextPrevLoc(tryEntry.firstLoc);
684
685 self.leapManager.withEntry(tryEntry, function() {
686 self.explodeStatement(path.get("block"));
687
688 if (catchLoc) {
689 if (finallyLoc) {
690 // If we have both a catch block and a finally block, then
691 // because we emit the catch block first, we need to jump over
692 // it to the finally block.
693 self.jump(finallyLoc);
694
695 } else {
696 // If there is no finally block, then we need to jump over the
697 // catch block to the fall-through location.
698 self.jump(after);
699 }
700
701 self.updateContextPrevLoc(self.mark(catchLoc));
702
703 let bodyPath = path.get("handler.body");
704 let safeParam = self.makeTempVar();
705 self.clearPendingException(tryEntry.firstLoc, safeParam);
706
707 bodyPath.traverse(catchParamVisitor, {
708 safeParam: safeParam,
709 catchParamName: handler.param.name
710 });
711
712 self.leapManager.withEntry(catchEntry, function() {
713 self.explodeStatement(bodyPath);
714 });
715 }
716
717 if (finallyLoc) {
718 self.updateContextPrevLoc(self.mark(finallyLoc));
719
720 self.leapManager.withEntry(finallyEntry, function() {
721 self.explodeStatement(path.get("finalizer"));
722 });
723
724 self.emit(t.returnStatement(t.callExpression(
725 self.contextProperty("finish"),
726 [finallyEntry.firstLoc]
727 )));
728 }
729 });
730
731 self.mark(after);
732
733 break;
734
735 case "ThrowStatement":
736 self.emit(t.throwStatement(
737 self.explodeExpression(path.get("argument"))
738 ));
739
740 break;
741
742 default:
743 throw new Error(
744 "unknown Statement of type " +
745 JSON.stringify(stmt.type));
746 }
747};
748
749let catchParamVisitor = {
750 Identifier: function(path, state) {
751 if (path.node.name === state.catchParamName && util.isReference(path)) {
752 path.replaceWith(state.safeParam);
753 }
754 },
755
756 Scope: function(path, state) {
757 if (path.scope.hasOwnBinding(state.catchParamName)) {
758 // Don't descend into nested scopes that shadow the catch
759 // parameter with their own declarations.
760 path.skip();
761 }
762 }
763};
764
765Ep.emitAbruptCompletion = function(record) {
766 if (!isValidCompletion(record)) {
767 assert.ok(
768 false,
769 "invalid completion record: " +
770 JSON.stringify(record)
771 );
772 }
773
774 assert.notStrictEqual(
775 record.type, "normal",
776 "normal completions are not abrupt"
777 );
778
779 let abruptArgs = [t.stringLiteral(record.type)];
780
781 if (record.type === "break" ||
782 record.type === "continue") {
783 t.assertLiteral(record.target);
784 abruptArgs[1] = record.target;
785 } else if (record.type === "return" ||
786 record.type === "throw") {
787 if (record.value) {
788 t.assertExpression(record.value);
789 abruptArgs[1] = record.value;
790 }
791 }
792
793 this.emit(
794 t.returnStatement(
795 t.callExpression(
796 this.contextProperty("abrupt"),
797 abruptArgs
798 )
799 )
800 );
801};
802
803function isValidCompletion(record) {
804 let type = record.type;
805
806 if (type === "normal") {
807 return !hasOwn.call(record, "target");
808 }
809
810 if (type === "break" ||
811 type === "continue") {
812 return !hasOwn.call(record, "value")
813 && t.isLiteral(record.target);
814 }
815
816 if (type === "return" ||
817 type === "throw") {
818 return hasOwn.call(record, "value")
819 && !hasOwn.call(record, "target");
820 }
821
822 return false;
823}
824
825
826// Not all offsets into emitter.listing are potential jump targets. For
827// example, execution typically falls into the beginning of a try block
828// without jumping directly there. This method returns the current offset
829// without marking it, so that a switch case will not necessarily be
830// generated for this offset (I say "not necessarily" because the same
831// location might end up being marked in the process of emitting other
832// statements). There's no logical harm in marking such locations as jump
833// targets, but minimizing the number of switch cases keeps the generated
834// code shorter.
835Ep.getUnmarkedCurrentLoc = function() {
836 return t.numericLiteral(this.listing.length);
837};
838
839// The context.prev property takes the value of context.next whenever we
840// evaluate the switch statement discriminant, which is generally good
841// enough for tracking the last location we jumped to, but sometimes
842// context.prev needs to be more precise, such as when we fall
843// successfully out of a try block and into a finally block without
844// jumping. This method exists to update context.prev to the freshest
845// available location. If we were implementing a full interpreter, we
846// would know the location of the current instruction with complete
847// precision at all times, but we don't have that luxury here, as it would
848// be costly and verbose to set context.prev before every statement.
849Ep.updateContextPrevLoc = function(loc) {
850 if (loc) {
851 t.assertLiteral(loc);
852
853 if (loc.value === -1) {
854 // If an uninitialized location literal was passed in, set its value
855 // to the current this.listing.length.
856 loc.value = this.listing.length;
857 } else {
858 // Otherwise assert that the location matches the current offset.
859 assert.strictEqual(loc.value, this.listing.length);
860 }
861
862 } else {
863 loc = this.getUnmarkedCurrentLoc();
864 }
865
866 // Make sure context.prev is up to date in case we fell into this try
867 // statement without jumping to it. TODO Consider avoiding this
868 // assignment when we know control must have jumped here.
869 this.emitAssign(this.contextProperty("prev"), loc);
870};
871
872Ep.explodeExpression = function(path, ignoreResult) {
873 let expr = path.node;
874 if (expr) {
875 t.assertExpression(expr);
876 } else {
877 return expr;
878 }
879
880 let self = this;
881 let result; // Used optionally by several cases below.
882 let after;
883
884 function finish(expr) {
885 t.assertExpression(expr);
886 if (ignoreResult) {
887 self.emit(expr);
888 } else {
889 return expr;
890 }
891 }
892
893 // If the expression does not contain a leap, then we either emit the
894 // expression as a standalone statement or return it whole.
895 if (!meta.containsLeap(expr)) {
896 return finish(expr);
897 }
898
899 // If any child contains a leap (such as a yield or labeled continue or
900 // break statement), then any sibling subexpressions will almost
901 // certainly have to be exploded in order to maintain the order of their
902 // side effects relative to the leaping child(ren).
903 let hasLeapingChildren = meta.containsLeap.onlyChildren(expr);
904
905 // In order to save the rest of explodeExpression from a combinatorial
906 // trainwreck of special cases, explodeViaTempVar is responsible for
907 // deciding when a subexpression needs to be "exploded," which is my
908 // very technical term for emitting the subexpression as an assignment
909 // to a temporary variable and the substituting the temporary variable
910 // for the original subexpression. Think of exploded view diagrams, not
911 // Michael Bay movies. The point of exploding subexpressions is to
912 // control the precise order in which the generated code realizes the
913 // side effects of those subexpressions.
914 function explodeViaTempVar(tempVar, childPath, ignoreChildResult) {
915 assert.ok(
916 !ignoreChildResult || !tempVar,
917 "Ignoring the result of a child expression but forcing it to " +
918 "be assigned to a temporary variable?"
919 );
920
921 let result = self.explodeExpression(childPath, ignoreChildResult);
922
923 if (ignoreChildResult) {
924 // Side effects already emitted above.
925
926 } else if (tempVar || (hasLeapingChildren &&
927 !t.isLiteral(result))) {
928 // If tempVar was provided, then the result will always be assigned
929 // to it, even if the result does not otherwise need to be assigned
930 // to a temporary variable. When no tempVar is provided, we have
931 // the flexibility to decide whether a temporary variable is really
932 // necessary. Unfortunately, in general, a temporary variable is
933 // required whenever any child contains a yield expression, since it
934 // is difficult to prove (at all, let alone efficiently) whether
935 // this result would evaluate to the same value before and after the
936 // yield (see #206). One narrow case where we can prove it doesn't
937 // matter (and thus we do not need a temporary variable) is when the
938 // result in question is a Literal value.
939 result = self.emitAssign(
940 tempVar || self.makeTempVar(),
941 result
942 );
943 }
944 return result;
945 }
946
947 // If ignoreResult is true, then we must take full responsibility for
948 // emitting the expression with all its side effects, and we should not
949 // return a result.
950
951 switch (expr.type) {
952 case "MemberExpression":
953 return finish(t.memberExpression(
954 self.explodeExpression(path.get("object")),
955 expr.computed
956 ? explodeViaTempVar(null, path.get("property"))
957 : expr.property,
958 expr.computed
959 ));
960
961 case "CallExpression":
962 let calleePath = path.get("callee");
963 let argsPath = path.get("arguments");
964
965 let newCallee;
966 let newArgs = [];
967
968 let hasLeapingArgs = false;
969 argsPath.forEach(function(argPath) {
970 hasLeapingArgs = hasLeapingArgs ||
971 meta.containsLeap(argPath.node);
972 });
973
974 if (t.isMemberExpression(calleePath.node)) {
975 if (hasLeapingArgs) {
976 // If the arguments of the CallExpression contained any yield
977 // expressions, then we need to be sure to evaluate the callee
978 // before evaluating the arguments, but if the callee was a member
979 // expression, then we must be careful that the object of the
980 // member expression still gets bound to `this` for the call.
981
982 let newObject = explodeViaTempVar(
983 // Assign the exploded callee.object expression to a temporary
984 // variable so that we can use it twice without reevaluating it.
985 self.makeTempVar(),
986 calleePath.get("object")
987 );
988
989 let newProperty = calleePath.node.computed
990 ? explodeViaTempVar(null, calleePath.get("property"))
991 : calleePath.node.property;
992
993 newArgs.unshift(newObject);
994
995 newCallee = t.memberExpression(
996 t.memberExpression(
997 newObject,
998 newProperty,
999 calleePath.node.computed
1000 ),
1001 t.identifier("call"),
1002 false
1003 );
1004
1005 } else {
1006 newCallee = self.explodeExpression(calleePath);
1007 }
1008
1009 } else {
1010 newCallee = self.explodeExpression(calleePath);
1011
1012 if (t.isMemberExpression(newCallee)) {
1013 // If the callee was not previously a MemberExpression, then the
1014 // CallExpression was "unqualified," meaning its `this` object
1015 // should be the global object. If the exploded expression has
1016 // become a MemberExpression (e.g. a context property, probably a
1017 // temporary variable), then we need to force it to be unqualified
1018 // by using the (0, object.property)(...) trick; otherwise, it
1019 // will receive the object of the MemberExpression as its `this`
1020 // object.
1021 newCallee = t.sequenceExpression([
1022 t.numericLiteral(0),
1023 newCallee
1024 ]);
1025 }
1026 }
1027
1028 argsPath.forEach(function(argPath) {
1029 newArgs.push(explodeViaTempVar(null, argPath));
1030 });
1031
1032 return finish(t.callExpression(
1033 newCallee,
1034 newArgs
1035 ));
1036
1037 case "NewExpression":
1038 return finish(t.newExpression(
1039 explodeViaTempVar(null, path.get("callee")),
1040 path.get("arguments").map(function(argPath) {
1041 return explodeViaTempVar(null, argPath);
1042 })
1043 ));
1044
1045 case "ObjectExpression":
1046 return finish(t.objectExpression(
1047 path.get("properties").map(function(propPath) {
1048 if (propPath.isObjectProperty()) {
1049 return t.objectProperty(
1050 propPath.node.key,
1051 explodeViaTempVar(null, propPath.get("value")),
1052 propPath.node.computed
1053 );
1054 } else {
1055 return propPath.node;
1056 }
1057 })
1058 ));
1059
1060 case "ArrayExpression":
1061 return finish(t.arrayExpression(
1062 path.get("elements").map(function(elemPath) {
1063 return explodeViaTempVar(null, elemPath);
1064 })
1065 ));
1066
1067 case "SequenceExpression":
1068 let lastIndex = expr.expressions.length - 1;
1069
1070 path.get("expressions").forEach(function(exprPath) {
1071 if (exprPath.key === lastIndex) {
1072 result = self.explodeExpression(exprPath, ignoreResult);
1073 } else {
1074 self.explodeExpression(exprPath, true);
1075 }
1076 });
1077
1078 return result;
1079
1080 case "LogicalExpression":
1081 after = loc();
1082
1083 if (!ignoreResult) {
1084 result = self.makeTempVar();
1085 }
1086
1087 let left = explodeViaTempVar(result, path.get("left"));
1088
1089 if (expr.operator === "&&") {
1090 self.jumpIfNot(left, after);
1091 } else {
1092 assert.strictEqual(expr.operator, "||");
1093 self.jumpIf(left, after);
1094 }
1095
1096 explodeViaTempVar(result, path.get("right"), ignoreResult);
1097
1098 self.mark(after);
1099
1100 return result;
1101
1102 case "ConditionalExpression":
1103 let elseLoc = loc();
1104 after = loc();
1105 let test = self.explodeExpression(path.get("test"));
1106
1107 self.jumpIfNot(test, elseLoc);
1108
1109 if (!ignoreResult) {
1110 result = self.makeTempVar();
1111 }
1112
1113 explodeViaTempVar(result, path.get("consequent"), ignoreResult);
1114 self.jump(after);
1115
1116 self.mark(elseLoc);
1117 explodeViaTempVar(result, path.get("alternate"), ignoreResult);
1118
1119 self.mark(after);
1120
1121 return result;
1122
1123 case "UnaryExpression":
1124 return finish(t.unaryExpression(
1125 expr.operator,
1126 // Can't (and don't need to) break up the syntax of the argument.
1127 // Think about delete a[b].
1128 self.explodeExpression(path.get("argument")),
1129 !!expr.prefix
1130 ));
1131
1132 case "BinaryExpression":
1133 return finish(t.binaryExpression(
1134 expr.operator,
1135 explodeViaTempVar(null, path.get("left")),
1136 explodeViaTempVar(null, path.get("right"))
1137 ));
1138
1139 case "AssignmentExpression":
1140 return finish(t.assignmentExpression(
1141 expr.operator,
1142 self.explodeExpression(path.get("left")),
1143 self.explodeExpression(path.get("right"))
1144 ));
1145
1146 case "UpdateExpression":
1147 return finish(t.updateExpression(
1148 expr.operator,
1149 self.explodeExpression(path.get("argument")),
1150 expr.prefix
1151 ));
1152
1153 case "YieldExpression":
1154 after = loc();
1155 let arg = expr.argument && self.explodeExpression(path.get("argument"));
1156
1157 if (arg && expr.delegate) {
1158 let result = self.makeTempVar();
1159
1160 self.emit(t.returnStatement(t.callExpression(
1161 self.contextProperty("delegateYield"), [
1162 arg,
1163 t.stringLiteral(result.property.name),
1164 after
1165 ]
1166 )));
1167
1168 self.mark(after);
1169
1170 return result;
1171 }
1172
1173 self.emitAssign(self.contextProperty("next"), after);
1174 self.emit(t.returnStatement(arg || null));
1175 self.mark(after);
1176
1177 return self.contextProperty("sent");
1178
1179 default:
1180 throw new Error(
1181 "unknown Expression of type " +
1182 JSON.stringify(expr.type));
1183 }
1184};