Yield Ahead: Regenerator in Depth


Ben Newman (Facebook)
JSConf 2014

{ github,
  twitter,
  instagram,
  facebook
}.com/benjamn

facebook.github.io/regenerator

“We should have been doing it this way all along!”

GitHub is strewn with better ways of doing things that never quite caught on.

There has to be a way forward.

Programming languages are notoriously difficult to “fix forward.”


Why isn't every Python project using Python 3 yet?

How can ECMAScript 6
avoid the Python 3 trap?


Crazy idea: ease into the new language by simulating its most useful features in the current version of JavaScript (ECMAScript 5).

“Linguistic time travel”

Example: => function syntax


Input (ES6):

[3, 1, 10, 28].sort((a, b) => a - b)


Output (ES5):

            

First, import some utilities and parse the code:


var recast = require("recast");
var types = recast.types;
var traverse = types.traverse;
var n = types.namedTypes;
var b = types.builders;

var ast = recast.parse(
  "[3, 1, 10, 28].sort((a, b) => a - b)"
);

Next, traverse and modify the syntax tree:


traverse(ast, function(node) {




















});

Next, traverse and modify the syntax tree:


traverse(ast, function(node) {
  if (n.ArrowFunctionExpression.check(node)) {


















  }
});

Next, traverse and modify the syntax tree:


traverse(ast, function(node) {
  if (n.ArrowFunctionExpression.check(node)) {
    var body = node.body;

    if (node.expression) {
      node.expression = false;
      body = b.blockStatement([b.returnStatement(body)]);
    }












  }
});

Next, traverse and modify the syntax tree:


traverse(ast, function(node) {
  if (n.ArrowFunctionExpression.check(node)) {
    var body = node.body;

    if (node.expression) {
      node.expression = false;
      body = b.blockStatement([b.returnStatement(body)]);
    }

    var funExp = b.functionExpression(
      node.id, node.params, body,
      node.generator, node.expression
    );







  }
});

Next, traverse and modify the syntax tree:


traverse(ast, function(node) {
  if (n.ArrowFunctionExpression.check(node)) {
    var body = node.body;

    if (node.expression) {
      node.expression = false;
      body = b.blockStatement([b.returnStatement(body)]);
    }

    var funExp = b.functionExpression(
      node.id, node.params, body,
      node.generator, node.expression
    );

    var bindExp = b.callExpression(
      b.memberExpression(funExp, b.identifier("bind"), false),
      [b.thisExpression()]
    );


  }
});

Next, traverse and modify the syntax tree:


traverse(ast, function(node) {
  if (n.ArrowFunctionExpression.check(node)) {
    var body = node.body;

    if (node.expression) {
      node.expression = false;
      body = b.blockStatement([b.returnStatement(body)]);
    }

    var funExp = b.functionExpression(
      node.id, node.params, body,
      node.generator, node.expression
    );

    var bindExp = b.callExpression(
      b.memberExpression(funExp, b.identifier("bind"), false),
      [b.thisExpression()]
    );

    this.replace(bindExp);
  }
});

Finally, reprint the code:


console.log(recast.print(ast).code);

// Which prints:
[3, 1, 10, 28].sort(function(a, b) {
  return a - b;
}.bind(this))

If you already have a build step for static resources, you can be cooking with arrow functions in a matter of minutes!

recast, v.


  1. to give (a metal object) a different form by melting it down and reshaping it.
  2. to form, fashion, or arrange again.
  3. to remodel or reconstruct (a literary work, document, sentence, etc.).
  4. to supply (a theater or opera work) with a new cast.

Recast recap:


var recast = require("recast");
var ast = recast.parse(source);
transform(ast); // Anything goes.
console.log(recast.print(ast).code);

“Non-destructive partial source transformation”

Ariya Hidayat

Moral hazard:


  • be the laziest, most productive programmer you know
  • be the the change you want to see in the world

Confession:


I gave into temptation for a time.

All told:


1647 files changed
76555 insertions(+)
78260 deletions(-)

