diff --git a/lib/6to5/transformation/templates/tail-call-body.js b/lib/6to5/transformation/templates/tail-call-body.js new file mode 100644 index 0000000000..c2366a8cb6 --- /dev/null +++ b/lib/6to5/transformation/templates/tail-call-body.js @@ -0,0 +1,13 @@ +{ + var ARGUMENTS_ID = arguments, + THIS_ID = this, + SHOULD_CONTINUE_ID, + RESULT_ID; + + do { + SHOULD_CONTINUE_ID = false; + RESULT_ID = FUNCTION.apply(THIS_ID, ARGUMENTS_ID); + } while(SHOULD_CONTINUE_ID); + + return RESULT_ID; +} diff --git a/lib/6to5/transformation/transformers/es6/tail-call.js b/lib/6to5/transformation/transformers/es6/tail-call.js index c54259135c..b8dfba2418 100644 --- a/lib/6to5/transformation/transformers/es6/tail-call.js +++ b/lib/6to5/transformation/transformers/es6/tail-call.js @@ -1,6 +1,7 @@ "use strict"; -var t = require("../../../types"); +var util = require("../../../util"); +var t = require("../../../types"); function returnBlock(expr) { return t.blockStatement([t.returnStatement(expr)]); @@ -17,6 +18,7 @@ function transformExpression(node, scope, state) { 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); @@ -33,6 +35,7 @@ function transformExpression(node, scope, state) { if (!callRight) { return; } + // cache left value as it might have side-effects var leftId = state.getLeftId(); var testExpr = t.assignmentExpression( @@ -47,16 +50,19 @@ function transformExpression(node, scope, state) { 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": @@ -68,12 +74,15 @@ function transformExpression(node, scope, state) { 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; } @@ -91,11 +100,13 @@ function transformExpression(node, scope, state) { state.getArgumentsId(), args || t.arrayExpression(node.arguments) )), + t.expressionStatement(t.assignmentExpression( "=", state.getThisId(), thisBinding || t.identifier("undefined") )), + t.returnStatement(t.assignmentExpression( "=", state.getShouldContinueId(), @@ -152,15 +163,19 @@ exports.FunctionExpression = function (node, parent, scope) { 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"); } @@ -182,32 +197,11 @@ exports.FunctionExpression = function (node, parent, scope) { 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) - ]); + node.body = util.template("tail-call-body", { + SHOULD_CONTINUE_ID: shouldContinueId, + ARGUMENTS_ID: argumentsId, + RESULT_ID: resultId, + FUNCTION: t.functionExpression(null, node.params, block), + THIS_ID: thisId, + }); };