#!/bin/bash # # transform_test.sh — Regression tests for the Klammermachine's typographic # TRANSFORM pass (the third positional of @@@target). # # Kept SKS-INDEPENDENT on purpose: a target is a Machine construct, so the # fixture target is declared inline and no klammer set is loaded. The SKS's # own transform tables (html, txt, tex) are exercised by # sks/tst/typography_test.sh. # # What the pass IS: each pair replaces a source character sequence in the # final output with its target spelling (the LaTeX input conventions carried # to other targets: --- to an em dash, quote pairs, ~ to a non-breaking # space). Since 2026-08-23 it runs PER KATOM at final processing, not over # the joined result string, which is what these cases pin: # # - ordinary writer text -> transformed # - ^'...'^ literal span (katom_t::literal) -> NEVER transformed # - a KTESC marker in the text -> never matched; decodes after # the pass (this is the contract hide_typographic() in the SKS's # klammer_base.py relies on to protect @c/@code verbatim text) # - a plain-text @eval result (a renderer) -> transformed like writer text # - a source split across a katom boundary -> NOT transformed (the writer # separated the characters structurally; they are not a dash) # - :includes -> transforms are inherited, # as escapes are # - the pairs run in declared order # # The transform/escape interplay is also pinned: escapes become markers # before klammer application, transforms run at final processing, markers # resolve last — so the two mechanisms cannot corrupt each other's output. # # Usage: ./transform_test.sh (needs KLAMMERTEXT_HOME set; ktext on PATH) # Exit code: 0 if all tests pass, 1 otherwise. PASS=0 FAIL=0 KTEXT=ktext K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set} ERR=/tmp/transform_test_err.$$ red=$'\033[31m' green=$'\033[32m' bold=$'\033[1m' reset=$'\033[0m' # strip leading/trailing blank lines and trailing whitespace trim() { awk '{ sub(/[ \t\r]+$/, "") } { line[NR]=$0 } END { f=1; while (f<=NR && line[f]=="") f++; l=NR; while (l>=1 && line[l]=="") l--; for (i=f;i<=l;i++) print line[i] }'; } # check_eq NAME EXPECTED KTEXT_ARGS... — exit 0 and stdout==EXPECTED. check_eq() { local name="$1" expected="$2"; shift 2 local out status err out=$("$KTEXT" "$@" 2>"$ERR"); status=$? err=$(cat "$ERR") out=$(printf '%s' "$out" | trim) if [ $status -ne 0 ]; then echo "${red}FAIL${reset} $name — ktext exited $status" echo " stderr: $(echo "$err" | head -2)"; FAIL=$((FAIL+1)); return fi if [ "$out" = "$expected" ]; then echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} $name" echo " expected: [$expected]"; echo " got: [$out]"; FAIL=$((FAIL+1)) fi } echo "${bold}Typographic transform tests (Machine mechanism, fixture target 't')${reset}" echo "===================================================================" echo # The fixture target: one escape (& -> AMP) and two transforms, whose # replacements are distinct tokens that are easy to assert. T='@@@target t | test target :escape & AMP | --- MDASH | -- NDASH @@@' check_eq " 1. writer text: --- transformed" 'a MDASH b' --klammersets none -t t -s "$T a --- b" check_eq " 2. pairs in declared order: -- after ---" 'a NDASH b' --klammersets none -t t -s "$T a -- b" check_eq " 3. literal span: NOT transformed" 'a -- b' --klammersets none -t t -s "$T ^'a -- b'^" check_eq " 4. escape and transform coexist" 'x AMP y NDASH' --klammersets none -t t -s "$T x & y --" # 5-6: the @eval sits in a klammer BODY, applied after the fixture target # is extracted — a top-level @eval runs at read time, before same-input # @@@ extraction (the documented pass-ordering caveat), and would not # find target t. check_eq " 5. renderer @eval result: transformed" 'aNDASHb' --klammersets none -t t -s "$T @@e.t : @eval \"a--b\" @ @@ @e@" check_eq " 6. KTESC marker: skipped, then decoded" '--' --klammersets none -t t -s "$T @@e.t : @eval \"KTESC002dKTESC\" * 2 @ @@ @e@" check_eq " 7. source across a katom boundary: NOT transformed" '- x -' --klammersets none -t t -s "$T @@g : x @@ - @g@ -" # 8. :includes — an including target inherits the included target's # transforms (and escapes), so a target built over another renders the # same writer conventions. T2="$T @@@target t2 | over t :includes t @@@" check_eq " 8. :includes inherits transforms" 'a NDASH b AMP c' --klammersets none -t t2 -s "$T2 a -- b & c" # 9-12: "^" before any punctuation character quotes it # (hide_quoted_punctuation, 2026-08-23): the pair becomes the KTESC marker # of the character, which the transform pass cannot match. The target # decides the rendering — a :resolve entry, an :escape entry, or (neither) # the raw character. Inside an @eval span the code keeps its carets. check_eq " 9. quoted hyphens: no transform, decode raw" '--' --klammersets none -t t -s "$T ^-^-" check_eq "10. :resolve maps a quoted character" 'LIT-' --klammersets none -t r -s '@@@target r | r :resolve - LIT- @@@ ^-' check_eq "11. quoted char with an :escape entry takes its replacement" 'AMP' --klammersets none -t t -s "$T ^&" check_eq "12. @eval code keeps its carets" '2' --klammersets none -t t -s "$T @@e.t : @eval 3 ^(1) @ @@ @e@" echo echo "====================================================================" echo "${bold}Results: $PASS passed, $FAIL failed${reset}" rm -f "$ERR" [ $FAIL -eq 0 ] && exit 0 || exit 1