1658 classes converted
3582 classes today
find ~/www/html/js/lib | \
  grep "\.js$" | \
  time parallel ~/www/scripts/bin/classify --update

228.03s user 12.25s system 1229% cpu 19.548 total


In case you're curious about the details, I gave a whole talk about this project two months ago: slides, video.

What about something trickier?


function *permutations(list) {
  if (list.length < 2) {
    yield list;
  } else {
    var first = list.slice(0, 1);
    var ps = permutations(list.slice(1));
    for (var p of ps) {
      for (var i = 0; i < list.length; ++i) {
        yield Array.prototype.concat.call(
          p.slice(0, i), // prefix
          first,         // middle
          p.slice(i)     // suffix
        );
      }
    }
  }
}
var g = permutations([1, 3, 2]);

g.next().value; // [1, 3, 2]
g.next().value; // [3, 1, 2]
g.next().value; // [3, 2, 1]
g.next().value; // [1, 2, 3]
g.next().value; // [2, 1, 3]
g.next().value; // [2, 3, 1]
g.next().done; // true

What about something trickier?


function *permutations(list) {
  if (list.length < 2) {
    yield list;
  } else {
    var first = list.slice(0, 1);
    var ps = permutations(list.slice(1));
    for (var p of ps) {
      for (var i = 0; i < list.length; ++i) {
        yield Array.prototype.concat.call(
          p.slice(0, i), // prefix
          first,         // middle
          p.slice(i)     // suffix
        );
      }
    }
  }
}
var g = permutations([1, 3, 2]);
var count = 0;

for (var p of g) {
  console.log(p);
  ++count;
}

console.log(count); // 6
How hard could it be to translate this function into one that no longer contains function* or yield?

It's the trickiest code I've ever written.

function permutations(list) {
  if (list.length < 2) {
    yield list;
  } else {
    var first = list.slice(0, 1);
    var ps = permutations(list.slice(1));
    for (var p of ps) {
      for (var i = 0; i < list.length; ++i) {
        yield Array.prototype.concat.call(
          p.slice(0, i), // prefix
          first,         // middle
          p.slice(i)     // suffix
        );
      }
    }
  }
}































function permutations(list) {
  var first, ps, p, i;

  if (list.length < 2) {
    yield list;
  } else {
    first = list.slice(0, 1);
    ps = permutations(list.slice(1));
    for (p of ps) {
      for (i = 0; i < list.length; ++i) {
        yield Array.prototype.concat.call(
          p.slice(0, i), // prefix
          first,         // middle
          p.slice(i)     // suffix
        );
      }
    }
  }
}





























function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  if (list.length < 2) {
    yield list;
  } else {
    first = list.slice(0, 1);
    ps = permutations(list.slice(1));
    for (p of ps) {
      for (i = 0; i < list.length; ++i) {
        yield Array.prototype.concat.call(
          p.slice(0, i), // prefix
          first,         // middle
          p.slice(i)     // suffix
        );
      }
    }
  }
}





            
























function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  if (list.length < 2) {
    yield list;
  } else {
    first = list.slice(0, 1);
    ps = permutations(list.slice(1));
    t$0 = wrapGenerator.values(ps);
    while (!(t$1 = t$0.next()).done) {
      p = t$1.value;
      for (i = 0; i < list.length; ++i) {
        yield Array.prototype.concat.call(
          p.slice(0, i), // prefix
          first,         // middle
          p.slice(i)     // suffix
        );
      }
    }
  }
}



























function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function*() {
    if (list.length < 2) {
      yield list;
    } else {
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);
      while (!(t$1 = t$0.next()).done) {
        p = t$1.value;
        for (i = 0; i < list.length; ++i) {
          yield Array.prototype.concat.call(
            p.slice(0, i), // prefix
            first,         // middle
            p.slice(i)     // suffix
          );
        }
      }
    }
  };
}

            
























