Verbatim-safe typography, ^-punctuation quoting, full-range ^UUUU^, polyglot html

Typographic transforms (---, quote pairs, ~) no longer touch verbatim
text: @c/@code/@source_listing content and ^'...'^ spans show exactly the
characters written. "^" before any punctuation character quotes it in
every target (the apostrophe excepted: ^' opens a literal span), with the
new :resolve option on @@@target declaring per-target renderings. The
^UUUU^ code-point form accepts 4-6 hex digits, the full Unicode range.
The html output and transform spellings are polyglot (XML-valid), in
preparation for an EPUB target. New suites: transform_test, character_test
(engine), typography_test (SKS).

(from dev 07ce5ea86a0a)
This commit is contained in:
2026-08-23 20:48:26 +02:00
parent d982c0d6cc
commit 37b6ba1c4f
77 changed files with 1665 additions and 1608 deletions

109
tst/transform_test.sh Executable file
View File

@@ -0,0 +1,109 @@
#!/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