UNPKG

mcard-js

Version:

MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers

1,586 lines (1,563 loc) 73.7 kB
import { IO } from "./chunk-MPMRBT5R.js"; import { Either } from "./chunk-2KADE3SE.js"; import { SqliteNodeEngine } from "./chunk-L36L2VDL.js"; import { CardCollection, Maybe } from "./chunk-RCEU7PFH.js"; import { MCard } from "./chunk-O2UMNZGF.js"; // src/ptr/node/runtimes/base.ts import * as util from "util"; import * as child_process from "child_process"; var execFile2 = util.promisify(child_process.execFile); async function checkCommand(cmd, args = ["--version"]) { try { await execFile2(cmd, args); return true; } catch { return false; } } function parseOutput(stdout) { const trimmed = stdout.trim(); if (!trimmed) return void 0; try { return JSON.parse(trimmed); } catch { return trimmed; } } // src/ptr/node/runtimes/javascript.ts import * as fs2 from "fs"; import * as path2 from "path"; import * as vm from "vm"; import * as child_process2 from "child_process"; import * as util2 from "util"; // src/ptr/node/FileSystemUtils.ts import * as fs from "fs"; import * as path from "path"; function findProjectRoot(startDir = process.cwd()) { let searchDir = startDir; for (let i = 0; i < 5; i++) { if (fs.existsSync(path.join(searchDir, "pyproject.toml"))) { return searchDir; } searchDir = path.dirname(searchDir); } return startDir; } function listFiles(dirPath, recursive) { const files = []; if (!fs.existsSync(dirPath)) { return files; } const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isFile()) { if (!entry.name.startsWith(".") && !isProblematicFile(fullPath)) { files.push(fullPath); } } else if (entry.isDirectory() && recursive) { files.push(...listFiles(fullPath, recursive)); } } return files; } function isProblematicFile(filePath) { try { const stats = fs.statSync(filePath); if (stats.size > 50 * 1024 * 1024) return true; if (stats.size > 1024) { const fd = fs.openSync(filePath, "r"); const buffer = Buffer.alloc(1024); fs.readSync(fd, buffer, 0, 1024, 0); fs.closeSync(fd); let nullCount = 0; let controlCount = 0; for (let i = 0; i < buffer.length; i++) { if (buffer[i] === 0) nullCount++; else if (buffer[i] < 32 && buffer[i] !== 9 && buffer[i] !== 10 && buffer[i] !== 13) controlCount++; } if (nullCount > 300) return true; } return false; } catch { return true; } } function toRecord(value) { return typeof value === "object" && value !== null ? value : {}; } function extractLoaderParams(ctx, defaults = {}) { const params = toRecord(ctx.params); const balanced = toRecord(ctx.balanced); const inputArgs = { ...toRecord(balanced.input_arguments), ...toRecord(ctx.input_arguments) }; const outputArgs = { ...toRecord(balanced.output_arguments), ...toRecord(ctx.output_arguments) }; const allParams = { ...inputArgs, ...outputArgs, ...params }; const sourceDir = allParams.source_dir ?? defaults.sourceDir ?? "test_data"; const recursive = params.recursive !== void 0 ? params.recursive !== false : allParams.recursive !== false; const dbPath = allParams.db_path ?? defaults.dbPath; return { params, inputArgs, outputArgs, allParams, sourceDir, recursive, dbPath }; } function computeTimingMetrics(startTime, processedCount) { const durationSeconds = (Date.now() - startTime) / 1e3; const time_s = Math.round(durationSeconds * 1e4) / 1e4; const files_per_sec = durationSeconds > 0 ? Math.round(processedCount / durationSeconds * 100) / 100 : 0; return { durationSeconds, time_s, files_per_sec }; } // src/ptr/node/runtimes/javascript.ts var writeFile2 = util2.promisify(fs2.writeFile); var unlink2 = util2.promisify(fs2.unlink); var JavaScriptRuntime = class { async execute(code, context, config) { if (this.requiresSubprocess(code)) { return this.executeSubprocess(code, context); } return this.executeInVM(code, context); } /** * Check if code requires subprocess execution (has imports/exports). */ requiresSubprocess(code) { return code.includes("require(") || code.includes("import(") || code.includes("import ") || code.includes("export "); } /** * Execute code in VM sandbox (fast, for simple scripts). */ async executeInVM(code, context) { const sandbox = { context, target: context, result: void 0, console: { log: (...args) => console.log("[JS]", ...args), error: (...args) => console.error("[JS]", ...args), warn: (...args) => console.warn("[JS]", ...args) }, Math, JSON, Date, setTimeout, clearTimeout, setInterval, clearInterval, process, spawn: child_process2.spawn, exec: child_process2.exec, // Network capabilities fetch: global.fetch ? global.fetch.bind(global) : void 0, Headers: global.Headers, Request: global.Request, Response: global.Response, URL: global.URL, URLSearchParams: global.URLSearchParams }; const codeRunnable = code + "\nresult;"; const script = new vm.Script(codeRunnable); const vmContext = vm.createContext(sandbox); const executionResult = script.runInContext(vmContext, { timeout: 5e3 }); return sandbox.result !== void 0 ? sandbox.result : executionResult; } /** * Execute code in subprocess (for modules/imports). */ async executeSubprocess(code, context) { const contextStr = JSON.stringify(context); const projectRoot = findProjectRoot(); const wrapper = ` const process = require('process'); const context = JSON.parse(process.argv[2]); global.context = context; let result; (async () => { try { ${code} console.log(JSON.stringify(result)); } catch (e) { console.error(JSON.stringify({ error: e.message, stack: e.stack })); process.exit(1); } })(); `; const tmpFile = path2.resolve(projectRoot, `tmp_js_${Date.now()}.js`); await writeFile2(tmpFile, wrapper); try { const { stdout, stderr } = await execFile2("node", [tmpFile, contextStr], { cwd: projectRoot, env: { ...process.env } }); if (stderr && stderr.trim()) { try { const errObj = JSON.parse(stderr.trim()); if (errObj.error) throw new Error(errObj.error); } catch (e) { } } return parseOutput(stdout); } catch (error) { const stderr = error.stderr ? ` Stderr: ${error.stderr.toString()}` : ""; try { const errObj = JSON.parse(error.stderr?.toString().trim() || ""); if (errObj.error) throw new Error(errObj.error); } catch { } throw new Error(`JavaScript execution failed: ${error.message}${stderr}`); } finally { await unlink2(tmpFile); } } }; // src/ptr/node/runtimes/python.ts import * as fs3 from "fs"; import * as path3 from "path"; import * as util3 from "util"; var writeFile4 = util3.promisify(fs3.writeFile); var unlink4 = util3.promisify(fs3.unlink); var PythonRuntime = class { async execute(code, context, config) { const entryPoint = config.entry_point || "add"; const contextStr = JSON.stringify(context); const projectRoot = findProjectRoot(); const wrapper = this.createWrapper(code, entryPoint); const tmpFile = path3.resolve(process.cwd(), `tmp_${Date.now()}.py`); await writeFile4(tmpFile, wrapper); try { const { stdout, stderr } = await execFile2("python3", [tmpFile, contextStr], { cwd: projectRoot, env: { ...process.env, PYTHONPATH: `${projectRoot}${path3.delimiter}${process.env.PYTHONPATH || ""}` } }); if (stderr) console.error("[Python Stderr]", stderr); return parseOutput(stdout); } catch (error) { const stderr = error.stderr ? ` Stderr: ${error.stderr.toString()}` : ""; const stdout = error.stdout ? ` Stdout: ${error.stdout.toString()}` : ""; throw new Error(`Python execution failed: ${error.message}${stderr}${stdout}`); } finally { await unlink4(tmpFile); } } /** * Create Python wrapper script with entry point invocation. */ createWrapper(code, entryPoint) { return ` import sys import json try: context = json.loads(sys.argv[1]) target = context if isinstance(target, str): target = target.encode('utf-8') except: context = {} target = {} pass # User logic ${code} # Entry point invocation def _is_arg_error(e): msg = str(e).lower() return 'positional argument' in msg or 'required argument' in msg or 'takes' in msg and 'argument' in msg try: fn = None if '${entryPoint}' in dir(): fn = ${entryPoint} elif '${entryPoint}' in globals(): fn = globals()['${entryPoint}'] if fn is not None: try: res = fn(context) except TypeError as e: if not _is_arg_error(e): raise try: res = fn(target) except TypeError as e2: if not _is_arg_error(e2): raise res = fn() print(json.dumps(res)) else: if 'result' in dir() or 'result' in globals(): print(json.dumps(result if 'result' in dir() else globals().get('result'))) else: print(json.dumps(None)) except Exception as e: print(json.dumps({"error": str(e)})) sys.exit(1) `; } }; // src/ptr/node/runtimes/binary.ts import * as path4 from "path"; var BinaryRuntime = class { async execute(binaryPath, context, config, chapterDir) { const fullPath = path4.resolve(chapterDir, binaryPath); const contextStr = typeof context === "string" ? context : JSON.stringify(context); const { stdout } = await execFile2(fullPath, [contextStr]); return parseOutput(stdout); } }; // src/ptr/node/runtimes/wasm.ts import * as path5 from "path"; import * as child_process3 from "child_process"; var WasmRuntime = class { async execute(wasmPath, context, config, chapterDir) { const fullPath = path5.resolve(chapterDir, wasmPath); const contextStr = typeof context === "string" ? context : JSON.stringify(context ?? {}); return new Promise((resolve6, reject) => { const proc = child_process3.spawn("wasmtime", [fullPath, contextStr]); proc.stdin.end(); let stdout = ""; let stderr = ""; proc.stdout.on("data", (data) => { stdout += data.toString("utf-8"); }); proc.stderr.on("data", (data) => { stderr += data.toString("utf-8"); }); proc.on("error", (err) => { reject(err); }); proc.on("close", (code) => { if (code !== 0) { if (stderr.includes("command not found") || stderr.includes("wasmtime: not found")) { return reject(new Error("WASM runtime requires wasmtime CLI. Install it, e.g. with: brew install wasmtime")); } return reject(new Error(`wasmtime exited with code ${code}: ${stderr || stdout}`)); } const out = stdout.trim(); if (!out) { return resolve6(void 0); } const numeric = out.trim(); if (numeric && !Number.isNaN(Number(numeric))) { return resolve6(Number(numeric)); } try { return resolve6(JSON.parse(out)); } catch { return resolve6(out); } }); }); } }; // src/ptr/node/runtimes/lean.ts import * as fs4 from "fs"; import * as path6 from "path"; import * as util4 from "util"; var writeFile6 = util4.promisify(fs4.writeFile); var leanCacheDir = null; var leanSourceCache = /* @__PURE__ */ new Map(); function ensureLeanCacheDir() { if (!leanCacheDir) { leanCacheDir = path6.resolve(process.cwd(), ".lean_cache"); if (!fs4.existsSync(leanCacheDir)) { fs4.mkdirSync(leanCacheDir, { recursive: true }); } } return leanCacheDir; } async function getCachedLeanSource(code) { const crypto = await import("crypto"); const hash = crypto.createHash("sha256").update(code).digest("hex"); if (leanSourceCache.has(hash)) { return leanSourceCache.get(hash); } const cacheDir = ensureLeanCacheDir(); const filePath = path6.join(cacheDir, `${hash}.lean`); if (!fs4.existsSync(filePath)) { await writeFile6(filePath, code); } leanSourceCache.set(hash, filePath); return filePath; } var LeanRuntime = class { async execute(code, context, config) { const contextStr = typeof context === "string" ? context : JSON.stringify(context); const sourcePath = await getCachedLeanSource(code); const os = await import("os"); const elanPath = path6.join(os.homedir(), ".elan", "bin", "elan"); let stdout; if (fs4.existsSync(elanPath)) { const result = await execFile2(elanPath, [ "run", "leanprover/lean4:v4.25.2", "lean", "--run", sourcePath, contextStr ]); stdout = result.stdout; } else { const result = await execFile2("lean", ["--run", sourcePath, contextStr]); stdout = result.stdout; } return parseOutput(stdout); } }; // src/ptr/node/runtimes/loader.ts import * as fs5 from "fs"; import * as path7 from "path"; var LoaderRuntime = class { /** * Loader runtime: load files using the standardized loadFileToCollection utility. * Returns unified metrics and results matching Python's loader builtin. */ async execute(_code, context, config) { const { loadFileToCollection } = await import("./Loader-NRHGDQBD.js"); const ctx = context; const loaderParams = extractLoaderParams(ctx, { sourceDir: "test_data", dbPath: ":memory:" }); const { sourceDir, recursive, dbPath } = loaderParams; const projectRoot = findProjectRoot(); const sourcePath = path7.isAbsolute(sourceDir) ? sourceDir : path7.join(projectRoot, sourceDir); if (!fs5.existsSync(sourcePath)) { return { success: false, error: `Source directory not found: ${sourceDir}` }; } try { let resolvedDbPath = dbPath || ":memory:"; if (dbPath && dbPath !== ":memory:" && !path7.isAbsolute(dbPath)) { resolvedDbPath = path7.join(projectRoot, dbPath); const dbDir = path7.dirname(resolvedDbPath); if (!fs5.existsSync(dbDir)) { fs5.mkdirSync(dbDir, { recursive: true }); } } if (resolvedDbPath !== ":memory:" && fs5.existsSync(resolvedDbPath)) { try { fs5.unlinkSync(resolvedDbPath); const walPath = resolvedDbPath + "-wal"; const shmPath = resolvedDbPath + "-shm"; if (fs5.existsSync(walPath)) fs5.unlinkSync(walPath); if (fs5.existsSync(shmPath)) fs5.unlinkSync(shmPath); } catch (e) { } } const engine = new SqliteNodeEngine(resolvedDbPath); const collection = new CardCollection(engine); const loaderResult = await loadFileToCollection(sourcePath, collection, { recursive, includeProblematic: false }); const response = { success: true, metrics: { total_files: loaderResult.metrics.filesCount, total_directories: loaderResult.metrics.directoriesCount, directory_levels: loaderResult.metrics.directoryLevels, total_size_bytes: loaderResult.results.reduce((acc, r) => acc + (r.size || 0), 0), duration_ms: 0 }, files: loaderResult.results.map((r) => ({ hash: r.hash, filename: r.filename, content_type: r.contentType, size_bytes: r.size, path: r.filePath })) }; await engine.close(); return response; } catch (err) { return { success: false, error: err instanceof Error ? err.message : String(err) }; } } }; var CollectionLoaderRuntime = class { /** * Collection loader runtime: ingest files into a CardCollection and return a normalized ingest report. */ async execute(_code, context, config) { const ctx = context; const loaderParams = extractLoaderParams(ctx, { sourceDir: "chapters/chapter_04_load_dir/test_data", dbPath: "data/loader_clm.db" }); const { sourceDir, recursive, dbPath = "data/loader_clm.db" } = loaderParams; const projectRoot = findProjectRoot(); const sourcePath = path7.isAbsolute(sourceDir) ? sourceDir : path7.join(projectRoot, sourceDir); if (!fs5.existsSync(sourcePath)) { return { success: false, error: `Source directory not found: ${sourceDir}` }; } const resolvedDbPath = path7.isAbsolute(dbPath) ? dbPath : path7.join(projectRoot, dbPath); try { const engine = new SqliteNodeEngine(resolvedDbPath); const collection = new CardCollection(engine); const files = listFiles(sourcePath, recursive); let ingested = 0; let skipped = 0; const errors = []; const startTime = Date.now(); for (const filePath of files) { try { const content = fs5.readFileSync(filePath); const card = await MCard.create(new Uint8Array(content)); await collection.add(card); ingested += 1; } catch (err) { skipped += 1; errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`); } } const metrics = computeTimingMetrics(startTime, ingested); return { success: true, report: { total_files: files.length, ingested, skipped, errors }, ingest_metrics: { db_path: resolvedDbPath, time_s: metrics.time_s, files_per_sec: metrics.files_per_sec } }; } catch (err) { return { success: false, error: err instanceof Error ? err.message : String(err) }; } } }; // src/ptr/node/runtimes/factory.ts var _LLMRuntime = null; var _LambdaRuntime = null; var _NetworkRuntime = null; var RuntimeFactory = class { /** * Get availability status of all runtimes. */ static async getAvailableRuntimes() { const runtimes = ["javascript", "python", "rust", "c", "wasm", "lean", "llm"]; const status = {}; for (const r of runtimes) { try { if (r === "javascript") { status[r] = true; } else if (r === "python") { status[r] = await checkCommand("python3"); } else if (r === "rust") { status[r] = await checkCommand("cargo"); } else if (r === "c") { const gccOk = await checkCommand("gcc"); status[r] = gccOk || await checkCommand("clang"); } else if (r === "lean") { status[r] = await checkCommand("lean"); } else if (r === "wasm") { status[r] = typeof WebAssembly !== "undefined"; } else if (r === "llm") { const { OllamaProvider } = await import("./OllamaProvider-MQXSXLCA.js"); const provider = new OllamaProvider(null, 5); status[r] = await provider.validate_connection(); } else { status[r] = false; } } catch (e) { status[r] = false; } } return status; } /** * Get a runtime instance by name (synchronous for most runtimes). * * Note: For llm/lambda/network runtimes, this returns a wrapper that * lazily initializes. Use getRuntimeAsync() for guaranteed initialization. */ static getRuntime(runtimeName, options) { switch (runtimeName) { case "javascript": return new JavaScriptRuntime(); case "python": return new PythonRuntime(); case "rust": case "c": return new BinaryRuntime(); case "wasm": return new WasmRuntime(); case "lean": return new LeanRuntime(); case "loader": return new LoaderRuntime(); case "collection_loader": return new CollectionLoaderRuntime(); case "llm": return { async execute(code, context, config) { if (!_LLMRuntime) { const mod = await import("./LLMRuntime-BKB3CLXM.js"); _LLMRuntime = mod.LLMRuntime; } const runtime = new _LLMRuntime(); return runtime.execute(code, context, config); } }; case "lambda": if (!options?.collection) { throw new Error("Lambda runtime requires a CardCollection in options"); } const collection = options.collection; return { async execute(code, context, config) { if (!_LambdaRuntime) { const mod = await import("./LambdaRuntime-73BSWP2G.js"); _LambdaRuntime = mod.LambdaRuntime; } const runtime = new _LambdaRuntime(collection); return runtime.execute(code, context, config); } }; case "network": const netCollection = options?.collection; return { async execute(code, context, config, chapterDir) { if (!_NetworkRuntime) { const mod = await import("./NetworkRuntime-LC7SUAOF.js"); _NetworkRuntime = mod.NetworkRuntime; } const runtime = new _NetworkRuntime(netCollection); return runtime.execute(code, context, config, chapterDir || ""); } }; default: throw new Error(`Unknown runtime: ${runtimeName}`); } } /** * Get a runtime instance asynchronously (for runtimes that need dynamic imports). */ static async getRuntimeAsync(runtimeName, options) { switch (runtimeName) { case "llm": { const { LLMRuntime } = await import("./LLMRuntime-BKB3CLXM.js"); return new LLMRuntime(); } case "lambda": { if (!options?.collection) { throw new Error("Lambda runtime requires a CardCollection in options"); } const { LambdaRuntime: LambdaRuntime2 } = await import("./LambdaRuntime-73BSWP2G.js"); return new LambdaRuntime2(options.collection); } case "network": { const { NetworkRuntime } = await import("./NetworkRuntime-LC7SUAOF.js"); return new NetworkRuntime(options?.collection); } default: return this.getRuntime(runtimeName, options); } } }; // src/ptr/lambda/LambdaTerm.ts function mkVar(name) { return { tag: "Var", name }; } function mkAbs(param, bodyHash) { return { tag: "Abs", param, body: bodyHash }; } function mkApp(funcHash, argHash) { return { tag: "App", func: funcHash, arg: argHash }; } function serializeTerm(term) { return JSON.stringify(term); } function deserializeTerm(content) { const parsed = JSON.parse(content); if (!parsed.tag) { throw new Error("Invalid Lambda term: missing tag"); } switch (parsed.tag) { case "Var": if (typeof parsed.name !== "string") { throw new Error("Invalid Var term: name must be string"); } return mkVar(parsed.name); case "Abs": if (typeof parsed.param !== "string" || typeof parsed.body !== "string") { throw new Error("Invalid Abs term: param and body must be strings"); } return mkAbs(parsed.param, parsed.body); case "App": if (typeof parsed.func !== "string" || typeof parsed.arg !== "string") { throw new Error("Invalid App term: func and arg must be strings"); } return mkApp(parsed.func, parsed.arg); default: throw new Error(`Unknown Lambda term tag: ${parsed.tag}`); } } async function termToMCard(term) { return MCard.create(serializeTerm(term)); } function mcardToTerm(mcard) { return deserializeTerm(mcard.getContentAsText()); } var termCache = /* @__PURE__ */ new Map(); async function storeTerm(collection, term) { const mcard = await termToMCard(term); termCache.set(mcard.hash, term); await collection.add(mcard); return mcard.hash; } async function loadTerm(collection, hash) { const cached = termCache.get(hash); if (cached) return cached; const mcard = await collection.get(hash); if (!mcard) return null; const term = mcardToTerm(mcard); termCache.set(hash, term); return term; } async function termExists(collection, hash) { const mcard = await collection.get(hash); return mcard !== null; } function prettyPrintShallow(term) { switch (term.tag) { case "Var": return term.name; case "Abs": return `\u03BB${term.param}.\u3008${term.body.substring(0, 8)}\u2026\u3009`; case "App": return `(\u3008${term.func.substring(0, 8)}\u2026\u3009 \u3008${term.arg.substring(0, 8)}\u2026\u3009)`; } } async function prettyPrintDeep(collection, hash) { const term = await loadTerm(collection, hash); if (!term) return `\u3008missing:${hash.substring(0, 8)}\u2026\u3009`; switch (term.tag) { case "Var": return term.name; case "Abs": const body = await prettyPrintDeep(collection, term.body); return `(\u03BB${term.param}.${body})`; case "App": const func = await prettyPrintDeep(collection, term.func); const arg = await prettyPrintDeep(collection, term.arg); return `(${func} ${arg})`; } } function isVar(term) { return term.tag === "Var"; } function isAbs(term) { return term.tag === "Abs"; } function isApp(term) { return term.tag === "App"; } // src/ptr/lambda/FreeVariables.ts var freeVarCache = /* @__PURE__ */ new Map(); function freeVariables(collection, termHash) { return IO.of(async () => { const result = await computeFreeVars(collection, termHash, /* @__PURE__ */ new Set()); return result; }); } async function computeFreeVars(collection, termHash, visited) { const cached = freeVarCache.get(termHash); if (cached) { return Maybe.just(new Set(cached)); } if (visited.has(termHash)) { return Maybe.just(/* @__PURE__ */ new Set()); } visited.add(termHash); const term = await loadTerm(collection, termHash); if (!term) { return Maybe.nothing(); } const result = await freeVarsOfTerm(collection, term, visited); freeVarCache.set(termHash, new Set(result)); return Maybe.just(result); } async function freeVarsOfTerm(collection, term, visited) { switch (term.tag) { case "Var": return /* @__PURE__ */ new Set([term.name]); case "Abs": { const bodyFV = await computeFreeVars(collection, term.body, visited); if (bodyFV.isNothing) { throw new Error(`Body term not found: ${term.body}`); } const result = new Set(bodyFV.value); result.delete(term.param); return result; } case "App": { const funcFV = await computeFreeVars(collection, term.func, visited); const argFV = await computeFreeVars(collection, term.arg, visited); if (funcFV.isNothing) { throw new Error(`Function term not found: ${term.func}`); } if (argFV.isNothing) { throw new Error(`Argument term not found: ${term.arg}`); } return union(funcFV.value, argFV.value); } } } function boundVariables(collection, termHash) { return IO.of(async () => { return computeBoundVars(collection, termHash, /* @__PURE__ */ new Set()); }); } async function computeBoundVars(collection, termHash, visited) { if (visited.has(termHash)) { return Maybe.just(/* @__PURE__ */ new Set()); } visited.add(termHash); const term = await loadTerm(collection, termHash); if (!term) { return Maybe.nothing(); } switch (term.tag) { case "Var": return Maybe.just(/* @__PURE__ */ new Set()); case "Abs": { const bodyBV = await computeBoundVars(collection, term.body, visited); if (bodyBV.isNothing) return bodyBV; const result = new Set(bodyBV.value); result.add(term.param); return Maybe.just(result); } case "App": { const funcBV = await computeBoundVars(collection, term.func, visited); const argBV = await computeBoundVars(collection, term.arg, visited); if (funcBV.isNothing || argBV.isNothing) { return Maybe.nothing(); } return Maybe.just(union(funcBV.value, argBV.value)); } } } function isFreeIn(collection, variable, termHash) { return freeVariables(collection, termHash).map((maybeFV) => { if (maybeFV.isNothing) return false; return maybeFV.value.has(variable); }); } function isClosed(collection, termHash) { return freeVariables(collection, termHash).map((maybeFV) => { if (maybeFV.isNothing) return false; return maybeFV.value.size === 0; }); } function generateFresh(base, avoid) { if (!avoid.has(base)) { return base; } let candidate = base + "'"; while (avoid.has(candidate)) { candidate += "'"; } return candidate; } async function generateFreshFor(collection, base, termHash) { const fvResult = await freeVariables(collection, termHash).run(); const bvResult = await boundVariables(collection, termHash).run(); const avoid = /* @__PURE__ */ new Set(); if (fvResult.isJust) { for (const v of fvResult.value) avoid.add(v); } if (bvResult.isJust) { for (const v of bvResult.value) avoid.add(v); } return generateFresh(base, avoid); } function union(a, b) { const result = new Set(a); for (const item of b) { result.add(item); } return result; } function difference(a, b) { const result = /* @__PURE__ */ new Set(); for (const item of a) { if (!b.has(item)) { result.add(item); } } return result; } function intersection(a, b) { const result = /* @__PURE__ */ new Set(); for (const item of a) { if (b.has(item)) { result.add(item); } } return result; } // src/ptr/lambda/AlphaConversion.ts function alphaRename(collection, absHash, newParam) { return IO.of(async () => { const term = await loadTerm(collection, absHash); if (!term) { return Either.left(`Term not found: ${absHash}`); } if (!isAbs(term)) { return Either.left( `Alpha conversion only applies to abstractions, got ${term.tag}` ); } if (term.param === newParam) { return Either.right(absHash); } const bodyFV = await freeVariables(collection, term.body).run(); if (bodyFV.isJust && bodyFV.value.has(newParam)) { return Either.left( `Cannot \u03B1-rename to '${newParam}': would capture free variable in body` ); } const newBodyHash = await substituteVar( collection, term.body, term.param, newParam ); const newAbs = mkAbs(newParam, newBodyHash); const resultHash = await storeTerm(collection, newAbs); return Either.right(resultHash); }); } async function substituteVar(collection, termHash, oldVar, newVar) { const term = await loadTerm(collection, termHash); if (!term) { throw new Error(`Term not found: ${termHash}`); } switch (term.tag) { case "Var": if (term.name === oldVar) { const newTerm = mkVar(newVar); return storeTerm(collection, newTerm); } return termHash; case "Abs": if (term.param === oldVar) { return termHash; } const newBody = await substituteVar(collection, term.body, oldVar, newVar); if (newBody === term.body) { return termHash; } const newAbs = mkAbs(term.param, newBody); return storeTerm(collection, newAbs); case "App": const newFunc = await substituteVar(collection, term.func, oldVar, newVar); const newArg = await substituteVar(collection, term.arg, oldVar, newVar); if (newFunc === term.func && newArg === term.arg) { return termHash; } const newApp = mkApp(newFunc, newArg); return storeTerm(collection, newApp); } } function alphaEquivalent(collection, hash1, hash2) { return IO.of(async () => { if (hash1 === hash2) { return Either.right(true); } const term1 = await loadTerm(collection, hash1); const term2 = await loadTerm(collection, hash2); if (!term1) return Either.left(`Term not found: ${hash1}`); if (!term2) return Either.left(`Term not found: ${hash2}`); const equiv = await checkAlphaEquiv(collection, term1, term2, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()); return Either.right(equiv); }); } async function checkAlphaEquiv(collection, term1, term2, rename1, rename2) { if (term1.tag !== term2.tag) { return false; } switch (term1.tag) { case "Var": { const t2 = term2; const idx1 = rename1.get(term1.name); const idx2 = rename2.get(t2.name); if (idx1 !== void 0 && idx2 !== void 0) { return idx1 === idx2; } if (idx1 === void 0 && idx2 === void 0) { return term1.name === t2.name; } return false; } case "Abs": { const t2 = term2; const depth = rename1.size; const newRename1 = new Map(rename1); const newRename2 = new Map(rename2); newRename1.set(term1.param, depth); newRename2.set(t2.param, depth); const body1 = await loadTerm(collection, term1.body); const body2 = await loadTerm(collection, t2.body); if (!body1 || !body2) return false; return checkAlphaEquiv(collection, body1, body2, newRename1, newRename2); } case "App": { const t2 = term2; const func1 = await loadTerm(collection, term1.func); const func2 = await loadTerm(collection, t2.func); const arg1 = await loadTerm(collection, term1.arg); const arg2 = await loadTerm(collection, t2.arg); if (!func1 || !func2 || !arg1 || !arg2) return false; const funcEquiv = await checkAlphaEquiv(collection, func1, func2, rename1, rename2); if (!funcEquiv) return false; return checkAlphaEquiv(collection, arg1, arg2, rename1, rename2); } } } function alphaNormalize(collection, termHash) { return IO.of(async () => { const result = await normalizeWithDepth(collection, termHash, /* @__PURE__ */ new Map(), 0); if (result === null) { return Either.left(`Term not found: ${termHash}`); } return Either.right(result); }); } async function normalizeWithDepth(collection, termHash, bindings, depth) { const term = await loadTerm(collection, termHash); if (!term) return null; switch (term.tag) { case "Var": { const canonicalName = bindings.get(term.name); if (canonicalName) { const newVar = mkVar(canonicalName); return storeTerm(collection, newVar); } return termHash; } case "Abs": { const canonicalName = `a${depth}`; const newBindings = new Map(bindings); newBindings.set(term.param, canonicalName); const newBody = await normalizeWithDepth(collection, term.body, newBindings, depth + 1); if (newBody === null) return null; const newAbs = mkAbs(canonicalName, newBody); return storeTerm(collection, newAbs); } case "App": { const newFunc = await normalizeWithDepth(collection, term.func, bindings, depth); const newArg = await normalizeWithDepth(collection, term.arg, bindings, depth); if (newFunc === null || newArg === null) return null; if (newFunc === term.func && newArg === term.arg) { return termHash; } const newApp = mkApp(newFunc, newArg); return storeTerm(collection, newApp); } } } // src/ptr/lambda/BetaReduction.ts function isRedex(collection, termHash) { return IO.of(async () => { const term = await loadTerm(collection, termHash); if (!term || !isApp(term)) return false; const func = await loadTerm(collection, term.func); return func !== null && isAbs(func); }); } function findLeftmostRedex(collection, termHash) { return IO.of(async () => { return findRedexNormalOrder(collection, termHash); }); } async function findRedexNormalOrder(collection, termHash) { const term = await loadTerm(collection, termHash); if (!term) return Maybe.nothing(); switch (term.tag) { case "Var": return Maybe.nothing(); case "Abs": return findRedexNormalOrder(collection, term.body); case "App": { const func = await loadTerm(collection, term.func); if (func && isAbs(func)) { return Maybe.just(termHash); } const funcRedex = await findRedexNormalOrder(collection, term.func); if (funcRedex.isJust) return funcRedex; return findRedexNormalOrder(collection, term.arg); } } } function findInnermostRedex(collection, termHash) { return IO.of(async () => { return findRedexApplicativeOrder(collection, termHash); }); } async function findRedexApplicativeOrder(collection, termHash) { const term = await loadTerm(collection, termHash); if (!term) return Maybe.nothing(); switch (term.tag) { case "Var": return Maybe.nothing(); case "Abs": return findRedexApplicativeOrder(collection, term.body); case "App": { const funcRedex = await findRedexApplicativeOrder(collection, term.func); if (funcRedex.isJust) return funcRedex; const argRedex = await findRedexApplicativeOrder(collection, term.arg); if (argRedex.isJust) return argRedex; const func = await loadTerm(collection, term.func); if (func && isAbs(func)) { return Maybe.just(termHash); } return Maybe.nothing(); } } } function betaReduce(collection, redexHash) { return IO.of(async () => { const app = await loadTerm(collection, redexHash); if (!app) { return Either.left(`Term not found: ${redexHash}`); } if (!isApp(app)) { return Either.left( `Beta reduction requires application term, got ${app.tag}` ); } const func = await loadTerm(collection, app.func); if (!func) { return Either.left(`Function not found: ${app.func}`); } if (!isAbs(func)) { return Either.left( `Not a beta-redex: function is ${func.tag}, not abstraction` ); } const resultHash = await substituteWithCapture( collection, func.body, // M func.param, // x app.arg // N (as hash) ); return Either.right(resultHash); }); } async function substituteWithCapture(collection, termHash, variable, replacementHash) { const term = await loadTerm(collection, termHash); if (!term) { throw new Error(`Term not found: ${termHash}`); } switch (term.tag) { case "Var": if (term.name === variable) { return replacementHash; } return termHash; case "Abs": { if (term.param === variable) { return termHash; } const replacementFV = await freeVariables(collection, replacementHash).run(); if (replacementFV.isJust && replacementFV.value.has(term.param)) { const allVars = new Set(replacementFV.value); const bodyFV = await freeVariables(collection, term.body).run(); if (bodyFV.isJust) { for (const v of bodyFV.value) allVars.add(v); } allVars.add(variable); const freshName = generateFresh(term.param, allVars); const renameResult = await alphaRename(collection, termHash, freshName).run(); if (renameResult.isLeft) { throw new Error(`Alpha rename failed: ${renameResult.left}`); } return substituteWithCapture( collection, renameResult.right, variable, replacementHash ); } const newBody = await substituteWithCapture( collection, term.body, variable, replacementHash ); if (newBody === term.body) { return termHash; } const newAbs = mkAbs(term.param, newBody); return storeTerm(collection, newAbs); } case "App": { const newFunc = await substituteWithCapture( collection, term.func, variable, replacementHash ); const newArg = await substituteWithCapture( collection, term.arg, variable, replacementHash ); if (newFunc === term.func && newArg === term.arg) { return termHash; } const newApp = mkApp(newFunc, newArg); return storeTerm(collection, newApp); } } } function reduceStep(collection, termHash, strategy = "normal") { return IO.of(async () => { let redexMaybe; switch (strategy) { case "normal": case "lazy": redexMaybe = await findRedexNormalOrder(collection, termHash); break; case "applicative": redexMaybe = await findRedexApplicativeOrder(collection, termHash); break; } if (redexMaybe.isNothing) { return Maybe.nothing(); } const redexHash = redexMaybe.value; if (redexHash === termHash) { const result = await betaReduce(collection, redexHash).run(); if (result.isLeft) { throw new Error(result.left); } return Maybe.just(result.right); } const reduced = await betaReduce(collection, redexHash).run(); if (reduced.isLeft) { throw new Error(reduced.left); } const rebuilt = await rebuildWithReduced( collection, termHash, redexHash, reduced.right ); return Maybe.just(rebuilt); }); } async function rebuildWithReduced(collection, termHash, redexHash, reducedHash) { if (termHash === redexHash) { return reducedHash; } const term = await loadTerm(collection, termHash); if (!term) throw new Error(`Term not found: ${termHash}`); switch (term.tag) { case "Var": return termHash; case "Abs": { const newBody = await rebuildWithReduced( collection, term.body, redexHash, reducedHash ); if (newBody === term.body) return termHash; const newAbs = mkAbs(term.param, newBody); return storeTerm(collection, newAbs); } case "App": { const newFunc = await rebuildWithReduced( collection, term.func, redexHash, reducedHash ); const newArg = await rebuildWithReduced( collection, term.arg, redexHash, reducedHash ); if (newFunc === term.func && newArg === term.arg) return termHash; const newApp = mkApp(newFunc, newArg); return storeTerm(collection, newApp); } } } function normalize(collection, termHash, strategy = "normal", maxSteps = 1e3, maxTimeMs, onStep) { return IO.of(async () => { const startTime = Date.now(); let current = termHash; let steps = 0; const path8 = [termHash]; while (steps < maxSteps) { if (maxTimeMs !== void 0 && Date.now() - startTime > maxTimeMs) { return Either.left( `Normalization exceeded time budget of ${maxTimeMs}ms after ${steps} steps` ); } const stepResult = await reduceStep(collection, current, strategy).run(); if (stepResult.isNothing) { return Either.right({ normalForm: current, steps, reductionPath: path8 }); } current = stepResult.value; path8.push(current); steps++; if (onStep) { await onStep(steps, current); } } return Either.left( `Normalization did not terminate within ${maxSteps} steps (possible infinite loop)` ); }); } function isNormalForm(collection, termHash) { return findLeftmostRedex(collection, termHash).map((m) => m.isNothing); } function hasNormalForm(collection, termHash, maxSteps = 1e3) { return normalize(collection, termHash, "normal", maxSteps).map((result) => result.isRight); } // src/ptr/lambda/EtaConversion.ts function isEtaRedex(collection, termHash) { return IO.of(async () => { const term = await loadTerm(collection, termHash); if (!term || !isAbs(term)) return false; const body = await loadTerm(collection, term.body); if (!body || !isApp(body)) return false; const arg = await loadTerm(collection, body.arg); if (!arg || !isVar(arg) || arg.name !== term.param) return false; const funcFV = await freeVariables(collection, body.func).run(); if (funcFV.isNothing) return false; return !funcFV.value.has(term.param); }); } function etaReduce(collection, termHash) { return IO.of(async () => { const term = await loadTerm(collection, termHash); if (!term) return Maybe.nothing(); if (!isAbs(term)) return Maybe.nothing(); const body = await loadTerm(collection, term.body); if (!body) return Maybe.nothing(); if (!isApp(body)) return Maybe.nothing(); const arg = await loadTerm(collection, body.arg); if (!arg) return Maybe.nothing(); if (!isVar(arg) || arg.name !== term.param) { return Maybe.nothing(); } const funcFV = await freeVariables(collection, body.func).run(); if (funcFV.isNothing) return Maybe.nothing(); if (funcFV.value.has(term.param)) { return Maybe.nothing(); } return Maybe.just(body.func); }); } function etaReduceE(collection, termHash) { return etaReduce(collection, termHash).map((maybe) => { if (maybe.isNothing) { return Either.left("Not an \u03B7-redex"); } return Either.right(maybe.value); }); } function etaExpand(collection, termHash, baseName = "x") { return IO.of(async () => { const freshVar = await generateFreshFor(collection, baseName, termHash); const varTerm = mkVar(freshVar); const varHash = await storeTerm(collection, varTerm); const appTerm = mkApp(termHash, varHash); const appHash = await storeTerm(collection, appTerm); const absTerm = mkAbs(freshVar, appHash); const absHash = await storeTerm(collection, absTerm); return absHash; }); } function etaEquivalent(collection, hash1, hash2) { return IO.of(async () => { if (hash1 === hash2) return true; const reduced1 = await etaReduceDeep(collection, hash1); const reduced2 = await etaReduceDeep(collection, hash2); return reduced1 === reduced2; }); } async function etaReduceDeep(collection, termHash) { const term = await loadTerm(collection, termHash); if (!term) return termHash; switch (term.tag) { case "Var": return termHash; case "Abs": { const etaResult = await etaReduce(collection, termHash).run(); if (etaResult.isJust) { return etaReduceDeep(collection, etaResult.value); } const newBody = await etaReduceDeep(collection, term.body); if (newBody === term.body) return termHash; const newAbs = mkAbs(term.param, newBody); return storeTerm(collection, newAbs); } case "App": { const newFunc = await etaReduceDeep(collection, term.func); const newArg = await etaReduceDeep(collection, term.arg); if (newFunc === term.func && newArg === term.arg) return termHash; const newApp = mkApp(newFunc, newArg); return storeTerm(collection, newApp); } } } function etaNormalize(collection, termHash) { return IO.of(() => etaReduceDeep(collection, termHash)); } function findEtaRedexes(collection, termHash) { return IO.of(async () => { const redexes = []; await collectEtaRedexes(collection, termHash, redexes); return redexes; }); } async function collectEtaRedexes(collection, termHash, redexes) { const term = await loadTerm(collection, termHash); if (!term) return; switch (term.tag) { case "Var": break; case "Abs": { const isRedex3 = await isEtaRedex(collection, termHash).run(); if (isRedex3) { redexes.push(termHash); } await collectEtaRedexes(collection, term.body, redexes); break; } case "App": { await collectEtaRedexes(collection, term.func, redexes); await collectEtaRedexes(collection, term.arg, redexes); break; } } } // src/ptr/lambda/IOEffects.ts var IOEffectsHandler = class { config; events = []; constructor(config = {}) { this.config = { enabled: config.enabled ?? false, console: config.console ?? true, network: config.network, onStep: config.onStep ?? false, onComplete: config.onComplete ?? true, onError: config.onError ?? true, format: config.format ?? "minimal" }; } /** * Check if IO effects are enabled */ isEnabled() { return this.config.enabled; } /** * Emit a step event */ async emitStep(stepNumber, termHash, prettyPrint) { if (!this.config.enabled || !this.config.onStep) return; const event = { type: "step", stepNumber, termHash, prettyPrint, timestamp: /* @__PURE__ */ new Date() }; this.events.push(event); await this.dispatch(event); } /** * Emit a completion event */ async emitComplete(normalForm, prettyPrint, totalSteps, reductionPath) { if (!this.config.enabled || !this.config.onComplete) return; const event = { type: "complete", normalForm, prettyPrint, totalSteps, reductionPath, timestamp: /* @__PURE__ */ new Date() }; this.events.push(event); await this.dispatch(event); } /** * Emit an error event */ async emitError(message, partialSteps, lastTermHash) { if (!this.config.enabled || !this.config.onError) return; const event = { type: "error", message, partialSteps, lastTermHash, timestamp: /* @__PURE__ */ new Date() }; this.events.push(event); await this.dispatch(event); } /** * Get all collected events */ getEvents() { return [...this.events]; } /** * Clear collected events */ clearEvents() { this.events = []; } /** * Dispatch event to configured outputs */ async dispatch(event) { const formatted = this.format(event); if (this.config.console) { this.logToConsole(event, formatted); } if (this.config.network?.enabled && this.config.network.endpoint) { await thi