function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {



















            






















    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }














            






















    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;












            






















    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;









            






















    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;









            



















    case 19:
    case "end":
      return context.stop();
    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;
    case 5:
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);





            



















    case 19:
    case "end":
      return context.stop();
    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;
    case 5:
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);
    case 8:
      if ((t$1 = t$0.next()).done) {
        context.next = 19;
        break;
      }
      p = t$1.value;



















    case 19:
    case "end":
      return context.stop();
    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;
    case 5:
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);
    case 8:
      if ((t$1 = t$0.next()).done) {
        context.next = 19;
        break;
      }
      p = t$1.value;
      i = 0;
    case 11:
      if (!(i < list.length)) {
        context.next = 17;
        break;
      }













    case 19:
    case "end":
      return context.stop();
    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;
    case 5:
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);
    case 8:
      if ((t$1 = t$0.next()).done) {
        context.next = 19;
        break;
      }
      p = t$1.value;
      i = 0;
    case 11:
      if (!(i < list.length)) {
        context.next = 17;
        break;
      }
      context.next = 14;
      return Array.prototype.concat.call(
        p.slice(0, i), // prefix
        first,         // middle
        p.slice(i)     // suffix
      );







    case 19:
    case "end":
      return context.stop();
    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;
    case 5:
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);
    case 8:
      if ((t$1 = t$0.next()).done) {
        context.next = 19;
        break;
      }
      p = t$1.value;
      i = 0;
    case 11:
      if (!(i < list.length)) {
        context.next = 17;
        break;
      }
      context.next = 14;
      return Array.prototype.concat.call(
        p.slice(0, i), // prefix
        first,         // middle
        p.slice(i)     // suffix
      );
    case 14:
      ++i;
      context.next = 11;
      break;



    case 19:
    case "end":
      return context.stop();
    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;
    case 5:
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);
    case 8:
      if ((t$1 = t$0.next()).done) {
        context.next = 19;
        break;
      }
      p = t$1.value;
      i = 0;
    case 11:
      if (!(i < list.length)) {
        context.next = 17;
        break;
      }
      context.next = 14;
      return Array.prototype.concat.call(
        p.slice(0, i), // prefix
        first,         // middle
        p.slice(i)     // suffix
      );
    case 14:
      ++i;
      context.next = 11;
      break;
    case 17:
      context.next = 8;
      break;
    case 19:
    case "end":
      return context.stop();
    }
  };
}
function permutations(list) {
  var first, ps, p, t$0, t$1, i;

  return wrapGenerator(function(context) {
    while (1) switch (context.next) {
    case 0:
      if (!(list.length < 2)) {
        context.next = 5;
        break;
      }
      context.next = 3;
      return list;
    case 3:
      context.next = 19;
      break;
    case 5:
      first = list.slice(0, 1);
      ps = permutations(list.slice(1));
      t$0 = wrapGenerator.values(ps);
    case 8:
      if ((t$1 = t$0.next()).done) {
        context.next = 19;
        break;
      }
      p = t$1.value;
      i = 0;
    case 11:
      if (!(i < list.length)) {
        context.next = 17;
        break;
      }
      context.next = 14;
      return Array.prototype.concat.call(
        p.slice(0, i), // prefix
        first,         // middle
        p.slice(i)     // suffix
      );
    case 14:
      ++i;
      context.next = 11;
      break;
    case 17:
      context.next = 8;
      break;
    case 19:
    case "end":
      return context.stop();
    }
  }, permutations, this);
}

