Merge branch 'master' of github.com:6to5/6to5
This commit is contained in:
commit
8cd2326ff9
213
lib/6to5/transformation/transformers/es6/tail-call.js
Normal file
213
lib/6to5/transformation/transformers/es6/tail-call.js
Normal file
@ -0,0 +1,213 @@
|
||||
"use strict";
|
||||
|
||||
var t = require("../../../types");
|
||||
|
||||
function returnBlock(expr) {
|
||||
return t.blockStatement([t.returnStatement(expr)]);
|
||||
}
|
||||
|
||||
function transformExpression(node, scope, state) {
|
||||
if (!node) return;
|
||||
|
||||
return (function subTransform(node) {
|
||||
switch (node.type) {
|
||||
case "ConditionalExpression":
|
||||
var callConsequent = subTransform(node.consequent);
|
||||
var callAlternate = subTransform(node.alternate);
|
||||
if (!callConsequent && !callAlternate) {
|
||||
return;
|
||||
}
|
||||
// if ternary operator had tail recursion in value, convert to optimized if-statement
|
||||
node.type = "IfStatement";
|
||||
node.consequent = callConsequent ? t.toBlock(callConsequent) : returnBlock(node.consequent);
|
||||
if (callAlternate) {
|
||||
node.alternate = t.isIfStatement(callAlternate) ? callAlternate : t.toBlock(callAlternate);
|
||||
} else {
|
||||
node.alternate = returnBlock(node.alternate);
|
||||
}
|
||||
return node;
|
||||
|
||||
case "LogicalExpression":
|
||||
// only call in right-value of can be optimized
|
||||
var callRight = subTransform(node.right);
|
||||
if (!callRight) {
|
||||
return;
|
||||
}
|
||||
// cache left value as it might have side-effects
|
||||
var leftId = state.getLeftId();
|
||||
var testExpr = t.assignmentExpression(
|
||||
"=",
|
||||
leftId,
|
||||
node.left
|
||||
);
|
||||
if (node.operator === "&&") {
|
||||
testExpr = t.unaryExpression("!", testExpr);
|
||||
}
|
||||
return [t.ifStatement(testExpr, returnBlock(leftId))].concat(callRight);
|
||||
|
||||
case "SequenceExpression":
|
||||
var seq = node.expressions;
|
||||
// only last element can be optimized
|
||||
var lastCall = subTransform(seq[seq.length - 1]);
|
||||
if (!lastCall) {
|
||||
return;
|
||||
}
|
||||
// remove converted expression from sequence
|
||||
// and convert to regular expression if needed
|
||||
if (--seq.length === 1) {
|
||||
node = seq[0];
|
||||
}
|
||||
return [t.expressionStatement(node)].concat(lastCall);
|
||||
|
||||
case "CallExpression":
|
||||
var callee = node.callee, prop, thisBinding, args;
|
||||
|
||||
if (t.isMemberExpression(callee, { computed: false }) &&
|
||||
t.isIdentifier(prop = callee.property)) {
|
||||
switch (prop.name) {
|
||||
case "call":
|
||||
args = t.arrayExpression(node.arguments.slice(1));
|
||||
break;
|
||||
case "apply":
|
||||
args = node.arguments[1] || t.identifier("undefined");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
thisBinding = node.arguments[0];
|
||||
callee = callee.object;
|
||||
}
|
||||
|
||||
// only tail recursion can be optimized as for now
|
||||
if (!t.isIdentifier(callee) || !scope.bindingEquals(callee.name, state.ownerId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.hasTailRecursion = true;
|
||||
|
||||
return [
|
||||
t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
state.getArgumentsId(),
|
||||
args || t.arrayExpression(node.arguments)
|
||||
)),
|
||||
t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
state.getThisId(),
|
||||
thisBinding || t.identifier("undefined")
|
||||
)),
|
||||
t.returnStatement(t.assignmentExpression(
|
||||
"=",
|
||||
state.getShouldContinueId(),
|
||||
t.literal(true)
|
||||
))
|
||||
];
|
||||
}
|
||||
})(node);
|
||||
}
|
||||
|
||||
var functionChildrenVisitor = {
|
||||
enter: function (node, parent, scope, state) {
|
||||
if (t.isReturnStatement(node)) {
|
||||
// prevent entrance by current visitor
|
||||
this.skip();
|
||||
// transform return argument into statement if
|
||||
// it contains tail recursion
|
||||
return transformExpression(node.argument, scope, state);
|
||||
} else if (t.isFunction(node)) {
|
||||
return this.skip();
|
||||
} else if (t.isTryStatement(parent)) {
|
||||
if (node === parent.block) {
|
||||
return this.skip();
|
||||
} else if (node === parent.finalizer) {
|
||||
return;
|
||||
} else {
|
||||
if (parent.finalizer) {
|
||||
this.skip();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var functionVisitor = {
|
||||
enter: function (node, parent, scope, state) {
|
||||
// traverse all child nodes of this function and find `arguments` and `this`
|
||||
scope.traverse(node, functionChildrenVisitor, state);
|
||||
|
||||
return this.skip();
|
||||
}
|
||||
};
|
||||
|
||||
exports.FunctionDeclaration =
|
||||
exports.FunctionExpression = function (node, parent, scope) {
|
||||
// only tail recursion can be optimized as for now,
|
||||
// so we can skip anonymous functions entirely
|
||||
var ownerId = node.id;
|
||||
if (!ownerId) return;
|
||||
|
||||
var argumentsId, thisId, shouldContinueId, leftId;
|
||||
|
||||
var state = {
|
||||
hasTailRecursion: false,
|
||||
ownerId: ownerId,
|
||||
getArgumentsId: function () {
|
||||
return argumentsId = argumentsId || scope.generateUidIdentifier("arguments");
|
||||
},
|
||||
getThisId: function () {
|
||||
return thisId = thisId || scope.generateUidIdentifier("this");
|
||||
},
|
||||
getShouldContinueId: function () {
|
||||
return shouldContinueId = shouldContinueId || scope.generateUidIdentifier("shouldContinue");
|
||||
},
|
||||
getLeftId: function () {
|
||||
return leftId = leftId || scope.generateUidIdentifier("left");
|
||||
}
|
||||
};
|
||||
|
||||
// traverse the function and look for tail recursion
|
||||
scope.traverse(node, functionVisitor, state);
|
||||
|
||||
if (!state.hasTailRecursion) return;
|
||||
|
||||
var block = t.ensureBlock(node);
|
||||
|
||||
if (leftId) {
|
||||
block.body.unshift(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(leftId)
|
||||
]));
|
||||
}
|
||||
|
||||
var resultId = scope.generateUidIdentifier("result");
|
||||
state.getShouldContinueId();
|
||||
|
||||
node.body = t.blockStatement([
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(argumentsId, t.identifier("arguments")),
|
||||
t.variableDeclarator(thisId, t.thisExpression()),
|
||||
t.variableDeclarator(shouldContinueId),
|
||||
t.variableDeclarator(resultId)
|
||||
]),
|
||||
t.doWhileStatement(t.blockStatement([
|
||||
t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
shouldContinueId,
|
||||
t.literal(false)
|
||||
)),
|
||||
t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
resultId,
|
||||
t.callExpression(
|
||||
t.memberExpression(
|
||||
t.functionExpression(null, node.params, block),
|
||||
t.identifier("apply"),
|
||||
false
|
||||
),
|
||||
[thisId, argumentsId]
|
||||
)
|
||||
))
|
||||
]), shouldContinueId),
|
||||
t.returnStatement(resultId)
|
||||
]);
|
||||
};
|
||||
@ -58,6 +58,8 @@ module.exports = {
|
||||
|
||||
"es6.destructuring": require("./es6/destructuring"),
|
||||
|
||||
"es6.tailCall": require("./es6/tail-call"),
|
||||
|
||||
regenerator: require("./other/regenerator"),
|
||||
|
||||
// needs to be after `regenerator` due to needing `regeneratorRuntime` references
|
||||
|
||||
@ -395,7 +395,7 @@ t.toIdentifier = function (name) {
|
||||
|
||||
t.ensureBlock = function (node, key) {
|
||||
key = key || "body";
|
||||
node[key] = t.toBlock(node[key], node);
|
||||
return node[key] = t.toBlock(node[key], node);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
7
test/fixtures/transformation/es6-tail-call/call-apply/actual.js
vendored
Normal file
7
test/fixtures/transformation/es6-tail-call/call-apply/actual.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
(function f(n) {
|
||||
if (n <= 0) {
|
||||
console.log(this, arguments);
|
||||
return "foo";
|
||||
}
|
||||
return Math.random() > 0.5 ? f.call(this, n - 1) : f.apply(this, [n - 1]);
|
||||
})(1e6) === "foo";
|
||||
27
test/fixtures/transformation/es6-tail-call/call-apply/expected.js
vendored
Normal file
27
test/fixtures/transformation/es6-tail-call/call-apply/expected.js
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
"use strict";
|
||||
|
||||
(function f(n) {
|
||||
var _arguments = arguments,
|
||||
_this = this,
|
||||
_shouldContinue,
|
||||
_result;
|
||||
do {
|
||||
_shouldContinue = false;
|
||||
_result = (function (n) {
|
||||
if (n <= 0) {
|
||||
console.log(this, arguments);
|
||||
return "foo";
|
||||
}
|
||||
if (Math.random() > 0.5) {
|
||||
_arguments = [n - 1];
|
||||
_this = this;
|
||||
return _shouldContinue = true;
|
||||
} else {
|
||||
_arguments = [n - 1];
|
||||
_this = this;
|
||||
return _shouldContinue = true;
|
||||
}
|
||||
}).apply(_this, _arguments);
|
||||
} while (_shouldContinue);
|
||||
return _result;
|
||||
})(1000000) === "foo";
|
||||
3
test/fixtures/transformation/es6-tail-call/expressions/actual.js
vendored
Normal file
3
test/fixtures/transformation/es6-tail-call/expressions/actual.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
(function f(n) {
|
||||
return n <= 0 ? "foo" : (doSmth(), getTrueValue() && (getFalseValue() || f(n - 1)));
|
||||
})(1e6, true) === "foo";
|
||||
30
test/fixtures/transformation/es6-tail-call/expressions/expected.js
vendored
Normal file
30
test/fixtures/transformation/es6-tail-call/expressions/expected.js
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
|
||||
(function f(n) {
|
||||
var _arguments = arguments,
|
||||
_this = this,
|
||||
_shouldContinue,
|
||||
_result;
|
||||
do {
|
||||
_shouldContinue = false;
|
||||
_result = (function (n) {
|
||||
var _left;
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
} else {
|
||||
doSmth();
|
||||
|
||||
if (!(_left = getTrueValue())) {
|
||||
return _left;
|
||||
}
|
||||
if (_left = getFalseValue()) {
|
||||
return _left;
|
||||
}
|
||||
_arguments = [n - 1];
|
||||
_this = undefined;
|
||||
return _shouldContinue = true;
|
||||
}
|
||||
}).apply(_this, _arguments);
|
||||
} while (_shouldContinue);
|
||||
return _result;
|
||||
})(1000000, true) === "foo";
|
||||
8
test/fixtures/transformation/es6-tail-call/recursion/actual.js
vendored
Normal file
8
test/fixtures/transformation/es6-tail-call/recursion/actual.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
(function f(n = getDefaultValue(), /* should be undefined after first pass */ m) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
// Should be clean (undefined) on each pass
|
||||
var local;
|
||||
return f(n - 1);
|
||||
})(1e6, true) === "foo";
|
||||
9
test/fixtures/transformation/es6-tail-call/recursion/exec.js
vendored
Normal file
9
test/fixtures/transformation/es6-tail-call/recursion/exec.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
var timeLimit = Date.now() + 5000;
|
||||
|
||||
assert.equal((function f(n) {
|
||||
assert.operator(Date.now(), '<', timeLimit, "Timeout");
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
return f(n - 1);
|
||||
})(1e6), "foo");
|
||||
23
test/fixtures/transformation/es6-tail-call/recursion/expected.js
vendored
Normal file
23
test/fixtures/transformation/es6-tail-call/recursion/expected.js
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
|
||||
(function f(_x, /* should be undefined after first pass */m) {
|
||||
var _arguments = arguments,
|
||||
_this = this,
|
||||
_shouldContinue,
|
||||
_result;
|
||||
do {
|
||||
_shouldContinue = false;
|
||||
_result = (function (_x, m) {
|
||||
var n = arguments[0] === undefined ? getDefaultValue() : arguments[0];
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
// Should be clean (undefined) on each pass
|
||||
var local;
|
||||
_arguments = [n - 1];
|
||||
_this = undefined;
|
||||
return _shouldContinue = true;
|
||||
}).apply(_this, _arguments);
|
||||
} while (_shouldContinue);
|
||||
return _result;
|
||||
})(1000000, true) === "foo";
|
||||
39
test/fixtures/transformation/es6-tail-call/try-catch/actual.js
vendored
Normal file
39
test/fixtures/transformation/es6-tail-call/try-catch/actual.js
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
(function f(n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {
|
||||
return f(n - 1);
|
||||
} catch (e) {}
|
||||
})(1e6) === "foo";
|
||||
|
||||
(function f(n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
return f(n - 1);
|
||||
}
|
||||
})(1e6) === "foo";
|
||||
|
||||
(function f(n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
return f(n - 1);
|
||||
} finally {}
|
||||
})(1e6) === "foo";
|
||||
|
||||
(function f(n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {} finally {
|
||||
return f(n - 1);
|
||||
}
|
||||
})(1e6) === "foo";
|
||||
65
test/fixtures/transformation/es6-tail-call/try-catch/expected.js
vendored
Normal file
65
test/fixtures/transformation/es6-tail-call/try-catch/expected.js
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
"use strict";
|
||||
|
||||
(function f(n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {
|
||||
return f(n - 1);
|
||||
} catch (e) {}
|
||||
})(1000000) === "foo";
|
||||
|
||||
(function f(n) {
|
||||
var _arguments = arguments,
|
||||
_this = this,
|
||||
_shouldContinue,
|
||||
_result;
|
||||
do {
|
||||
_shouldContinue = false;
|
||||
_result = (function (n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
_arguments = [n - 1];
|
||||
_this = undefined;
|
||||
return _shouldContinue = true;
|
||||
}
|
||||
}).apply(_this, _arguments);
|
||||
} while (_shouldContinue);
|
||||
return _result;
|
||||
})(1000000) === "foo";
|
||||
|
||||
(function f(n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
return f(n - 1);
|
||||
} finally {}
|
||||
})(1000000) === "foo";
|
||||
|
||||
(function f(n) {
|
||||
var _arguments = arguments,
|
||||
_this = this,
|
||||
_shouldContinue,
|
||||
_result;
|
||||
do {
|
||||
_shouldContinue = false;
|
||||
_result = (function (n) {
|
||||
if (n <= 0) {
|
||||
return "foo";
|
||||
}
|
||||
try {} finally {
|
||||
_arguments = [n - 1];
|
||||
_this = undefined;
|
||||
return _shouldContinue = true;
|
||||
}
|
||||
}).apply(_this, _arguments);
|
||||
} while (_shouldContinue);
|
||||
return _result;
|
||||
})(1000000) === "foo";
|
||||
Loading…
x
Reference in New Issue
Block a user