harn-stdlib 0.10.133

Embedded Harn standard library source catalog
Documentation
/**
 * `harn precompile` ported to .harn — see harn#2313 (W13).
 *
 * Walks a file or directory tree, dispatches each `.harn` source to a
 * `harn precompile <single-file>` child that runs the Rust compiler
 * entrypoint. The .harn script owns:
 *
 *   * argv parsing (target, --out, --keep-going, --quiet)
 *   * directory walking (single source vs recursive .harn discovery)
 *   * per-file --out path mirroring under the source root
 *   * the per-file render and the trailing "N compiled, N reused, N
 *     failed" summary
 *
 * The actual parse+typecheck+compile work stays in Rust on the spawned
 * child (toggled via HARN_PRECOMPILE_INNER=1 so the child doesn't recurse
 * back into this script). That keeps the bytecode-cache wire-format,
 * compiler diagnostics, and module artifact emission untouched while
 * letting the orchestration / file-walk layer live in Harn.
 *
 * Inputs (from the dispatch shim in crates/harn-cli/src/commands/precompile.rs):
 *   HARN_CLI_SELF_EXE   — absolute path to the running `harn` binary,
 *                         captured via `std::env::current_exe()` so the
 *                         child invocation is robust to $PATH ordering.
 *   argv[0]             — target file or directory (required)
 *   HARN_PRECOMPILE_OUT — output directory (optional; mirrors source tree)
 *   HARN_PRECOMPILE_KEEP_GOING — "1" to continue after a per-file error
 *   HARN_PRECOMPILE_QUIET      — "1" to suppress per-file progress
 */
fn __walk_harn_files(fs: HarnessFs, root: string) -> dict {
  // walk_dir returns a flat list of {path, is_dir, is_file, depth}. We
  // only emit `.harn` files. Walk order is stable per the builtin
  // contract; we sort + dedupe afterwards to match the Rust impl which
  // calls `files.sorted(); files.dedup();`.
  //
  // The Rust collect_harn_files() (crates/harn-cli/src/commands/mod.rs)
  // also drops files with a sibling `<name>.conformance-skip` marker;
  // mirror that here so a directory with skip markers walks identically.
  //
  // The walk's own depth-0 entry is returned alongside the files, and it is
  // the root every destination path is computed against. The caller's root is
  // whatever the user typed and may be relative, while every walked path is
  // absolute; deriving a relative path by mixing the two silently yields
  // nothing, which is how the output mirror came to collapse. Reading the root
  // back out of the same walk that produced the files keeps one spelling.
  const entries = fs.walk(root)
  let walked_root = ""
  let files = []
  for entry in entries {
    const path = entry["path"]
    const is_file = entry["is_file"] ?? false
    if !is_file {
      if (entry["depth"] ?? -1) == 0 {
        walked_root = path
      }
      continue
    }
    if path_extension(path) != ".harn" {
      continue
    }
    const skip_marker = path_with_extension(path, "conformance-skip")
    if fs.exists(skip_marker) {
      continue
    }
    files = files.appending(path)
  }
  const sorted = files.sorted()
  // dedupe stable-sorted list: keep entry when it differs from previous.
  let out = []
  let prev = ""
  for path in sorted {
    if path != prev || len(out) == 0 {
      out = out.appending(path)
    }
    prev = path
  }
  return {root: walked_root, files: out}
}

fn __destination_path(
  source_path: string,
  source_root: string,
  out_root: string,
  extension: string,
) -> string? {
  // Mirror the Rust output_path() exactly: when out_root is empty, write
  // adjacent to source; otherwise compute the relative path from
  // source_root and join under out_root, then swap the extension.
  //
  // Returns nil rather than a basename when a source under a directory walk
  // will not relativize. A basename there is not a fallback, it is a different
  // destination: every equally named module in the tree lands on it and all
  // but the last are overwritten, with nothing written to the paths the caller
  // was promised and a zero exit. An empty source_root is the single-file
  // case, where the basename IS the mirrored path.
  if out_root == "" {
    return path_with_extension(source_path, extension)
  }
  if source_root == "" {
    return path_with_extension(path_join(out_root, path_basename(source_path)), extension)
  }
  const relative = path_relative_to(source_path, source_root)
  if relative == nil {
    return nil
  }
  return path_with_extension(path_join(out_root, relative), extension)
}

fn __ensure_parent(fs: HarnessFs, path: string) {
  const parent = path_parent(path)
  if parent != "" && !fs.exists(parent) {
    fs.mkdir(parent)
  }
}