ForStatement
code generation

  case "ForStatement":



























  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();
























  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }




















  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);


















  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);

    if (stmt.test) {
      self.jumpIfNot(self.explodeExpression(path.get("test")), after);
    }














  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);

    if (stmt.test) {
      self.jumpIfNot(self.explodeExpression(path.get("test")), after);
    }

    self.leapManager.withEntry(
      new leap.LoopEntry(after, update, labelId),
      function() { self.explodeStatement(path.get("body")); }
    );









  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);

    if (stmt.test) {
      self.jumpIfNot(self.explodeExpression(path.get("test")), after);
    }

    self.leapManager.withEntry(
      new leap.LoopEntry(after, update, labelId),
      function() { self.explodeStatement(path.get("body")); }
    );

    self.mark(update);







  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);

    if (stmt.test) {
      self.jumpIfNot(self.explodeExpression(path.get("test")), after);
    }

    self.leapManager.withEntry(
      new leap.LoopEntry(after, update, labelId),
      function() { self.explodeStatement(path.get("body")); }
    );

    self.mark(update);

    if (stmt.update) {
      self.explode(path.get("update"), true);
    }



  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);

    if (stmt.test) {
      self.jumpIfNot(self.explodeExpression(path.get("test")), after);
    }

    self.leapManager.withEntry(
      new leap.LoopEntry(after, update, labelId),
      function() { self.explodeStatement(path.get("body")); }
    );

    self.mark(update);

    if (stmt.update) {
      self.explode(path.get("update"), true);
    }

    self.jump(head);

  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);

    if (stmt.test) {
      self.jumpIfNot(self.explodeExpression(path.get("test")), after);
    }

    self.leapManager.withEntry(
      new leap.LoopEntry(after, update, labelId),
      function() { self.explodeStatement(path.get("body")); }
    );

    self.mark(update);

    if (stmt.update) {
      self.explode(path.get("update"), true);
    }

    self.jump(head);
    self.mark(after);
  case "ForStatement":
    var head = loc();
    var update = loc();
    var after = loc();

    if (stmt.init) {
      self.explode(path.get("init"), true);
    }

    self.mark(head);

    if (stmt.test) {
      self.jumpIfNot(self.explodeExpression(path.get("test")), after);
    }

    self.leapManager.withEntry(
      new leap.LoopEntry(after, update, labelId),
      function() { self.explodeStatement(path.get("body")); }
    );

    self.mark(update);

    if (stmt.update) {
      self.explode(path.get("update"), true);
    }

    self.jump(head);
    self.mark(after);
    break;

loc() and this.mark(loc)






















loc() and this.mark(loc)


// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
  return { type: "Literal", value: -1 };
}















loc() and this.mark(loc)


// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
  return require("recast").types.builders.literal(-1);
}















loc() and this.mark(loc)


// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
  return b.literal(-1);
}















loc() and this.mark(loc)


// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
  return b.literal(-1);
}

// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Emitter.prototype.mark = function(loc) {
  assert.strictEqual(loc.type, "Literal");
  var index = this.listing.length;
  if (loc.value === -1) {
    loc.value = index;
  } else {
    // Locations can be marked redundantly, but their values cannot change
    // once set the first time.
    assert.strictEqual(loc.value, index);
  }
  this.marked[index] = true;
  return loc;
};

loc() and this.mark(loc)


// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
  return b.literal(-1);
}

// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Emitter.prototype.mark = function(loc) {
  require("recast").types.namedTypes.Literal.assert(loc);
  var index = this.listing.length;
  if (loc.value === -1) {
    loc.value = index;
  } else {
    // Locations can be marked redundantly, but their values cannot change
    // once set the first time.
    assert.strictEqual(loc.value, index);
  }
  this.marked[index] = true;
  return loc;
};

loc() and this.mark(loc)


// Offsets into this.listing that could be used as targets for branches or
// jumps are represented as numeric Literal nodes.
function loc() {
  return b.literal(-1);
}

// Sets the exact value of the given location to the offset of the next
// Statement emitted.
Emitter.prototype.mark = function(loc) {
  n.Literal.assert(loc);
  var index = this.listing.length;
  if (loc.value === -1) {
    loc.value = index;
  } else {
    // Locations can be marked redundantly, but their values cannot change
    // once set the first time.
    assert.strictEqual(loc.value, index);
  }
  this.marked[index] = true;
  return loc;
};

this.jumpIfNot(expr, loc)























this.jumpIfNot(expr, loc)


// Conditional jump, with the condition negated.
Emitter.prototype.jumpIfNot = function(test, toLoc) {



















};

this.jumpIfNot(expr, loc)


