source: imaps-frontend/node_modules/regenerator-transform/src/emit.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

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