Merge pull request #1518 from babel/es7.functionBind

Add experimental support for ES7 function bind.
This commit is contained in:
Sebastian McKenzie
2015-05-14 16:22:21 +01:00
20 changed files with 174 additions and 5 deletions

View File

@@ -214,7 +214,12 @@ pp.parseExprSubscripts = function(refShorthandDefaultPos) {
}
pp.parseSubscripts = function(base, start, noCalls) {
if (this.eat(tt.dot)) {
if (!noCalls && this.eat(tt.doubleColon)) {
let node = this.startNodeAt(start)
node.object = base
node.callee = this.parseNoCallExpr()
return this.parseSubscripts(this.finishNode(node, "BindExpression"), start, noCalls)
} else if (this.eat(tt.dot)) {
let node = this.startNodeAt(start)
node.object = base
node.property = this.parseIdent(true)
@@ -240,6 +245,13 @@ pp.parseSubscripts = function(base, start, noCalls) {
} return base
}
// Parse a no-call expression (like argument of `new` or `::` operators).
pp.parseNoCallExpr = function() {
let start = this.markPosition()
return this.parseSubscripts(this.parseExprAtom(), start, true)
}
// Parse an atomic expression — either a single token that is an
// expression, an expression started by a keyword like `function` or
// `new`, or an expression wrapped in punctuation like `()`, `[]`,
@@ -363,6 +375,15 @@ pp.parseExprAtom = function(refShorthandDefaultPos) {
case tt.backQuote:
return this.parseTemplate()
case tt.doubleColon:
node = this.startNode()
this.next()
node.object = null
let callee = node.callee = this.parseNoCallExpr()
if (callee.type !== "MemberExpression")
this.raise(callee.start, "Binding should be performed on object property.")
return this.finishNode(node, "BindExpression")
default:
this.unexpected()
}
@@ -472,8 +493,7 @@ pp.parseNew = function() {
this.raise(node.property.start, "The only valid meta property for new is new.target")
return this.finishNode(node, "MetaProperty")
}
let start = this.markPosition()
node.callee = this.parseSubscripts(this.parseExprAtom(), start, true)
node.callee = this.parseNoCallExpr()
if (this.eat(tt.parenL)) node.arguments = this.parseExprList(
tt.parenR,
this.options.features["es7.trailingFunctionCommas"]

View File

@@ -320,7 +320,13 @@ pp.getTokenFromCode = function(code) {
case 93: ++this.pos; return this.finishToken(tt.bracketR)
case 123: ++this.pos; return this.finishToken(tt.braceL)
case 125: ++this.pos; return this.finishToken(tt.braceR)
case 58: ++this.pos; return this.finishToken(tt.colon)
case 58:
if (this.options.features["es7.functionBind"] && this.input.charCodeAt(this.pos + 1) === 58)
return this.finishOp(tt.doubleColon, 2)
++this.pos
return this.finishToken(tt.colon)
case 63: ++this.pos; return this.finishToken(tt.question)
case 64: ++this.pos; return this.finishToken(tt.at)

View File

@@ -54,6 +54,7 @@ export const types = {
comma: new TokenType(",", beforeExpr),
semi: new TokenType(";", beforeExpr),
colon: new TokenType(":", beforeExpr),
doubleColon: new TokenType("::", beforeExpr),
dot: new TokenType("."),
question: new TokenType("?", beforeExpr),
arrow: new TokenType("=>", beforeExpr),

View File

@@ -133,6 +133,12 @@ export function AssignmentExpression(node, print) {
print(node.right);
}
export function BindExpression(node, print) {
print(node.object);
this.push("::");
print(node.callee);
}
export {
AssignmentExpression as BinaryExpression,
AssignmentExpression as LogicalExpression,

View File

@@ -72,4 +72,10 @@ def("ExportAllDeclaration")
.field("exported", def("Identifier"))
.field("source", def("Literal"));
def("BindExpression")
.bases("Expression")
.build("object", "callee")
.field("object", or(def("Expression"), null))
.field("callee", def("Expression"));
types.finalize();

View File

@@ -0,0 +1,41 @@
// https://github.com/zenparsing/es-function-bind
import * as t from "../../../types";
export var metadata = {
optional: true,
stage: 0
};
function getTempId(scope) {
var id = scope.path.getData("functionBind");
if (id) return id;
id = scope.generateTemp("context");
return scope.path.setData("functionBind", id);
}
function inferBindContext(bind, scope) {
var tempId = getTempId(scope);
if (bind.object) {
bind.callee = t.sequenceExpression([
t.assignmentExpression("=", tempId, bind.object),
bind.callee
]);
} else {
bind.callee.object = t.assignmentExpression("=", tempId, bind.callee.object);
}
return tempId;
}
export function CallExpression(node, parent, scope, file) {
var bind = node.callee;
if (!t.isBindExpression(bind)) return;
var context = inferBindContext(bind, scope);
node.callee = t.memberExpression(bind.callee, t.identifier("call"));
node.arguments.unshift(context);
}
export function BindExpression(node, parent, scope, file) {
var context = inferBindContext(node, scope);
return t.callExpression(t.memberExpression(node.callee, t.identifier("bind")), [context]);
}

View File

@@ -51,6 +51,7 @@ export default {
"spec.protoToAssign": require("./spec/proto-to-assign"),
"es7.doExpressions": require("./es7/do-expressions"),
"es6.spec.symbols": require("./es6/spec.symbols"),
"es7.functionBind": require("./es7/function-bind"),
"spec.undefinedToVoid": require("./spec/undefined-to-void"),
jscript: require("./other/jscript"),
flow: require("./other/flow"),

View File

@@ -20,6 +20,11 @@
"right": null
},
"BindExpression": {
"object": null,
"callee": null
},
"BlockStatement": {
"body": null
},

View File

@@ -6,6 +6,7 @@
"AssignmentPattern": ["left", "right"],
"AwaitExpression": ["argument"],
"BinaryExpression": ["left", "right"],
"BindExpression": ["object", "callee"],
"BlockStatement": ["body"],
"BreakStatement": ["label"],
"CallExpression": ["callee", "arguments"],

View File

@@ -0,0 +1,5 @@
::foo.bar.foo;
::foo.bar["foo"];
ctx::foo.bar.foo;
ctx::foo.bar["foo"];

View File

@@ -0,0 +1,5 @@
::foo.bar.foo;
::foo.bar["foo"];
ctx::foo.bar.foo;
ctx::foo.bar["foo"];

View File

@@ -0,0 +1,3 @@
var f = ctx::ns.obj.func;
var g = ::ns.obj.func;
var h = new X::y;

View File

@@ -0,0 +1,7 @@
"use strict";
var _context;
var f = (_context = ctx, ns.obj.func).bind(_context);
var g = (_context = ns.obj).func.bind(_context);
var h = (_context = new X(), y).bind(_context);

View File

@@ -0,0 +1,4 @@
ctx::ns.obj.func();
::ns.obj.func();
ns.obj2::ns.obj1.func();

View File

@@ -0,0 +1,8 @@
"use strict";
var _context;
(_context = ctx, ns.obj.func).call(_context);
(_context = ns.obj).func.call(_context);
(_context = ns.obj2, ns.obj1.func).call(_context);

View File

@@ -0,0 +1,6 @@
import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));

View File

@@ -0,0 +1,27 @@
var operations = [];
var lib = {};
for (let key of ['f', 'g', 'h']) {
let func = () => operations.push(`lib.${key}()`);
Object.defineProperty(lib, key, {
get() {
operations.push(`get lib.${key}`);
return func;
}
});
}
({prop:'value'})
::lib.f()
::lib.g()
::lib.h();
assert.deepEqual(operations, [
'get lib.f',
'lib.f()',
'get lib.g',
'lib.g()',
'get lib.h',
'lib.h()'
]);

View File

@@ -0,0 +1,13 @@
"use strict";
var _context;
var _iterlib = require("iterlib");
(_context = (_context = (_context = getPlayers(), _iterlib.map).call(_context, function (x) {
return x.character();
}), _iterlib.takeWhile).call(_context, function (x) {
return x.strength > 100;
}), _iterlib.forEach).call(_context, function (x) {
return console.log(x);
});

View File

@@ -0,0 +1,3 @@
{
"optional": "es7.functionBind"
}

View File

@@ -34,7 +34,8 @@ _.each(helper.get("generation"), function (testSuite) {
features: {
"es7.comprehensions": true,
"es7.asyncFunctions": true,
"es7.exportExtensions": true
"es7.exportExtensions": true,
"es7.functionBind": true
}
});
var actualCode = generate(actualAst, task.options, actual.code).code;