// Conditional jump, with the condition negated.
Emitter.prototype.jumpIfNot = function(test, toLoc) {
  n.Expression.assert(test);
  n.Literal.assert(toLoc);

















};

this.jumpIfNot(expr, loc)


// Conditional jump, with the condition negated.
Emitter.prototype.jumpIfNot = function(test, toLoc) {
  n.Expression.assert(test);
  n.Literal.assert(toLoc);

  var negatedTest;
  if (n.UnaryExpression.check(test) &&
      test.operator === "!") {


  }










};

this.jumpIfNot(expr, loc)


// Conditional jump, with the condition negated.
Emitter.prototype.jumpIfNot = function(test, toLoc) {
  n.Expression.assert(test);
  n.Literal.assert(toLoc);

  var negatedTest;
  if (n.UnaryExpression.check(test) &&
      test.operator === "!") {
    // Avoid double negation.
    negatedTest = test.argument;
  }










};

this.jumpIfNot(expr, loc)


// Conditional jump, with the condition negated.
Emitter.prototype.jumpIfNot = function(test, toLoc) {
  n.Expression.assert(test);
  n.Literal.assert(toLoc);

  var negatedTest;
  if (n.UnaryExpression.check(test) &&
      test.operator === "!") {
    // Avoid double negation.
    negatedTest = test.argument;
  } else {
    negatedTest = b.unaryExpression("!", test);
  }








};

this.jumpIfNot(expr, loc)


// Conditional jump, with the condition negated.
Emitter.prototype.jumpIfNot = function(test, toLoc) {
  n.Expression.assert(test);
  n.Literal.assert(toLoc);

  var negatedTest;
  if (n.UnaryExpression.check(test) &&
      test.operator === "!") {
    // Avoid double negation.
    negatedTest = test.argument;
  } else {
    negatedTest = b.unaryExpression("!", test);
  }

  this.emit(b.ifStatement(
    negatedTest,
    b.blockStatement([
      this.assign(this.contextProperty("next"), toLoc),
      b.breakStatement()
    ])
  ));
};

this.explodeStatement(path)
























this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {





















};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);




















};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);

  var stmt = path.value;
  n.Statement.assert(stmt);

















};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);

  var stmt = path.value;
  n.Statement.assert(stmt);

  if (n.BlockStatement.check(stmt)) {
    return path.get("body").each(self.explodeStatement, self);
  }













};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);

  var stmt = path.value;
  n.Statement.assert(stmt);

  if (n.BlockStatement.check(stmt)) {
    return path.get("body").each(self.explodeStatement, self);
  }

  if (!require("./meta").containsLeap(stmt)) {
    self.emit(stmt);
    return;
  }








};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);

  var stmt = path.value;
  n.Statement.assert(stmt);

  if (n.BlockStatement.check(stmt)) {
    return path.get("body").each(self.explodeStatement, self);
  }

  if (!require("./meta").containsLeap(stmt)) {
    self.emit(stmt);
    return;
  }

  switch (stmt.type) {





  }
};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);

  var stmt = path.value;
  n.Statement.assert(stmt);

  if (n.BlockStatement.check(stmt)) {
    return path.get("body").each(self.explodeStatement, self);
  }

  if (!require("./meta").containsLeap(stmt)) {
    self.emit(stmt);
    return;
  }

  switch (stmt.type) {
  case "ForStatement":




  }
};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);

  var stmt = path.value;
  n.Statement.assert(stmt);

  if (n.BlockStatement.check(stmt)) {
    return path.get("body").each(self.explodeStatement, self);
  }

  if (!require("./meta").containsLeap(stmt)) {
    self.emit(stmt);
    return;
  }

  switch (stmt.type) {
  case "ForStatement": ... break; ...




  }
};

this.explodeStatement(path)


Emitter.prototype.explodeStatement = function(path) {
  assert.ok(path instanceof require("recast").types.NodePath);

  var stmt = path.value;
  n.Statement.assert(stmt);

  if (n.BlockStatement.check(stmt)) {
    return path.get("body").each(self.explodeStatement, self);
  }

  if (!require("./meta").containsLeap(stmt)) {
    self.emit(stmt);
    return;
  }

  switch (stmt.type) {
  case "ForStatement": ... break; ...
  default:
    throw new Error(
      "unknown Statement of type " +
        JSON.stringify(stmt.type));
  }
};

