source: trip-planner-front/node_modules/regenerator-transform/lib/emit.js@ 8d391a1

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

initial commit

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