// Transform function definitions to support tail call optimization via trampolines
function transformToTCO(functionDefs) {
const functions = new Map();
// Parse function definitions
functionDefs.forEach(([name, params, body]) => {
functions.set(name, { params, body });
});
// Transform each function body to use trampolines
const transformedFunctions = {};
functionDefs.forEach(([name, params, body]) => {
const transformedBody = transformFunctionBody(body, functions);
// Create the trampoline wrapper
transformedFunctions[name] = new Function(...params, `
function trampoline(fn) {
let result = fn;
while (typeof result === 'function') {
result = result();
}
return result;
}
function ${name}_impl(${params.join(', ')}) {
${transformedBody}
}
return trampoline(() => ${name}_impl(${params.join(', ')}));
`);
});
return transformedFunctions;
}
function transformFunctionBody(body, functions) {
// Remove 'return ' prefix if present
const cleanBody = body.replace(/^return\s+/, '');
// Transform the body to use continuation-passing style
return `return ${transformExpression(cleanBody, functions)};`;
}
function transformExpression(expr, functions) {
// Handle ternary operator (condition ? expr1 : expr2)
const ternaryMatch = expr.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
if (ternaryMatch) {
const [, condition, thenExpr, elseExpr] = ternaryMatch;
const transformedThen = transformExpression(thenExpr.trim(), functions);
const transformedElse = transformExpression(elseExpr.trim(), functions);
return `(${condition}) ? ${transformedThen} : ${transformedElse}`;
}
// Handle function calls - detect if it's a recursive call
const functionCallMatch = expr.match(/^(\w+)\s*\(([^)]*)\)$/);
if (functionCallMatch) {
const [, funcName, args] = functionCallMatch;
// If it's a function we're transforming, make it return a thunk
if (functions.has(funcName)) {
return `(() => ${funcName}_impl(${args}))`;
}
}
// Handle throw statements
if (expr.trim().startsWith('throw ')) {
return expr; // Keep throw statements as-is
}
// For non-function expressions (like strings, numbers), return as-is
return expr;
}
// Example usage and test cases
console.log("=== Testing TCO Transformer ===\n");
// Test Case 1: Simple countdown
const simpleCountdown = [
['f', ['x'], 'return x ? f(--x) : "Yay"']
];
console.log("Test 1: Simple countdown");
const tco1 = transformToTCO(simpleCountdown);
try {
console.log("f(5):", tco1.f(5));
console.log("f(100000):", tco1.f(100000)); // Should not stack overflow
console.log("✓ Success");
} catch (e) {
console.log("✗ Failed:", e.message);
}
console.log();
// Test Case 2: Function with multiple parameters
const multiParam = [
['g', ['n', 's'], 'return n ? g(--n, s || 0) : s']
];
console.log("Test 2: Multiple parameters");
const tco2 = transformToTCO(multiParam);
try {
console.log("g(3, 'hello'):", tco2.g(3, 'hello'));
console.log("g(5, undefined):", tco2.g(5, undefined));
console.log("g(50000, 'result'):", tco2.g(50000, 'result')); // Should not stack overflow
console.log("✓ Success");
} catch (e) {
console.log("✗ Failed:", e.message);
}
console.log();
// Test Case 3: Circular tail calls (mutual recursion)
const circularCalls = [
['f0', ['x', 'y'], 'return f1(x, y)'],
['f1', ['x', 'y'], 'return f2(x, y)'],
['f2', ['x', 'y'], 'return x > 0 ? f0(x - 1, y + 1) : y']
];
console.log("Test 3: Circular tail calls");
const tco3 = transformToTCO(circularCalls);
try {
console.log("f0(5, 10):", tco3.f0(5, 10));
console.log("f0(1000, 0):", tco3.f0(1000, 0)); // Should not stack overflow
console.log("✓ Success");
} catch (e) {
console.log("✗ Failed:", e.message);
}
console.log();
// Test Case 4: Large circular call chain (like your 10000 functions example)
const largeCircular = [...Array(100)].map((_, i) => [
`f${i}`,
['x', 'y'],
i === 99
? 'return x > 0 ? f0(x - 1, y + 1) : y' // Last function loops back to f0
: `return f${i + 1}(x, y)` // Each function calls the next
]);
console.log("Test 4: Large circular call chain (100 functions)");
const tco4 = transformToTCO(largeCircular);
try {
console.log("f0(5, 0):", tco4.f0(5, 0));
console.log("f0(200, 100):", tco4.f0(200, 100)); // Should not stack overflow
console.log("✓ Success");
} catch (e) {
console.log("✗ Failed:", e.message);
}
console.log();
// Test Case 5: With error throwing
const withErrors = [
['errorTest', ['x'], 'return x < 0 ? throw new Error("Negative!") : x === 0 ? "Done" : errorTest(x - 1)']
];
console.log("Test 5: With error throwing");
const tco5 = transformToTCO(withErrors);
try {
console.log("errorTest(3):", tco5.errorTest(3));
console.log("errorTest(0):", tco5.errorTest(0));
try {
console.log("errorTest(-1):", tco5.errorTest(-1));
} catch (e) {
console.log("errorTest(-1) threw:", e.message);
}
console.log("✓ Success");
} catch (e) {
console.log("✗ Failed:", e.message);
}
// Performance comparison
console.log("\n=== Performance Comparison ===");
// Original recursive function (will stack overflow at high values)
function originalF(x) {
return x ? originalF(x - 1) : "Yay";
}
// TCO version
const tcoF = transformToTCO([['f', ['x'], 'return x ? f(--x) : "Yay"']]);
console.time("TCO f(10000)");
console.log("TCO result:", tcoF.f(10000));
console.timeEnd("TCO f(10000)");
console.time("Original f(1000)");
console.log("Original result:", originalF(1000));
console.timeEnd("Original f(1000)");
console.log("\nNote: Original function will stack overflow at ~10000+ iterations");
console.log("TCO version can handle much larger values without stack overflow");
To embed this program on your website, copy the following code and paste it into your website's HTML: