source: imaps-frontend/node_modules/regenerator-transform/lib/emit.js@ 79a0317

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

F4 Finalna Verzija

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