refactor tail call transformer into a class - @RReverser
This commit is contained in:
parent
87af83f1cb
commit
f1bca0013e
@ -1,21 +1,144 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var _ = require("lodash");
|
|
||||||
var util = require("../../../util");
|
var util = require("../../../util");
|
||||||
var t = require("../../../types");
|
var t = require("../../../types");
|
||||||
|
var _ = require("lodash");
|
||||||
|
|
||||||
function returnBlock(expr) {
|
function returnBlock(expr) {
|
||||||
return t.blockStatement([t.returnStatement(expr)]);
|
return t.blockStatement([t.returnStatement(expr)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformExpression(node, scope, state) {
|
function TailCall(node, scope, file) {
|
||||||
if (!node) return;
|
this.hasTailRecursion = false;
|
||||||
|
this.needsArguments = false;
|
||||||
|
this.setsArguments = false;
|
||||||
|
this.needsThis = false;
|
||||||
|
this.ownerId = node.id;
|
||||||
|
this.vars = [];
|
||||||
|
|
||||||
return (function subTransform(node) {
|
this.scope = scope;
|
||||||
switch (node.type) {
|
this.file = file;
|
||||||
case "ConditionalExpression":
|
this.node = node;
|
||||||
var callConsequent = subTransform(node.consequent);
|
}
|
||||||
var callAlternate = subTransform(node.alternate);
|
|
||||||
|
TailCall.prototype.getArgumentsId = function () {
|
||||||
|
return this.argumentsId = this.argumentsId || this.scope.generateUidIdentifier("arguments");
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.getThisId = function () {
|
||||||
|
return this.thisId = this.thisId || this.scope.generateUidIdentifier("this");
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.getLeftId = function () {
|
||||||
|
return this.leftId = this.leftId || this.scope.generateUidIdentifier("left");
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.getFunctionId = function () {
|
||||||
|
return this.functionId = this.functionId || this.scope.generateUidIdentifier("function");
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.getParams = function () {
|
||||||
|
var params = this.params;
|
||||||
|
|
||||||
|
if (!params) {
|
||||||
|
params = this.node.params;
|
||||||
|
this.paramDecls = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < params.length; i++) {
|
||||||
|
var param = params[i];
|
||||||
|
if (!param._isDefaultPlaceholder) {
|
||||||
|
this.paramDecls.push(t.variableDeclarator(
|
||||||
|
param,
|
||||||
|
params[i] = this.scope.generateUidIdentifier("x")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.params = params;
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.run = function () {
|
||||||
|
var scope = this.scope;
|
||||||
|
var node = this.node;
|
||||||
|
|
||||||
|
// only tail recursion can be optimized as for now,
|
||||||
|
// so we can skip anonymous functions entirely
|
||||||
|
var ownerId = this.ownerId;
|
||||||
|
if (!ownerId) return;
|
||||||
|
|
||||||
|
// traverse the function and look for tail recursion
|
||||||
|
scope.traverse(node, firstPass, this);
|
||||||
|
|
||||||
|
if (!this.hasTailRecursion) return;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
scope.traverse(node, secondPass, this);
|
||||||
|
|
||||||
|
if (!this.needsThis || !this.needsArguments) {
|
||||||
|
scope.traverse(node, thirdPass, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = t.ensureBlock(node).body;
|
||||||
|
|
||||||
|
if (this.vars.length > 0) {
|
||||||
|
body.unshift(t.expressionStatement(
|
||||||
|
_(this.vars)
|
||||||
|
.map(function (decl) {
|
||||||
|
return decl.declarations;
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.reduceRight(function (expr, decl) {
|
||||||
|
return t.assignmentExpression("=", decl.id, expr);
|
||||||
|
}, t.identifier("undefined"))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
var paramDecls = this.paramDecls;
|
||||||
|
if (paramDecls.length > 0) {
|
||||||
|
body.unshift(t.variableDeclaration("var", paramDecls));
|
||||||
|
}
|
||||||
|
|
||||||
|
node.body = util.template("tail-call-body", {
|
||||||
|
THIS_ID: this.thisId,
|
||||||
|
ARGUMENTS_ID: this.argumentsId,
|
||||||
|
FUNCTION_ID: this.getFunctionId(),
|
||||||
|
BLOCK: node.body
|
||||||
|
});
|
||||||
|
|
||||||
|
var topVars = [];
|
||||||
|
|
||||||
|
if (this.needsThis) {
|
||||||
|
topVars.push(t.variableDeclarator(this.getThisId(), t.thisExpression()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.needsArguments || this.setsArguments) {
|
||||||
|
var decl = t.variableDeclarator(this.getArgumentsId());
|
||||||
|
if (this.needsArguments) {
|
||||||
|
decl.init = t.identifier("arguments");
|
||||||
|
}
|
||||||
|
topVars.push(decl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftId = this.leftId;
|
||||||
|
if (leftId) {
|
||||||
|
topVars.push(t.variableDeclarator(leftId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topVars.length > 0) {
|
||||||
|
node.body.body.unshift(t.variableDeclaration("var", topVars));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.subTransform = function (node) {
|
||||||
|
var handler = this["subTransform" + node.type];
|
||||||
|
if (handler) return handler.call(this, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.subTransformConditionalExpression = function (node) {
|
||||||
|
var callConsequent = this.subTransform(node.consequent);
|
||||||
|
var callAlternate = this.subTransform(node.alternate);
|
||||||
if (!callConsequent && !callAlternate) {
|
if (!callConsequent && !callAlternate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -23,22 +146,23 @@ function transformExpression(node, scope, state) {
|
|||||||
// if ternary operator had tail recursion in value, convert to optimized if-statement
|
// if ternary operator had tail recursion in value, convert to optimized if-statement
|
||||||
node.type = "IfStatement";
|
node.type = "IfStatement";
|
||||||
node.consequent = callConsequent ? t.toBlock(callConsequent) : returnBlock(node.consequent);
|
node.consequent = callConsequent ? t.toBlock(callConsequent) : returnBlock(node.consequent);
|
||||||
|
|
||||||
if (callAlternate) {
|
if (callAlternate) {
|
||||||
node.alternate = t.isIfStatement(callAlternate) ? callAlternate : t.toBlock(callAlternate);
|
node.alternate = t.isIfStatement(callAlternate) ? callAlternate : t.toBlock(callAlternate);
|
||||||
} else {
|
} else {
|
||||||
node.alternate = returnBlock(node.alternate);
|
node.alternate = returnBlock(node.alternate);
|
||||||
}
|
}
|
||||||
return [node];
|
|
||||||
|
|
||||||
case "LogicalExpression":
|
return [node];
|
||||||
|
};
|
||||||
|
|
||||||
|
TailCall.prototype.subTransformLogicalExpression = function (node) {
|
||||||
// only call in right-value of can be optimized
|
// only call in right-value of can be optimized
|
||||||
var callRight = subTransform(node.right);
|
var callRight = this.subTransform(node.right);
|
||||||
if (!callRight) {
|
if (!callRight) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cache left value as it might have side-effects
|
// cache left value as it might have side-effects
|
||||||
var leftId = state.getLeftId();
|
var leftId = this.getLeftId();
|
||||||
var testExpr = t.assignmentExpression(
|
var testExpr = t.assignmentExpression(
|
||||||
"=",
|
"=",
|
||||||
leftId,
|
leftId,
|
||||||
@ -50,12 +174,13 @@ function transformExpression(node, scope, state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [t.ifStatement(testExpr, returnBlock(leftId))].concat(callRight);
|
return [t.ifStatement(testExpr, returnBlock(leftId))].concat(callRight);
|
||||||
|
};
|
||||||
|
|
||||||
case "SequenceExpression":
|
TailCall.prototype.subTransformSequenceExpression = function (node) {
|
||||||
var seq = node.expressions;
|
var seq = node.expressions;
|
||||||
|
|
||||||
// only last element can be optimized
|
// only last element can be optimized
|
||||||
var lastCall = subTransform(seq[seq.length - 1]);
|
var lastCall = this.subTransform(seq[seq.length - 1]);
|
||||||
if (!lastCall) {
|
if (!lastCall) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -67,8 +192,9 @@ function transformExpression(node, scope, state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [t.expressionStatement(node)].concat(lastCall);
|
return [t.expressionStatement(node)].concat(lastCall);
|
||||||
|
};
|
||||||
|
|
||||||
case "CallExpression":
|
TailCall.prototype.subTransformCallExpression = function (node) {
|
||||||
var callee = node.callee, prop, thisBinding, args;
|
var callee = node.callee, prop, thisBinding, args;
|
||||||
|
|
||||||
if (t.isMemberExpression(callee, { computed: false }) &&
|
if (t.isMemberExpression(callee, { computed: false }) &&
|
||||||
@ -91,18 +217,18 @@ function transformExpression(node, scope, state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only tail recursion can be optimized as for now
|
// only tail recursion can be optimized as for now
|
||||||
if (!t.isIdentifier(callee) || !scope.bindingEquals(callee.name, state.ownerId)) {
|
if (!t.isIdentifier(callee) || !this.scope.bindingEquals(callee.name, this.ownerId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.hasTailRecursion = true;
|
this.hasTailRecursion = true;
|
||||||
|
|
||||||
var body = [];
|
var body = [];
|
||||||
|
|
||||||
if (!t.isThisExpression(thisBinding)) {
|
if (!t.isThisExpression(thisBinding)) {
|
||||||
body.push(t.expressionStatement(t.assignmentExpression(
|
body.push(t.expressionStatement(t.assignmentExpression(
|
||||||
"=",
|
"=",
|
||||||
state.getThisId(),
|
this.getThisId(),
|
||||||
thisBinding || t.identifier("undefined")
|
thisBinding || t.identifier("undefined")
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@ -111,8 +237,8 @@ function transformExpression(node, scope, state) {
|
|||||||
args = t.arrayExpression(node.arguments);
|
args = t.arrayExpression(node.arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
var argumentsId = state.getArgumentsId();
|
var argumentsId = this.getArgumentsId();
|
||||||
var params = state.getParams();
|
var params = this.getParams();
|
||||||
|
|
||||||
body.push(t.expressionStatement(t.assignmentExpression(
|
body.push(t.expressionStatement(t.assignmentExpression(
|
||||||
"=",
|
"=",
|
||||||
@ -132,7 +258,7 @@ function transformExpression(node, scope, state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.setsArguments = true;
|
this.setsArguments = true;
|
||||||
for (i = 0; i < params.length; i++) {
|
for (i = 0; i < params.length; i++) {
|
||||||
param = params[i];
|
param = params[i];
|
||||||
if (!param._isDefaultPlaceholder) {
|
if (!param._isDefaultPlaceholder) {
|
||||||
@ -145,19 +271,17 @@ function transformExpression(node, scope, state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.push(t.continueStatement(state.getFunctionId()));
|
body.push(t.continueStatement(this.getFunctionId()));
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
}
|
};
|
||||||
})(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Looks for and replaces tail recursion calls.
|
// looks for and replaces tail recursion calls
|
||||||
var firstPass = {
|
var firstPass = {
|
||||||
enter: function (node, parent, scope, state) {
|
enter: function (node, parent, scope, state) {
|
||||||
if (t.isReturnStatement(node)) {
|
if (t.isReturnStatement(node)) {
|
||||||
this.skip();
|
this.skip();
|
||||||
return transformExpression(node.argument, scope, state);
|
return state.subTransform(node.argument);
|
||||||
} else if (t.isTryStatement(parent)) {
|
} else if (t.isTryStatement(parent)) {
|
||||||
if (node === parent.block) {
|
if (node === parent.block) {
|
||||||
this.skip();
|
this.skip();
|
||||||
@ -173,8 +297,8 @@ var firstPass = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hoists up function declarations, replaces `this` and `arguments` and
|
// hoists up function declarations, replaces `this` and `arguments` and marks
|
||||||
// marks them as needed.
|
// them as needed
|
||||||
var secondPass = {
|
var secondPass = {
|
||||||
enter: function (node, parent, scope, state) {
|
enter: function (node, parent, scope, state) {
|
||||||
if (t.isThisExpression(node)) {
|
if (t.isThisExpression(node)) {
|
||||||
@ -196,8 +320,7 @@ var secondPass = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimizes recursion by removing `this` and `arguments`
|
// optimizes recursion by removing `this` and `arguments` if they aren't used
|
||||||
// if they are not used.
|
|
||||||
var thirdPass = {
|
var thirdPass = {
|
||||||
enter: function (node, parent, scope, state) {
|
enter: function (node, parent, scope, state) {
|
||||||
if (!t.isExpressionStatement(node)) return;
|
if (!t.isExpressionStatement(node)) return;
|
||||||
@ -213,112 +336,7 @@ var thirdPass = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.Function = function (node, parent, scope) {
|
exports.Function = function (node, parent, scope, file) {
|
||||||
// only tail recursion can be optimized as for now,
|
var tailCall = new TailCall(node, scope, file);
|
||||||
// so we can skip anonymous functions entirely
|
tailCall.run();
|
||||||
var ownerId = node.id;
|
|
||||||
if (!ownerId) return;
|
|
||||||
|
|
||||||
var argumentsId, thisId, leftId, functionId, params, paramDecls;
|
|
||||||
|
|
||||||
var state = {
|
|
||||||
hasTailRecursion: false,
|
|
||||||
needsThis: false,
|
|
||||||
needsArguments: false,
|
|
||||||
setsArguments: false,
|
|
||||||
ownerId: ownerId,
|
|
||||||
vars: [],
|
|
||||||
|
|
||||||
getArgumentsId: function () {
|
|
||||||
return argumentsId = argumentsId || scope.generateUidIdentifier("arguments");
|
|
||||||
},
|
|
||||||
|
|
||||||
getThisId: function () {
|
|
||||||
return thisId = thisId || scope.generateUidIdentifier("this");
|
|
||||||
},
|
|
||||||
|
|
||||||
getLeftId: function () {
|
|
||||||
return leftId = leftId || scope.generateUidIdentifier("left");
|
|
||||||
},
|
|
||||||
|
|
||||||
getFunctionId: function () {
|
|
||||||
return functionId = functionId || scope.generateUidIdentifier("function");
|
|
||||||
},
|
|
||||||
|
|
||||||
getParams: function () {
|
|
||||||
if (!params) {
|
|
||||||
params = node.params;
|
|
||||||
paramDecls = [];
|
|
||||||
for (var i = 0; i < params.length; i++) {
|
|
||||||
var param = params[i];
|
|
||||||
if (!param._isDefaultPlaceholder) {
|
|
||||||
paramDecls.push(t.variableDeclarator(
|
|
||||||
param,
|
|
||||||
params[i] = scope.generateUidIdentifier("x")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// traverse the function and look for tail recursion
|
|
||||||
scope.traverse(node, firstPass, state);
|
|
||||||
|
|
||||||
if (!state.hasTailRecursion) return;
|
|
||||||
|
|
||||||
scope.traverse(node, secondPass, state);
|
|
||||||
|
|
||||||
if (!state.needsThis || !state.needsArguments) {
|
|
||||||
scope.traverse(node, thirdPass, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = t.ensureBlock(node).body;
|
|
||||||
|
|
||||||
if (state.vars.length > 0) {
|
|
||||||
body.unshift(t.expressionStatement(
|
|
||||||
_(state.vars)
|
|
||||||
.map(function (decl) {
|
|
||||||
return decl.declarations;
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.reduceRight(function (expr, decl) {
|
|
||||||
return t.assignmentExpression("=", decl.id, expr);
|
|
||||||
}, t.identifier("undefined"))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paramDecls.length > 0) {
|
|
||||||
body.unshift(t.variableDeclaration("var", paramDecls));
|
|
||||||
}
|
|
||||||
|
|
||||||
node.body = util.template("tail-call-body", {
|
|
||||||
THIS_ID: thisId,
|
|
||||||
ARGUMENTS_ID: argumentsId,
|
|
||||||
FUNCTION_ID: state.getFunctionId(),
|
|
||||||
BLOCK: node.body
|
|
||||||
});
|
|
||||||
|
|
||||||
var topVars = [];
|
|
||||||
|
|
||||||
if (state.needsThis) {
|
|
||||||
topVars.push(t.variableDeclarator(state.getThisId(), t.thisExpression()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.needsArguments || state.setsArguments) {
|
|
||||||
var decl = t.variableDeclarator(state.getArgumentsId());
|
|
||||||
if (state.needsArguments) {
|
|
||||||
decl.init = t.identifier("arguments");
|
|
||||||
}
|
|
||||||
topVars.push(decl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leftId) {
|
|
||||||
topVars.push(t.variableDeclarator(leftId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topVars.length > 0) {
|
|
||||||
node.body.body.unshift(t.variableDeclaration("var", topVars));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user