Exception handling

function *attempt(block) {
  console.log("before");
  try {
    var result = block();
  } catch (thrown) {
    yield thrown;
  }
  console.log("after");
  return result;















}

Exception handling

function attempt(block) {
  var result;

  return wrapGenerator(function(context) {
    while (1) switch (context.prev = context.next) {

















    }
  }, attempt, this);
}

Exception handling

function attempt(block) {
  var result;

  return wrapGenerator(function(context) {
    while (1) switch (context.prev = context.next) {
    case 0:
      console.log("before");
      context.prev = 1;
      result = block();
      context.next = 9;
      break;











    }
  }, attempt, this);
}

Exception handling

function attempt(block) {
  var result;

  return wrapGenerator(function(context) {
    while (1) switch (context.prev = context.next) {
    case 0:
      console.log("before");
      context.prev = 1;
      result = block();
      context.next = 9;
      break;
    case 5:
      context.prev = 5;
      context.t0 = context.catch(1);
      context.next = 9;
      return context.t0;






    }
  }, attempt, this);
}

Exception handling

function attempt(block) {
  var result;

  return wrapGenerator(function(context) {
    while (1) switch (context.prev = context.next) {
    case 0:
      console.log("before");
      context.prev = 1;
      result = block();
      context.next = 9;
      break;
    case 5:
      context.prev = 5;
      context.t0 = context.catch(1);
      context.next = 9;
      return context.t0;
    case 9:
      console.log("after");
      return context.abrupt("return", result);



    }
  }, attempt, this);
}

Exception handling

function attempt(block) {
  var result;

  return wrapGenerator(function(context) {
    while (1) switch (context.prev = context.next) {
    case 0:
      console.log("before");
      context.prev = 1;
      result = block();
      context.next = 9;
      break;
    case 5:
      context.prev = 5;
      context.t0 = context.catch(1);
      context.next = 9;
      return context.t0;
    case 9:
      console.log("after");
      return context.abrupt("return", result);
    case 11:
    case "end":
      return context.stop();
    }
  }, attempt, this);
}

Exception handling

function attempt(block) {
  var result;

  return wrapGenerator(function(context) {
    while (1) switch (context.prev = context.next) {
    case 0:
      console.log("before");
      context.prev = 1;
      result = block();
      context.next = 9;
      break;
    case 5:
      context.prev = 5;
      context.t0 = context.catch(1);
      context.next = 9;
      return context.t0;
    case 9:
      console.log("after");
      return context.abrupt("return", result);
    case 11:
    case "end":
      return context.stop();
    }
  }, attempt, this, [[1, 5]]);
}

Exception dispatch (runtime/dev.js)























Exception dispatch (runtime/dev.js)

while (true) {





















}

Exception dispatch (runtime/dev.js)

while (true) {
  if (state === GenStateSuspendedYield) {
    context.sent = arg;
  } else {
    delete context.sent;
  }
















}

Exception dispatch (runtime/dev.js)

while (true) {
  if (state === GenStateSuspendedYield) {
    context.sent = arg;
  } else {
    delete context.sent;
  }

  state = GenStateExecuting;














}

Exception dispatch (runtime/dev.js)

while (true) {
  if (state === GenStateSuspendedYield) {
    context.sent = arg;
  } else {
    delete context.sent;
  }

  state = GenStateExecuting;

  try {








  } catch (thrown) {


  }
}

Exception dispatch (runtime/dev.js)

while (true) {
  if (state === GenStateSuspendedYield) {
    context.sent = arg;
  } else {
    delete context.sent;
  }

  state = GenStateExecuting;

  try {
    var value = innerFn.call(self, context);







  } catch (thrown) {


  }
}

Exception dispatch (runtime/dev.js)