fn __spawn_precompile(
  process: HarnessProcess,
  bin: string,
  source_path: string,
  per_file_out: string,
  source_root: string,
) -> dict {
  // Always force the child into the Rust compiler entrypoint: this script IS
  // the .harn dispatch target, so leaving HARN_PRECOMPILE_INNER unset would
  // re-enter us and spin forever.
  //
  // --quiet on the child suppresses its own per-file println; the
  // parent script owns the user-visible output so the byte-for-byte
  // shape is preserved when this port becomes the default.
  let args = ["precompile", "--quiet", source_path]
  if source_root != "" {
    args = args.appending("--relocatable")
  }
  if per_file_out != "" {
    args = args.appending("--out")
    args = args.appending(per_file_out)
  }
  return process.run({program: bin, args: args, env: {HARN_PRECOMPILE_INNER: "1"}})
}

fn __child_reused(child: dict) -> bool {
  // The child reports per-source outcome on stdout, which this driver captures
  // and never forwards. Absence means compiled, not unknown: a child that
  // succeeded without printing the marker is an older binary than this script,
  // and counting that as a reuse would report work as skipped that was done.
  const stdout = child["stdout"] ?? ""
  for line in split(stdout, "\n") {
    if trim(line) == "precompile-outcome: reused" {
      return true
    }
  }
  return false
}

fn __render_failure(source: string, child: dict) -> string {
  // Match the Rust eprintln format `{source}: {err}` where `err` is
  // either the child's first stderr line or a compact exit summary.
  const stderr = child["stderr"] ?? ""
  const trimmed = trim(stderr)
  if trimmed != "" {
    const lines = split(trimmed, "\n")
    return source + ": " + lines[0]
  }
  const exit_code = child["exit_code"] ?? -1
  return source + ": precompile exited with code " + to_string(exit_code)
}

fn main(harness: Harness) {
  if len(argv) < 1 {
    harness.stdio.eprintln("precompile: target path is required")
    harness.runtime.exit(2)
  }
  const bin = harness.env.get_or("HARN_CLI_SELF_EXE", "")
  if bin == "" {
    harness.stdio.eprintln("internal error: HARN_CLI_SELF_EXE not set by dispatch shim")
    harness.runtime.exit(70)
  }
  const target = argv[0]
  const out_root = harness.env.get_or("HARN_PRECOMPILE_OUT", "")
  const keep_going = harness.env.get_or("HARN_PRECOMPILE_KEEP_GOING", "0") == "1"
  const quiet = harness.env.get_or("HARN_PRECOMPILE_QUIET", "0") == "1"
  if !harness.fs.exists(target) {
    harness.stdio.eprintln("error: target does not exist: " + target)
    harness.runtime.exit(1)
  }
  const info = harness.fs.stat(target)
  const is_dir = info["is_dir"] ?? false
  const walked = if is_dir {
    __walk_harn_files(harness.fs, target)
  } else {
    {root: "", files: [target]}
  }
  const sources = walked["files"] ?? []
  // The walk's own spelling of the root, not the caller's. See
  // __walk_harn_files: mixing a relative root with absolute walked paths is
  // what collapsed the mirror.
  const source_root = walked["root"] ?? ""
  if is_dir && source_root == "" {
    harness.stdio.eprintln(
      "internal error: directory walk of " + target + " reported no root entry",
    )
    harness.runtime.exit(70)
  }
  if len(sources) == 0 {
    harness.stdio.eprintln("error: no .harn files found under " + target)
    harness.runtime.exit(1)
  }
  let compiled = 0
  let reused = 0
  let failed = 0
  for source in sources {
    // For each source, pre-compute the destination dir so the child --out
    // points at the exact directory the .harnbc should land in. This
    // preserves the source-tree mirroring the Rust output_path() does;
    // without this, the child (which sees a single-file target) would
    // collapse everything into out_root with no subdirectories.
    const placed = __destination_path(source, source_root, out_root, "harnbc")
    if placed == nil {
      harness.stdio.eprintln(source + ": cannot place under --out: not relative to " + source_root)
      harness.runtime.exit(1)
    }
    // Unreachable default: the exit above is terminal, but the checker does
    // not narrow through it.
    const mirrored = placed ?? ""
    const per_file_out = if out_root == "" {
      ""
    } else {
      __ensure_parent(harness.fs, mirrored)
      path_parent(mirrored)
    }
    const child = __spawn_precompile(harness.process, bin, source, per_file_out, source_root)
    const exit_code = child["exit_code"] ?? -1
    if exit_code == 0 {
      if __child_reused(child) {
        reused = reused + 1
      } else {
        compiled = compiled + 1
      }
      if !quiet {
        harness.stdio.println(source + " -> " + mirrored)
      }
    } else {
      failed = failed + 1
      harness.stdio.eprintln(__render_failure(source, child))
      if !keep_going {
        break
      }
    }
  }
  if !quiet {
    harness.stdio.eprintln(
      "precompile: " + to_string(compiled) + " compiled, "
        + to_string(reused)
        + " reused, "
        + to_string(failed)
        + " failed",
    )
  }
  if failed > 0 {
    harness.runtime.exit(1)
  }
}