source: trip-planner-front/node_modules/regenerator-transform/src/emit.js@ 188ee53

Last change on this file since 188ee53 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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