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
JavaScript
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