///|
pub fn render_module_opcode_comparison_json(
  report : ModuleOpcodeComparison,
) -> String {
  let root : Map[String, Json] = Map([])
  root.set("format", Json::string("moonsize-module-opcodes-diff"))
  root.set("schema_version", int_json(1))
  root.set("baseline", module_opcodes_json_value(report.baseline))
  root.set("current", module_opcodes_json_value(report.current))
  root.set("scope", Json::string(report.scope))
  let d = report.delta
  let delta : Map[String, Json] = Map([])
  delta.set("file_bytes", int_json(d.file_bytes))
  delta.set("code_bytes", int_json(d.code_bytes))
  delta.set("non_code_bytes", int_json(d.non_code_bytes))
  delta.set("code_header_bytes", int_json(d.code_header_bytes))
  delta.set("count_encoding_bytes", int_json(d.count_encoding_bytes))
  delta.set("body_prefix_bytes", int_json(d.body_prefix_bytes))
  delta.set("locals_encoding_bytes", int_json(d.locals_encoding_bytes))
  delta.set("instruction_bytes", int_json(d.instruction_bytes))
  delta.set("instruction_count", int_json(d.instruction_count))
  delta.set("imported_function_count", int_json(d.imported_function_count))
  delta.set("local_function_count", int_json(d.local_function_count))
  root.set("delta", Json::object(delta))
  root.set(
    "opcodes",
    Json::array(
      report.opcodes.map(fn(row) {
        let item : Map[String, Json] = Map([])
        item.set("mnemonic", Json::string(row.mnemonic))
        item.set("status", Json::string(row.status))
        item.set("baseline_count", int_json(row.baseline_count))
        item.set("current_count", int_json(row.current_count))
        item.set("count_delta", int_json(row.count_delta))
        item.set("baseline_encoded_bytes", int_json(row.baseline_encoded_bytes))
        item.set("current_encoded_bytes", int_json(row.current_encoded_bytes))
        item.set("encoded_bytes_delta", int_json(row.encoded_bytes_delta))
        item.set(
          "baseline_function_count",
          int_json(row.baseline_function_count),
        )
        item.set("current_function_count", int_json(row.current_function_count))
        item.set("function_count_delta", int_json(row.function_count_delta))
        Json::object(item)
      }),
    ),
  )
  Json::object(root).stringify(indent=2) + "\n"
}

///|
pub fn render_module_opcode_comparison(
  report : ModuleOpcodeComparison,
  top : Int,
) -> String {
  let out = StringBuilder()
  out.write_string("MODULE FUNCTION OPCODE COMPARISON\n\n")
  for (side, r) in [("Baseline", report.baseline), ("Current", report.current)] {
    out.write_string(
      "\{side}: \{r.file_size} B; SHA-256 \{r.sha256}\nImported functions: \{r.imported_function_count}; local functions: \{r.local_function_count}\n",
    )
    if r.code_section is None {
      out.write_string("No code section; code/count ranges are absent.\n")
    }
  }
  let b = report.baseline
  let c = report.current
  let d = report.delta
  out.write_string(
    "\nBYTE AND COUNT LEDGER (delta = current - baseline)\nMETRIC  BASELINE  CURRENT  DELTA\n",
  )
  for
    (label, before, after, change) in [
      ("file_bytes", b.file_size, c.file_size, d.file_bytes),
      (
        "code_bytes",
        module_opcode_code_bytes(b),
        module_opcode_code_bytes(c),
        d.code_bytes,
      ),
      (
        "non_code_bytes",
        b.file_size - module_opcode_code_bytes(b),
        c.file_size - module_opcode_code_bytes(c),
        d.non_code_bytes,
      ),
      (
        "code_header_bytes",
        b.code_header_bytes,
        c.code_header_bytes,
        d.code_header_bytes,
      ),
      (
        "count_encoding_bytes",
        b.count_encoding_bytes,
        c.count_encoding_bytes,
        d.count_encoding_bytes,
      ),
      (
        "body_prefix_bytes",
        b.body_prefix_bytes,
        c.body_prefix_bytes,
        d.body_prefix_bytes,
      ),
      (
        "locals_encoding_bytes",
        b.locals_encoding_bytes,
        c.locals_encoding_bytes,
        d.locals_encoding_bytes,
      ),
      (
        "instruction_bytes",
        b.instruction_bytes,
        c.instruction_bytes,
        d.instruction_bytes,
      ),
      (
        "instruction_count",
        b.instruction_count,
        c.instruction_count,
        d.instruction_count,
      ),
      (
        "imported_function_count",
        b.imported_function_count,
        c.imported_function_count,
        d.imported_function_count,
      ),
      (
        "local_function_count",
        b.local_function_count,
        c.local_function_count,
        d.local_function_count,
      ),
    ] {
    out.write_string("\{label}  \{before}  \{after}  \{change}\n")
  }
  out.write_string(
    "File delta: \{d.file_bytes} = code \{d.code_bytes} + non-code \{d.non_code_bytes}\n",
  )
  out.write_string(
    "Code delta: \{d.code_bytes} = header \{d.code_header_bytes} + count encoding \{d.count_encoding_bytes} + body prefixes \{d.body_prefix_bytes} + locals \{d.locals_encoding_bytes} + instructions \{d.instruction_bytes}\n",
  )
  let shown = top.max(0).min(report.opcodes.length())
  out.write_string(
    "\nOPCODE COMPOSITION: showing \{shown} of \{report.opcodes.length()}\nMNEMONIC  STATUS  BEFORE COUNT  AFTER COUNT  COUNT DELTA  BEFORE B  AFTER B  BYTE DELTA  BEFORE FUNCTIONS  AFTER FUNCTIONS  FUNCTION DELTA\n",
  )
  for i in 0..