while (true) {
  if (state === GenStateSuspendedYield) {
    context.sent = arg;
  } else {
    delete context.sent;
  }

  state = GenStateExecuting;

  try {
    var value = innerFn.call(self, context);

    // If an exception is thrown from innerFn, we leave state ===
    // GenStateExecuting and loop back for another invocation.
    state = context.done ? GenStateCompleted : GenStateSuspendedYield;



  } catch (thrown) {


  }
}

Exception dispatch (runtime/dev.js)

while (true) {
  if (state === GenStateSuspendedYield) {
    context.sent = arg;
  } else {
    delete context.sent;
  }

  state = GenStateExecuting;

  try {
    var value = innerFn.call(self, context);

    // If an exception is thrown from innerFn, we leave state ===
    // GenStateExecuting and loop back for another invocation.
    state = context.done ? GenStateCompleted : GenStateSuspendedYield;

    return { value: value, done: context.done };

  } catch (thrown) {


  }
}

Exception dispatch (runtime/dev.js)

while (true) {
  if (state === GenStateSuspendedYield) {
    context.sent = arg;
  } else {
    delete context.sent;
  }

  state = GenStateExecuting;

  try {
    var value = innerFn.call(self, context);

    // If an exception is thrown from innerFn, we leave state ===
    // GenStateExecuting and loop back for another invocation.
    state = context.done ? GenStateCompleted : GenStateSuspendedYield;

    return { value: value, done: context.done };

  } catch (thrown) {
    state = GenStateCompleted;
    context.dispatchException(thrown);
  }
}

Good news!


That's pretty much as hard as transpilation gets.

Easy now: async functions


async function chainAnimations(elem, animations) {
  var ret = null;
  for (var i = 0; i < animations.length; ++i)
    ret = await animations[i](elem);
  return ret;
}

chainAnimations($("#card"), [
  flip(100), slide(200), ...
]).then(function() {
  console.log("all done!");
});
function chainAnimations(elem, animations) {
  var ret, i;
  return wrapGenerator.async(function(context) {
    while (1) switch (context.next) {
    case 0:
      ret = null;
      i = 0;
    case 2:
      if (!(i < animations.length)) {
        context.next = 9;
        break;
      }
      context.next = 5;
      return animations[i](elem);
    case 5:
      ret = context.sent;
    case 6:
      ++i;
      context.next = 2;
      break;
    case 9:
      return context.abrupt("return", ret);
    case 10:
    case "end":
      return context.stop();
    }
  }, null, this);
}
wrapGenerator.async = function(innerFn, self, tryList) {
  return new Promise(function(resolve, reject) {
    var generator = wrapGenerator(innerFn, self, tryList);
    var callNext = step.bind(generator.next);
    var callThrow = step.bind(generator.throw);

    function step(arg) {
      try {
        var info = this(arg);
        var value = info.value;
      } catch (error) {
        return reject(error);
      }

      if (info.done) {
        resolve(value);
      } else {
        Promise.resolve(value).then(callNext, callThrow);
      }
    }

    callNext();
  });
};

Already implemented in
this pull request.

But wait!


When can we expect native support for async functions and the await keyword?

I have a feeling we're not talking about ECMAScript 6 anymore...

Always Be Transpiling


Incremental transpilation is the key to avoiding the Python 3 trap.


It's how we bring about the future without choking on it.


It's how we know, sooner rather than later, how great the future will be.


It is, quite literally, linguistic time travel.

But it's still really hard!


Not everything can be transpiled.


AST transforms don't always play well together.


Niceties like source maps become an absolute necessity when debugging generated code.


The language specification can change out from under you.

The JavaScript Infrastructure team at Facebook will be working on this problem for as long as it takes to make the process seamless.

It's all going to be open-source.


We're only just getting started.


You are more than welcome to help.

Thanks!

External links:


github.com/{
  benjamn/jsconf-2014,
  facebook/jstransform,
  benjamn/recast,
  benjamn/ast-types,
  facebook/regenerator,
  square/esnext
}

code.facebook.com/projects

What about Traceur?