UNPKG

jambda-calc

Version:

Convert JavaScript functions to the Lambda Calculus, and visualize them with Tromp diagrams.

196 lines 8.06 kB
#!/usr/bin/env node /** * Main command-line tool for jambda-calc - runs transpilation and/or visualization */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { execSync } from 'child_process'; import { transpile } from '../lib/transpiler/index.js'; import { visualize } from '../lib/visualizer/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const args = process.argv.slice(2); let inputFile = ''; let outputPath = null; let format = 'png'; let shouldTranspile = true; let shouldVisualize = false; // used in terminal outputs const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', purple: '\x1b[35m', yellow: '\x1b[33m', white: '\x1b[37m', gray: '\x1b[90m', }; function showHelp() { console.log('Usage: jambda-calc [options]\n'); console.log('Commands:'); console.log(' examples Run built-in examples with explanations\n'); console.log('Options:'); console.log(' --input, -i Input file. JS/TS file for transpilation, or lambda calculus file for visualization.'); console.log(' --transpile, -t Only transpile the input to lambda expression (no visualization).'); console.log(' --visualize, -v Only visualize the input (treats input as lambda expression when used alone)'); console.log(' --output, -o Output path for all generated files (if not provided, displays in console)'); console.log(' --format, -f Output format for diagrams: png (default), svg, or ascii'); console.log(' --help, -h Show this help message'); } if (args.length === 1 && args[0] === 'examples') { try { const examplesScriptPath = path.resolve(__dirname, '../lib/scripts/run-examples.js'); try { execSync(`node "${examplesScriptPath}"`, { stdio: 'inherit' }); process.exit(0); } catch (error) { console.error('Error running examples:', error instanceof Error ? error.message : String(error)); process.exit(1); } } catch (error) { console.error('Error running examples:', error instanceof Error ? error.message : String(error)); process.exit(1); } } for (let i = 0; i < args.length; i++) { if (args[i] === '--input' || args[i] === '-i') { if (i + 1 < args.length) { inputFile = args[i + 1]; i++; } } else if (args[i] === '--output' || args[i] === '-o') { if (i + 1 < args.length) { outputPath = args[i + 1]; i++; } } else if (args[i] === '--transpile' || args[i] === '-t') { shouldTranspile = true; shouldVisualize = false; } else if (args[i] === '--visualize' || args[i] === '-v') { shouldVisualize = true; shouldTranspile = false; } else if (args[i] === '--format' || args[i] === '-f') { if (i + 1 < args.length) { const validFormats = ['svg', 'png', 'ascii']; const selectedFormat = args[i + 1].toLowerCase(); if (validFormats.includes(selectedFormat)) { format = selectedFormat; } else { console.error(`${colors.red}Invalid format: ${selectedFormat}${colors.reset}`); process.exit(1); } i++; } } else if (args[i] === '--help' || args[i] === '-h') { showHelp(); process.exit(0); } } // process flags if (!args.some((arg) => arg === '--transpile' || arg === '-t' || arg === '--visualize' || arg === '-v')) { // if neither -t or -v, do both shouldTranspile = true; shouldVisualize = true; } else if (args.some((arg) => arg === '--visualize' || arg === '-v') && !args.some((arg) => arg === '--transpile' || arg === '-t')) { // if only -v, treat input as lambda expression shouldTranspile = false; shouldVisualize = true; } async function main() { try { if (inputFile === '') return; const inputContent = fs.readFileSync(inputFile, 'utf-8'); let lambdaExpression; //// 1. TRANSPILE (OR SKIP) //// if (!shouldTranspile && shouldVisualize) { // if only -v, treat input as lambda expression lambdaExpression = inputContent.trim(); console.log(`${colors.gray}Using lambda expression from ${inputFile}${colors.reset}`); } else { // default transpile input lambdaExpression = transpile(inputContent); // save .txt of lambda expression if -o specified if (outputPath) { let lambdaOutputPath; if (path.extname(outputPath) === '') { lambdaOutputPath = path.join(outputPath, `${path.basename(inputFile, path.extname(inputFile))}.txt`); } else { // use current output path if extension already given lambdaOutputPath = outputPath; } if (!fs.existsSync(path.dirname(lambdaOutputPath))) { fs.mkdirSync(path.dirname(lambdaOutputPath), { recursive: true }); } fs.writeFileSync(lambdaOutputPath, lambdaExpression, 'utf-8'); console.log(`${colors.gray}Lambda calculus notation written to ${lambdaOutputPath}${colors.reset}`); } } // print to console if no -o if (!outputPath) { console.log(`\n${colors.bright}${colors.purple}λ Expression:\n${colors.reset} ${colors.purple}${lambdaExpression .replace(/\\/g, 'λ') .replace(/->/g, '.') .replace(/\(\(([^()]*)\)\)/g, '($1)')}${colors.reset}\n`); } //// 2: VISUALIZE (IF NEEDED) //// if (shouldVisualize) { const options = { unitSize: 12, lineWidth: 2, padding: 10, backgroundColor: '#282a36', preserveAspectRatio: true, }; if (outputPath) { let visOutputPath; // handle cases where user adds an extension to the output if (path.extname(outputPath) === '') { visOutputPath = path.join(outputPath, `${path.basename(inputFile, path.extname(inputFile))}.${format}`); } else { const baseOutputPath = outputPath.replace(path.extname(outputPath), ''); visOutputPath = `${baseOutputPath}.${format}`; } if (!fs.existsSync(path.dirname(visOutputPath))) { fs.mkdirSync(path.dirname(visOutputPath), { recursive: true }); } visualize(lambdaExpression, options, visOutputPath, format); console.log(`${colors.gray}Generated diagram: ${visOutputPath}${colors.reset}`); } else { // if no -o, display in console const diagram = visualize(lambdaExpression, options, undefined, 'ascii'); try { console.log(`\n${colors.bright}${colors.white}Tromp Diagram${colors.reset}\n`); console.log(colors.white + diagram + colors.reset); console.log(`\n${colors.yellow}Note: Use --output (or -o) to save graphical SVG/PNG versions.${colors.reset}`); } catch (err) { console.error(`${colors.red}Error rendering ASCII diagram:${colors.reset}`, err); console.log(`${colors.yellow}Use --output to save a graphical SVG/PNG version.${colors.reset}`); } } } } catch (error) { console.error(`${colors.red}Error:${colors.reset}`, error instanceof Error ? error.message : String(error)); process.exit(1); } } main(); //# sourceMappingURL=jambda.js.map