cloverleaf-larry/lib/nc-xlate.sh
Bryan Johnson d58e4e0ec8 v0.8.28: expose 5 lib-only tools + fix nc-engine arg-parsing crash
Wires nc_status, nc_engine, nc_xlate, nc_smat_diff, nc_tclgen as first-class
LLM tools (all 4 surfaces). nc_engine unlocks TPS testing (hcitps) + the
route-test driver. Fixes a real nc-engine.sh bug surfaced by the exposure:
the dispatcher treated every --flag as taking a value (--dry-run ate the next
token) and a set -u leak from journal.sh crashed start/stop/bounce on bash 3.2;
fixed with set +u + a multi-case parser (no over-shift on bare trailing flags).
Corrects stale CHANGELOG + nc_engine schema text that misstated the bug as live.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:18:23 -07:00

156 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# nc-xlate.sh — visualize and explore Cloverleaf xlate (.xlt) files.
# Parses the TCL nested-block format and renders operation flows.
#
# Subcommands:
# list [--site SITE] list .xlt files
# show <name> [--site SITE] dump the raw file
# ops <name> [--site SITE] list operations as TSV (op, in, out, err)
# tree <name> [--site SITE] ASCII tree by op type
# summary <name> [--site SITE] counts by operation + segments touched
# diff <name1> <name2> diff two xlates (semantic, sorted-by-op)
set -o pipefail
NC_SELF="$0"
die() { printf 'nc-xlate: %s\n' "$*" >&2; exit 1; }
locate_xlate() {
local name="$1" site="${2:-${HCISITE:-}}"
name="${name%.xlt}"
for d in "${HCIROOT:-}/$site/Xlate" "${HCIROOT:-}/$site/xlate" "${HCIROOT:-}/Xlate"; do
[ -f "$d/${name}.xlt" ] && { printf '%s\n' "$d/${name}.xlt"; return 0; }
done
return 1
}
cmd_list() {
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
for d in "${HCIROOT:-}/$site/Xlate" "${HCIROOT:-}/$site/xlate"; do
[ -d "$d" ] || continue
find "$d" -maxdepth 1 -name '*.xlt' -type f 2>/dev/null | sort
done
}
cmd_show() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
cat "$f"
}
# Parse the .xlt operations. Each top-level block looks like:
# { { OP <name> }
# { ERR <code> }
# { IN <path> }
# { OUT <path> }
# [ { TABLE <name> } ]
# [ ... ]
# }
# Emit TSV: op_num \t op \t in \t out \t err \t extra
parse_ops() {
local f="$1"
awk '
BEGIN { depth=0; in_block=0; n=0 }
/^end_prologue$/ { in_pre=0; next }
/^prologue$/ { in_pre=1; next }
in_pre { next }
/^[[:space:]]*\{ \{ OP / {
depth=1; in_block=1
op=""; in_path=""; out_path=""; err=""; extra=""
# First line has "{ { OP <name> }"
match($0, /\{ OP [A-Z]+ \}/)
if (RSTART) {
op = substr($0, RSTART+5, RLENGTH-7)
}
next
}
in_block {
# Track depth via gsub-of-self trick
no = gsub(/\{/, "{", $0)
nc = gsub(/\}/, "}", $0)
depth += no - nc
if (match($0, /\{ IN [^}]+\}/)) { in_path = substr($0, RSTART+5, RLENGTH-6); gsub(/[[:space:]]+$/, "", in_path) }
if (match($0, /\{ OUT [^}]+\}/)) { out_path = substr($0, RSTART+6, RLENGTH-7); gsub(/[[:space:]]+$/, "", out_path) }
if (match($0, /\{ ERR [^}]+\}/)) { err = substr($0, RSTART+6, RLENGTH-7); gsub(/[[:space:]]+$/, "", err) }
if (match($0, /\{ TABLE [^}]+\}/)) { extra = "TABLE=" substr($0, RSTART+8, RLENGTH-9); gsub(/[[:space:]]+$/, "", extra) }
if (depth == 0) {
n++
printf "%d\t%s\t%s\t%s\t%s\t%s\n", n, op, in_path, out_path, err, extra
in_block = 0
}
}
' "$f"
}
cmd_ops() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
printf 'num\top\tin\tout\terr\textra\n'
parse_ops "$f"
}
cmd_tree() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
printf 'Xlate: %s\n' "$name"
parse_ops "$f" | awk -F'\t' '
{
op=$2; in_path=$3; out_path=$4; extra=$6
if (in_path == "" && out_path == "") { printf " %d. %s\n", $1, op; next }
arrow = "→"
if (op == "MOVE") arrow = "↦"
if (op == "DELETE") arrow = "✗"
if (extra != "") arrow = "↦(" extra ")→"
printf " %2d. %-12s %-30s %s %s\n", $1, op, in_path, arrow, out_path
}
'
}
cmd_summary() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
printf 'Xlate: %s (path: %s)\n\n' "$name" "$f"
printf 'Operations by type:\n'
parse_ops "$f" | awk -F'\t' 'NR>0 {c[$2]++} END {for (k in c) printf " %-12s %d\n", k, c[k]}' | sort -k2 -rn
printf '\nSegments touched (IN side):\n'
parse_ops "$f" | awk -F'\t' '{
if (match($3, /\.[A-Z][A-Z0-9]+\(/)) {
seg = substr($3, RSTART+1, RLENGTH-2)
c[seg]++
}
} END {for (k in c) printf " %-6s %d\n", k, c[k]}' | sort
printf '\nTables referenced:\n'
parse_ops "$f" | awk -F'\t' '$6 ~ /TABLE=/ { t=substr($6,7); print t }' | sort -u | sed 's/^/ /'
}
cmd_diff() {
local n1="$1" n2="$2"
local f1 f2
f1=$(locate_xlate "$n1") || die "no such xlate: $n1"
f2=$(locate_xlate "$n2") || die "no such xlate: $n2"
diff -u <(parse_ops "$f1" | sort -k2) <(parse_ops "$f2" | sort -k2)
}
SUB="${1:-list}"
case "$SUB" in
list) shift; cmd_list "$@" ;;
show) shift; [ $# -ge 1 ] || die "usage: show NAME"; cmd_show "$@" ;;
ops) shift; [ $# -ge 1 ] || die "usage: ops NAME"; cmd_ops "$@" ;;
tree) shift; [ $# -ge 1 ] || die "usage: tree NAME"; cmd_tree "$@" ;;
summary) shift; [ $# -ge 1 ] || die "usage: summary NAME"; cmd_summary "$@" ;;
diff) shift; [ $# -ge 2 ] || die "usage: diff NAME1 NAME2"; cmd_diff "$@" ;;
help|-h|--help) sed -n '2,11p' "$NC_SELF" ;;
*) die "unknown subcommand: $SUB" ;;
esac