Works across functions and generates simpler and faster code than #701. Works even across files when used in conjunction with `runtime` option. Closes #256.
154 lines
4.2 KiB
JavaScript
154 lines
4.2 KiB
JavaScript
"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;
|
|
}
|
|
|
|
var test = state.wrapSideEffect(node.left);
|
|
if (node.operator === "&&") {
|
|
test.expr = t.unaryExpression("!", test.expr);
|
|
}
|
|
return [t.ifStatement(test.expr, returnBlock(test.ref))].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, thisBinding;
|
|
var args = [callee];
|
|
|
|
// bind `this` to object in member expressions
|
|
if (t.isMemberExpression(callee)) {
|
|
var object = state.wrapSideEffect(callee.object);
|
|
callee.object = object.expr;
|
|
thisBinding = object.ref;
|
|
}
|
|
|
|
if (node.arguments.length > 0 || thisBinding) {
|
|
args.push(t.arrayExpression(node.arguments));
|
|
}
|
|
|
|
if (thisBinding) {
|
|
args.push(thisBinding);
|
|
}
|
|
|
|
return [t.returnStatement(t.callExpression(
|
|
state.getHelperRef(),
|
|
args
|
|
))];
|
|
}
|
|
})(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, file) {
|
|
var tempId, helperRef;
|
|
|
|
var state = {
|
|
ownerId: node.id,
|
|
|
|
getHelperRef: function () {
|
|
return helperRef = helperRef || file.addHelper("tail-call");
|
|
},
|
|
|
|
wrapSideEffect: function (node) {
|
|
if (t.isIdentifier(node) || t.isLiteral(node)) {
|
|
return {expr: node, ref: node};
|
|
}
|
|
tempId = tempId || scope.generateUidIdentifier("temp");
|
|
return {
|
|
expr: t.assignmentExpression("=", tempId, node),
|
|
ref: tempId
|
|
};
|
|
}
|
|
};
|
|
|
|
// traverse the function and look for tail recursion
|
|
scope.traverse(node, functionVisitor, state);
|
|
|
|
if (tempId) {
|
|
t.ensureBlock(node).body.unshift(t.variableDeclaration("var", [
|
|
t.variableDeclarator(tempId)
|
|
]));
|
|
}
|
|
};
|