harn-stdlib 0.8.39

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 legacy Rust
 * compiler. The .harn port 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 success/fail per-line render and trailing "N succeeded, N
 *     failed" summary
 *
 * The actual parse+typecheck+compile work stays in Rust on the spawned
 * child (toggled via HARN_CLI_IMPL=rust 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 move into 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 __collect_harn_files(harness: Harness, root: string) -> list<string> {
  // 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.sort(); 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.
  let entries = harness.fs.walk(root)
  var files = []
  for entry in entries {
    let path = entry["path"]
    let is_file = entry["is_file"] ?? false
    if !is_file {
      continue
    }
    if path_extension(path) != ".harn" {
      continue
    }
    let skip_marker = path_with_extension(path, "conformance-skip")
    if harness.fs.exists(skip_marker) {
      continue
    }
    files = files.push(path)
  }
  let sorted = files.sort()
  // dedupe stable-sorted list: keep entry when it differs from previous.
  var out = []
  var prev = ""
  for path in sorted {
    if path != prev || len(out) == 0 {
      out = out.push(path)
    }
    prev = path
  }
  return 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.
  if out_root == "" {
    return path_with_extension(source_path, extension)
  }
  let relative = if source_root == "" {
    path_basename(source_path)
  } else {
    let rel = path_relative_to(source_path, source_root)
    if rel == nil {
      path_basename(source_path)
    } else {
      rel
    }
  }
  let joined = path_join(out_root, relative)
  return path_with_extension(joined, extension)
}

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

fn __spawn_precompile(harness: Harness, bin: string, source_path: string, per_file_out: string) -> dict {
  // Always force the child onto the Rust path: this script IS the .harn
  // dispatch target, so leaving HARN_CLI_IMPL unset would re-enter us
  // and spin forever. HARN_CLI_IMPL=rust short-circuits the dispatch
  // wedge in crates/harn-cli/src/commands/precompile.rs.
  //
  // --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.
  var args = ["precompile", "--quiet", source_path]
  if per_file_out != "" {
    args = args.push("--out")
    args = args.push(per_file_out)
  }
  let env = {HARN_CLI_IMPL: "rust"}
  return harness.process.spawn_captured({cmd: bin, args: args, env: env})
}

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.
  let stderr = child["stderr"] ?? ""
  let trimmed = trim(stderr)
  if trimmed != "" {
    let lines = split(trimmed, "\n")
    return source + ": " + lines[0]
  }
  let 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")
    exit(2)
  }
  let 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")
    exit(70)
  }
  let target = argv[0]
  let out_root = harness.env.get_or("HARN_PRECOMPILE_OUT", "")
  let keep_going = harness.env.get_or("HARN_PRECOMPILE_KEEP_GOING", "0") == "1"
  let quiet = harness.env.get_or("HARN_PRECOMPILE_QUIET", "0") == "1"
  if !harness.fs.exists(target) {
    harness.stdio.eprintln("error: target does not exist: " + target)
    exit(1)
  }
  let info = harness.fs.stat(target)
  let is_dir = info["is_dir"] ?? false
  let sources = if is_dir {
    __collect_harn_files(harness, target)
  } else {
    [target]
  }
  let source_root = if is_dir {
    target
  } else {
    ""
  }
  if len(sources) == 0 {
    harness.stdio.eprintln("error: no .harn files found under " + target)
    exit(1)
  }
  var compiled = 0
  var 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.
    let per_file_out = if out_root == "" {
      ""
    } else {
      let dest = __destination_path(source, source_root, out_root, "harnbc")
      __ensure_parent(harness, dest)
      path_parent(dest)
    }
    let child = __spawn_precompile(harness, bin, source, per_file_out)
    let exit_code = child["exit_code"] ?? -1
    if exit_code == 0 {
      compiled = compiled + 1
      if !quiet {
        let dest = __destination_path(source, source_root, out_root, "harnbc")
        harness.stdio.println(source + " -> " + dest)
      }
    } else {
      failed = failed + 1
      harness.stdio.eprintln(__render_failure(source, child))
      if !keep_going {
        break
      }
    }
  }
  if !quiet {
    harness.stdio
      .eprintln(
      "precompile: " + to_string(compiled) + " succeeded, "
        + to_string(failed)
        + " failed",
    )
  }
  if failed > 0 {
    exit(1)
  }
}