UNPKG

babel-plugin-recursive-tail-calls

Version:

A babel plugin for performing tail call optimization in recursive functions.

65 lines (64 loc) 3.12 kB
import { arrayPattern, isIdentifier, isVariableDeclarator, } from "@babel/types"; import { callExpressionRewriter, } from "./callExpressionRewriter.js"; import { findRecursion } from "./tailRecursionFinder.js"; export default function ({ types: t }) { return { visitor: { Function(path) { const functionIdentifier = getFunctionIdentifier(path); if (!functionIdentifier) return; const functionBody = path.get("body"); const labelIdentifier = path.scope.generateUidIdentifier("tail-call-loop"); const conditionIdentifier = path.scope.generateUidIdentifier("continue-recursion"); if (functionBody.isExpression()) { if (!findRecursion(functionBody, functionIdentifier)) return; functionBody.replaceWith(t.blockStatement([t.returnStatement(functionBody.node)])); } // this should never happen but is added for adequate type hints if (!functionBody.isBlockStatement()) throw new Error("Body is not block statement"); const state = { recursion: false, labelIdentifier, functionIdentifier, conditionIdentifier, functionPath: path, parameters: arrayPattern(path.node.params), }; path.traverse(callExpressionRewriter, state); // abort if there is no recursion if (!state.recursion) return; const conditionDeclaration = t.variableDeclaration("let", [ t.variableDeclarator(conditionIdentifier, t.booleanLiteral(true)), ]); // wrap function body in while loop const whileStatement = t.whileStatement(conditionIdentifier, functionBody.node); // insert `condition = false` first in loop functionBody.unshiftContainer("body", t.expressionStatement(t.assignmentExpression("=", conditionIdentifier, t.booleanLiteral(false)))); const labeledStatement = t.labeledStatement(labelIdentifier, whileStatement); const blockStatement = t.blockStatement([ conditionDeclaration, labeledStatement, ]); functionBody.replaceWith(blockStatement); }, }, }; } function getFunctionIdentifier(functionPath) { var _a; if (functionPath.isFunctionDeclaration()) { return functionPath.node.id; } else if (functionPath.isArrowFunctionExpression() || functionPath.isFunctionExpression()) { if (isVariableDeclarator(functionPath.parent) && isIdentifier(functionPath.parent.id) && ((_a = functionPath.scope.getBinding(functionPath.parent.id.name)) === null || _a === void 0 ? void 0 : _a.constant)) { return functionPath.parent.id; } } }