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>
156 lines
5.4 KiB
Bash
Executable File
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
|