import { type Options, type Dist, DISTS, defaultOptions } from "./types"; import { showHelp } from "./help"; export function showError(msg: string): never { console.error(`rand: ${msg}\nTry 'rand --help' for usage.`); process.exit(1); } export function parseArgs(raw: string[]): { options: Options; help: boolean } { const positional: string[] = []; let count = 1; let decimals = 0; let dist: Dist = "uniform"; let help = false; let i = 0; while (i < raw.length) { const arg = raw[i]!; if (arg === "-h" || arg === "--help") { help = true; i++; continue; } // -f (plain) → default 2 decimal places if (arg === "-f") { decimals = 2; i++; continue; } // -fN combined form: -f1, -f2, -f3, … if (/^-f\d+$/.test(arg)) { decimals = parseInt(arg.slice(2), 10); i++; continue; } // --float [N] if (arg === "--float") { i++; if (i < raw.length && /^\d+$/.test(raw[i]!)) { decimals = parseInt(raw[i]!, 10); i++; } else { decimals = 2; } continue; } // -d, --dist if (arg === "-d" || arg === "--dist") { i++; if (i >= raw.length) showError("--dist requires a distribution name"); const name = raw[i]!; if (!(DISTS as string[]).includes(name)) { showError(`unknown distribution: ${name}\n valid: ${DISTS.join(", ")}`); } dist = name as Dist; i++; continue; } // -c, --count if (arg === "-c" || arg === "--count") { i++; if (i >= raw.length) showError("--count requires a value"); const n = parseInt(raw[i]!, 10); if (isNaN(n) || n < 1) showError(`invalid count: ${raw[i]}`); count = n; i++; continue; } // -- : everything after is positional if (arg === "--") { i++; while (i < raw.length) { positional.push(raw[i]!); i++; } break; } // Negative number as positional (e.g. -1, -0.5) if (/^-\d+(\.\d+)?$/.test(arg)) { positional.push(arg); i++; continue; } // Catch unknown flags if (arg.startsWith("-")) { showError(`unknown option: ${arg}`); } positional.push(arg); i++; } const opts = defaultOptions(dist); opts.count = count; opts.decimals = decimals; applyPositionals(opts, positional); return { options: opts, help }; } export function applyPositionals(opts: Options, args: string[]): void { const nums = args.map(parseNum); switch (opts.dist) { case "uniform": { if (nums.length === 0) return; if (nums.length === 1) { opts.max = nums[0]!; } else if (nums.length === 2) { opts.min = nums[0]!; opts.max = nums[1]!; } else { showError(`uniform expects 0–2 args, got ${nums.length}: ${args.join(" ")}`); } if (opts.min > opts.max) showError(`min (${opts.min}) > max (${opts.max})`); break; } case "normal": { if (nums.length === 0) return; if (nums.length === 1) { opts.mean = nums[0]!; } else if (nums.length === 2) { opts.mean = nums[0]!; opts.stddev = nums[1]!; } else { showError(`normal expects 0–2 args, got ${nums.length}: ${args.join(" ")}`); } if (opts.stddev <= 0) showError(`stddev must be > 0, got ${opts.stddev}`); break; } case "binomial": { if (nums.length === 0) return; if (nums.length === 1) { opts.trials = nums[0]!; } else if (nums.length === 2) { opts.trials = nums[0]!; opts.prob = nums[1]!; } else { showError(`binomial expects 0–2 args, got ${nums.length}: ${args.join(" ")}`); } if (opts.trials < 0 || !Number.isInteger(opts.trials)) { showError(`trials must be a non-negative integer, got ${opts.trials}`); } if (opts.prob < 0 || opts.prob > 1) showError(`prob must be 0–1, got ${opts.prob}`); break; } case "poisson": { if (nums.length === 0) return; if (nums.length === 1) { opts.lambda = nums[0]!; } else { showError(`poisson expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`); } if (opts.lambda <= 0) showError(`lambda must be > 0, got ${opts.lambda}`); break; } case "exponential": { if (nums.length === 0) return; if (nums.length === 1) { opts.lambda = nums[0]!; } else { showError(`exponential expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`); } if (opts.lambda <= 0) showError(`lambda must be > 0, got ${opts.lambda}`); break; } case "hypergeometric": { if (nums.length === 0) return; if (nums.length >= 1) opts.popSize = nums[0]!; if (nums.length >= 2) opts.successes = nums[1]!; if (nums.length >= 3) opts.draws = nums[2]!; if (nums.length > 3) { showError(`hypergeometric expects 0–3 args, got ${nums.length}: ${args.join(" ")}`); } if (opts.popSize < 0 || !Number.isInteger(opts.popSize)) { showError(`population size N must be a non-negative integer, got ${opts.popSize}`); } if (opts.successes < 0 || opts.successes > opts.popSize || !Number.isInteger(opts.successes)) { showError(`successes K must be 0–N, got ${opts.successes} (N=${opts.popSize})`); } if (opts.draws < 0 || opts.draws > opts.popSize || !Number.isInteger(opts.draws)) { showError(`draws n must be 0–N, got ${opts.draws} (N=${opts.popSize})`); } break; } } } function parseNum(s: string): number { const n = Number(s); if (isNaN(n)) showError(`not a number: ${s}`); return n; }