UNPKG

mcard-js

Version:

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

2,310 lines 73.7 kB
import {
  CardCollection,
  Maybe
} from "./chunk-YFULZTKP.js";
import {
  SqliteNodeEngine
} from "./chunk-KRKK3KP6.js";
import {
  MCard
} from "./chunk-PW4XS7M3.js";
import {
  IO
} from "./chunk-MPMRBT5R.js";
import {
  Either
} from "./chunk-2KADE3SE.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-W72QZ3T7.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-TNBC4JCB.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-4YILF3G7.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-L7P4VCGK.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-TNBC4JCB.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-4YILF3G7.js");
        return new LambdaRuntime2(options.collection);
      }
      case "network": {
        const { NetworkRuntime } = await import("./NetworkRuntime-L7P4VCGK.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 this.sendToNetwork(event, formatted);
    }
  }
  /**
   * Format event for output
   */
  format(event) {
    switch (this.config.format) {
      case "json":
        return JSON.stringify(event);
      case "verbose":
        return this.formatVerbose(event);
      case "minimal":
      default:
        return this.formatMinimal(event);
    }
  }
  formatMinimal(event) {
    switch (event.type) {
      case "step":
        return `[IO:step ${event.stepNumber}] ${event.prettyPrint}`;
      case "complete":
        return `[IO:complete] ${event.totalSteps} steps \u2192 ${event.prettyPrint}`;
      case "error":
        return `[IO:error] ${event.message}`;
    }
  }
  formatVerbose(event) {
    const ts = event.timestamp.toISOString();
    switch (event.type) {
      case "step":
        return [
          `\u250C\u2500 IO Effect: Reduction Step ${event.stepNumber}`,
          `\u2502  Time: ${ts}`,
          `\u2502  Hash: ${event.termHash.substring(0, 16)}...`,
          `\u2502  Term: ${event.prettyPrint}`,
          `\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
        ].join("\n");
      case "complete":
        return [
          `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
          `\u2551 IO Effect: Normalization Complete`,
          `\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
          `\u2551 Time: ${ts}`,
          `\u2551 Total Steps: ${event.totalSteps}`,
          `\u2551 Normal Form: ${event.prettyPrint}`,
          `\u2551 Hash: ${event.normalForm.substring(0, 16)}...`,
          `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`
        ].join("\n");
      case "error":
        return [
          `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
          `\u2551 IO Effect: ERROR`,
          `\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
          `\u2551 Time: ${ts}`,
          `\u2551 Message: ${event.message}`,
          `\u2551 Partial Steps: ${event.partialSteps}`,
          `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`
        ].join("\n");
    }
  }
  logToConsole(event, formatted) {
    const prefix = "\x1B[36m";
    const reset = "\x1B[0m";
    switch (event.type) {
      case "step":
        console.log(`${prefix}${formatted}${reset}`);
        break;
      case "complete":
        console.log(`${prefix}${formatted}${reset}`);
        break;
      case "error":
        console.error(`\x1B[31m${formatted}${reset}`);
        break;
    }
  }
  async sendToNetwork(event, formatted) {
    if (!this.config.network?.endpoint) return;
    try {
      const response = await fetch(this.config.network.endpoint, {
        method: this.config.network.method || "POST",
        headers: {
          "Content-Type": "application/json",
          ...this.config.network.headers
        },
        body: JSON.stringify({
          event: event.type,
          data: event,
          formatted
        })
      });
      if (!response.ok) {
        console.warn(`IO network effect failed: ${response.status}`);
      }
    } catch (err) {
      console.warn(`IO network effect error: ${err}`);
    }
  }
};
function createIOHandler(config) {
  if (!config || !config.io_effects) {
    return new IOEffectsHandler({ enabled: false });
  }
  const ioConfig = config.io_effects;
  return new IOEffectsHandler({
    enabled: ioConfig.enabled ?? false,
    console: ioConfig.console ?? true,
    network: ioConfig.network,
    onStep: ioConfig.on_step ?? ioConfig.onStep ?? false,
    onComplete: ioConfig.on_complete ?? ioConfig.onComplete ?? true,
    onError: ioConfig.on_error ?? ioConfig.onError ?? true,
    format: ioConfig.format ?? "minimal"
  });
}
var noopIOHandler = new IOEffectsHandler({ enabled: false });

// src/ptr/lambda/LambdaRuntime.ts
var LambdaRuntime = class {
  collection;
  constructor(collection) {
    this.collection = collection;
  }
  /**
   * Execute a Lambda operation
   * 
   * @param codeOrPath - For Lambda runtime, this is the term hash to operate on
   * @param context - Additional context (varies by operation)
   * @param config - Lambda configuration with operation type
   * @param chapterDir - Chapter directory (used for relative paths if needed)
   */
  async execute(codeOrPath, context, config, chapterDir) {
    let termHash = codeOrPath;
    const ctx = context && typeof context === "object" ? context : {};
    const operation = ctx.operation || config.process || config.action || config.operation || "normalize";
    const lambdaConfig = { ...config, operation };
    const strategy = lambdaConfig.strategy || ctx.strategy || "normal";
    const maxSteps = lambdaConfig.maxSteps || ctx.maxSteps || ctx.max_steps || 1e3;
    const maxTimeMs = lambdaConfig.maxTimeMs || ctx.maxTimeMs || ctx.max_time_ms || ctx.timeoutMs || ctx.timeout_ms;
    if (termHash && (termHash.includes("\\") || termHash.includes("\u03BB") || termHash.includes(" ") || termHash.includes("("))) {
      try {
        termHash = await parseLambdaExpression(this.collection, termHash);
      } catch (e) {
        return {
          success: false,
          error: `Failed to parse input expression: ${e instanceof Error ? e.message : String(e)}`
        };
      }
    }
    if (operation === "check-readiness") {
      return this.doCheckReadiness(ctx);
    }
    if (operation.startsWith("num-")) {
      return this.doNumericOp(operation, ctx);
    }
    if (operation === "http-request") {
      return this.doHttpRequest(ctx);
    }
    if (!termHash || termHash === "lambda-op" || termHash === "builtin") {
      const expression = ctx.expression || ctx.term;
      if (expression) {
        try {
          termHash = await parseLambdaExpression(this.collection, expression);
        } catch (e) {
          return { success: false, error: `Invalid expression in context: ${e}` };
        }
      } else if (operation !== "parse" && operation !== "build") {
        return { success: false, error: `Lambda operation ${operation} requires a term or expression` };
      }
    }
    try {
      switch (operation) {
        case "alpha":
          return this.doAlphaRename(termHash, lambdaConfig, ctx);
        case "beta":
          return this.doBetaReduce(termHash);
        case "eta-reduce":
          return this.doEtaReduce(termHash);
        case "eta-expand":
          return this.doEtaExpand(termHash, lambdaConfig, ctx);
        case "normalize":
          return this.doNormalize(termHash, { ...lambdaConfig, strategy, maxSteps, maxTimeMs });
        case "step":
          return this.doStep(termHash, { ...lambdaConfig, strategy });
        case "alpha-equiv":
          return this.doAlphaEquiv(termHash, lambdaConfig, ctx);
        case "eta-equiv":
          return this.doEtaEquiv(termHash, lambdaConfig, ctx);
        case "alpha-norm":
          return this.doAlphaNormalize(termHash);
        case "eta-norm":
          return this.doEtaNormalize(termHash);
        case "free-vars":
          return this.doFreeVars(termHash);
        case "is-closed":
          return this.doIsClosed(termHash);
        case "is-normal":
          return this.doIsNormal(termHash);
        case "parse":
          return this.doParse(ctx);
        case "pretty":
          return this.doPretty(termHash);
        case "build":
          return this.doBuild(ctx);
        case "church-to-int":
          return this.doChurchToInt(termHash);
        default:
          return {
            success: false,
            error: `Unknown Lambda operation: ${operation}`
          };
      }
    } catch (err) {
      return {
        success: false,
        error: err instanceof Error ? err.message : String(err)
      };
    }
  }
  // ─────────────────────────────────────────────────────────────────────────
  // Operation Implementations
  // ─────────────────────────────────────────────────────────────────────────
  async doAlphaRename(termHash, config, ctx) {
    const newName = config.newName || ctx.newName;
    if (!newName) {
      return { success: false, error: "Alpha rename requires newName parameter" };
    }
    const result = await alphaRename(this.collection, termHash, newName).run();
    if (result.isLeft) {
      return { success: false, error: result.left };
    }
    const pretty = await prettyPrintDeep(this.collection, result.right);
    return {
      success: true,
      result: result.right,
      termHash: result.right,
      prettyPrint: pretty
    };
  }
  async doBetaReduce(termHash) {
    const result = await betaReduce(this.collection, termHash).run();
    if (result.isLeft) {
      return { success: false, error: result.left };
    }
    const pretty = await prettyPrintDeep(this.collection, result.right);
    return {
      success: true,
      result: result.right,
      termHash: result.right,
      prettyPrint: pretty
    };
  }
  async doEtaReduce(termHash) {
    const result = await etaReduce(this.collection, termHash).run();
    if (result.isNothing) {
      return { success: false, error: "Not an \u03B7-redex" };
    }
    const pretty = await prettyPrintDeep(this.collection, result.value);
    return {
      success: true,
      result: result.value,
      termHash: result.value,
      prettyPrint: pretty
    };
  }
  async doEtaExpand(termHash, config, ctx) {
    const freshVar = config.freshVar || ctx.freshVar || "x";
    const result = await etaExpand(this.collection, termHash, freshVar).run();
    const pretty = await prettyPrintDeep(this.collection, result);
    return {
      success: true,
      result,
      termHash: result,
      prettyPrint: pretty
    };
  }
  async doNormalize(termHash, config) {
    const strategy = config.strategy || "normal";
    const maxSteps = config.maxSteps || 1e3;
    const maxTimeMs = config.maxTimeMs;
    const ioHandler = createIOHandler(config);
    const onStep = ioHandler.isEnabled() ? async (step, hash) => {
      const pretty2 = await prettyPrintDeep(this.collection, hash);
      await ioHandler.emitStep(step, hash, pretty2);
    } : void 0;
    const result = await normalize(
      this.collection,
      termHash,
      strategy,
      maxSteps,
      maxTimeMs,
      onStep
    ).run();
    if (result.isLeft) {
      if (ioHandler.isEnabled()) {
        await ioHandler.emitError(result.left, 0);
      }
      return { success: false, error: result.left };
    }
    const normResult = result.right;
    const pretty = await prettyPrintDeep(this.collection, normResult.normalForm);
    if (ioHandler.isEnabled()) {
      await ioHandler.emitComplete(
        normResult.normalForm,
        pretty,
        normResult.steps,
        normResult.reductionPath
      );
    }
    return {
      success: true,
      result: {
        normalForm: normResult.normalForm,
        steps: normResult.steps,
        reductionPath: normResult.reductionPath,
        ioEvents: ioHandler.getEvents()
        // Include IO events in result
      },
      termHash: normResult.normalForm,
      prettyPrint: pretty
    };
  }
  async doStep(termHash, config) {
    const strategy = config.strategy || "normal";
    const result = await reduceStep(this.collection, termHash, strategy).run();
    if (result.isNothing) {
      return {
        success: true,
        result: { alreadyNormal: true },
        termHash,
        prettyPrint: await prettyPrintDeep(this.collection, termHash)
      };
    }
    const pretty = await prettyPrintDeep(this.collection, result.value);
    return {
      success: true,
      result: result.value,
      termHash: result.value,
      prettyPrint: pretty
    };
  }
  async doAlphaEquiv(termHash, config, ctx) {
    const compareWith = config.compareWith || ctx.compareWith;
    if (!compareWith) {
      return { success: false, error: "Alpha equivalence check requires compareWith parameter" };
    }
    const result = await alphaEquivalent(this.collection, termHash, compareWith).run();
    if (result.isLeft) {
      return { success: false, error: result.left };
    }
    return {
      success: true,
      result: { equivalent: result.right }
    };
  }
  async doEtaEquiv(termHash, config, ctx) {
    const compareWith = config.compareWith || ctx.compareWith;
    if (!compareWith) {
      return { success: false, error: "Eta equivalence check requires compareWith parameter" };
    }
    const result = await etaEquivalent(this.collection, termHash, compareWith).run();
    return {
      success: true,
      result: { equivalent: result }
    };
  }
  async doAlphaNormalize(termHash) {
    const result = await alphaNormalize(this.collection, termHash).run();
    if (result.isLeft) {
      return { success: false, error: result.left };
    }
    const pretty = await prettyPrintDeep(this.collection, result.right);
    return {
      success: true,
      result: result.right,
      termHash: result.right,
      prettyPrint: pretty
    };
  }
  async doEtaNormalize(termHash) {
    const result = await etaNormalize(this.collection, termHash).run();
    const pretty = await prettyPrintDeep(this.collection, result);
    return {
      success: true,
      result,
      termHash: result,
      prettyPrint: pretty
    };
  }
  async doFreeVars(termHash) {
    const result = await freeVariables(this.collection, termHash).run();
    if (result.isNothing) {
      return { success: false, error: `Term not found: ${termHash}` };
    }
    return {
      success: true,
      result: { freeVariables: Array.from(result.value) }
    };
  }
  async doIsClosed(termHash) {
    const result = await isClosed(this.collection, termHash).run();
    return {
      success: true,
      result: { closed: result }
    };
  }
  async doIsNormal(termHash) {
    const result = await isNormalForm(this.collection, termHash).run();
    return {
      success: true,
      result: { normalForm: result }
    };
  }
  async doParse(ctx) {
    const expression = ctx.expression;
    if (!expression) {
      return { success: false, error: "Parse requires expression parameter" };
    }
    try {
      const hash = await parseLambdaExpression(this.collection, expression);
      const pretty = await prettyPrintDeep(this.collection, hash);
      return {
        success: true,
        result: hash,
        termHash: hash,
        prettyPrint: pretty
      };
    } catch (err) {
      return {
        success: false,
        error: `Parse error: ${err instanceof Error ? err.message : String(err)}`
      };
    }
  }
  async doPretty(termHash) {
    const pretty = await prettyPrintDeep(this.collection, termHash);
    return {
      success: true,
      result: pretty,
      prettyPrint: pretty
    };
  }
  async doBuild(ctx) {
    const spec = ctx.term;
    if (!spec) {
      return { success: false, error: "Build requires term specification" };
    }
    const hash = await storeTerm(this.collection, spec);
    const pretty = await prettyPrintDeep(this.collection, hash);
    return {
      success: true,
      result: hash,
      termHash: hash,
      prettyPrint: pretty
    };
  }
  /**
   * Decode a Church numeral to a regular integer.
   * Church numeral n = λf.λx.f^n(x) where f is applied n times.
   * 
   * Algorithm:
   * 1. Normalize the term first
   * 2. Expect form: Abs(f, Abs(x, body))
   * 3. Count how many times 'f' appears in application position in body
   */
  async doChurchToInt(termHash) {
    try {
      const result = await normalize(this.collection, termHash, "normal", 1e3).run();
      if (result.isLeft) {
        return { success: false, error: result.left };
      }
      const normResult = result.right;
      const pretty = await prettyPrintDeep(this.collection, normResult.normalForm);
      const count = await this.countChurchApplications(normResult.normalForm);
      return {
        success: true,
        result: count,
        prettyPrint: `${count} (Church: ${pretty})`
      };
    } catch (err) {
      return {
        success: false,
        error: `Church decode error: ${err instanceof Error ? err.message : String(err)}`
      };
    }
  }
  /**
   * Count applications in a Church numeral body.
   * Church numeral n has the form: λf.λx.f(f(f(...f(x)...)))
   * where f appears n times.
   */
  async countChurchApplications(termHash) {
    const term = await loadTerm(this.collection, termHash);
    if (!term) return -1;
    if (term.tag !== "Abs") return -1;
    const fVar = term.param;
    const innerTerm = await loadTerm(this.collection, term.body);
    if (!innerTerm || innerTerm.tag !== "Abs") return -1;
    const xVar = innerTerm.param;
    let count = 0;
    let currentHash = innerTerm.body;
    while (true) {
      const body = await loadTerm(this.collection, currentHash);
      if (!body) break;
      if (body.tag === "App") {
        const func = await loadTerm(this.collection, body.func);
        if (func && func.tag === "Var" && func.name === fVar) {
          count++;
          currentHash = body.arg;
        } else {
          break;
        }
      } else if (body.tag === "Var" && body.name === xVar) {
        return count;
      } else {
        break;
      }
    }
    return count;
  }
  async doCheckReadiness(ctx) {
    const runtimeName = ctx.runtime_name || "lambda";
    let available = false;
    try {
      const status = await RuntimeFactory.getAvailableRuntimes();
      available = !!status[runtimeName] || runtimeName === "lambda";
    } catch (e) {
      available = runtimeName === "lambda" || runtimeName === "javascript";
    }
    return {
      success: true,
      result: `Runtime ${runtimeName} status: ${available ? "True" : "False"}`
    };
  }
  async doNumericOp(op, ctx) {
    const a = Number(ctx.a ?? 0);
    const b = Number(ctx.b ?? 0);
    let res = 0;
    switch (op) {
      case "num-add":
        res = a + b;
        break;
      case "num-sub":
        res = a - b;
        break;
      case "num-mul":
        res = a * b;
        break;
      case "num-div":
        res = b !== 0 ? a / b : 0;
        break;
      default:
        return { success: false, error: `Unknown numeric op: ${op}` };
    }
    return {
      success: true,
      result: String(res)
    };
  }
  async doHttpRequest(ctx) {
    const url = ctx.url;
    if (!url) return { success: false, error: "http-request requires 'url'" };
    try {
      const response = await fetch(url);
      const text = await response.text();
      return {
        success: true,
        result: text.substring(0, 1e3)
        // Truncate for safety
      };
    } catch (e) {
      return { success: false, error: `HTTP Request failed: ${e}` };
    }
  }
};
async function parseLambdaExpression(collection, expression) {
  const tokens = tokenize(expression);
  let pos = 0;
  function peek() {
    return pos < tokens.length ? tokens[pos] : null;
  }
  function consume() {
    if (pos >= tokens.length) throw new Error("Unexpected end of expression");
    return tokens[pos++];
  }
  function expect(token) {
    const actual = consume();
    if (actual !== token) {
      throw new Error(`Expected '${token}', got '${actual}'`);
    }
  }
  async function parseExpr() {
    const terms = [];
    while (peek() && peek() !== ")") {
      terms.push(await parseTerm());
    }
    if (terms.length === 0) {
      throw new Error("Empty expression");
    }
    let result = terms[0];
    for (let i = 1; i < terms.length; i++) {
      const app = mkApp(result, terms[i]);
      result = await storeTerm(collection, app);
    }
    return result;
  }
  async function parseTerm() {
    const token = peek();
    if (token === "\\" || token === "\u03BB") {
      return parseAbstraction();
    }
    if (token === "(") {
      consume();
      const expr = await parseExpr();
      expect(")");
      return expr;
    }
    const name = consume();
    if (!name.startsWith('"') && !name.match(/^[a-zA-Z_][a-zA-Z0-9_']*$/)) {
      throw new Error(`Invalid variable name: ${name}`);
    }
    const varTerm = mkVar(name);
    return storeTerm(collection, varTerm);
  }
  async function parseAbstraction() {
    consume();
    const param = consume();
    if (!param.match(/^[a-zA-Z_][a-zA-Z0-9_']*$/)) {
      throw new Error(`Invalid parameter name: ${param}`);
    }
    expect(".");
    const body = await parseExpr();
    const abs = mkAbs(param, body);
    return storeTerm(collection, abs);
  }
  return parseExpr();
}
function tokenize(expression) {
  const tokens = [];
  let i = 0;
  while (i < expression.length) {
    const ch = expression[i];
    if (/\s/.test(ch)) {
      i++;
      continue;
    }
    if ("()\\\u03BB.".includes(ch)) {
      tokens.push(ch);
      i++;
      continue;
    }
    if (ch === '"') {
      let str = '"';
      i++;
      while (i < expression.length && expression[i] !== '"') {
        str += expression[i++];
      }
      if (i < expression.length) {
        str += '"';
        i++;
      } else {
        throw new Error("Unterminated string literal");
      }
      tokens.push(str);
      continue;
    }
    if (/[a-zA-Z_]/.test(ch)) {
      let name = "";
      while (i < expression.length && /[a-zA-Z0-9_']/.test(expression[i])) {
        name += expression[i++];
      }
      tokens.push(name);
      continue;
    }
    throw new Error(`Unexpected character: ${ch}`);
  }
  return tokens;
}

export {
  mkVar,
  mkAbs,
  mkApp,
  serializeTerm,
  deserializeTerm,
  termToMCard,
  mcardToTerm,
  storeTerm,
  loadTerm,
  termExists,
  prettyPrintShallow,
  prettyPrintDeep,
  isVar,
  isAbs,
  isApp,
  freeVariables,
  boundVariables,
  isFreeIn,
  isClosed,
  generateFresh,
  generateFreshFor,
  difference,
  intersection,
  alphaRename,
  alphaEquivalent,
  alphaNormalize,
  isRedex,
  findLeftmostRedex,
  findInnermostRedex,
  betaReduce,
  reduceStep,
  normalize,
  isNormalForm,
  hasNormalForm,
  isEtaRedex,
  etaReduce,
  etaReduceE,
  etaExpand,
  etaEquivalent,
  etaNormalize,
  findEtaRedexes,
  RuntimeFactory,
  LambdaRuntime,
  parseLambdaExpression
};