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

121
tst/state_test.sh Executable file
View File

@@ -0,0 +1,121 @@
#!/bin/bash
#
# state_test.sh — The @@@state system command and *name* substitution.
#
# Added 2026-08-22 by the error-gallery coverage review, which found that no
# suite covered @@@state at all: its two error messages had never been
# printed by any test. What is pinned here, each by outcome:
#
# * :value sets a variable and *name* substitutes it in a klammer BODY
# (bodies are processed at application time). A top-level *name* of a
# document-declared variable does NOT substitute — the same read-time
# asymmetry @cond had before 2026-08-15; if that is ever changed, case 2
# documents today's behaviour and should change with it.
# * :replace replaces an existing value.
# * Redefining WITHOUT :replace is an error that names the remedy.
# * An undefined variable's error lists the frames it searched.
# * A shell environment variable substitutes like any state variable.
#
# NOT to be confused with "state_test", the C++ diagnostic program built from
# state_test.cpp in this directory: that one constructs engine objects and
# prints what it gets, for a person to read, and asserts nothing (see the
# `smoke` target in tst/Makefile). This is the regression suite. The ".sh"
# is the only thing distinguishing them — the same collision as
# eval_test/eval_test.sh, documented there first.
#
# Usage: ./state_test.sh (needs ktext on PATH)
# Exit code: 0 if all tests pass, 1 otherwise.
PASS=0
FAIL=0
KTEXT=ktext
export KT_STATE_PROBE="probe-value" # for the environment-variable case
red=$'\033[31m'
green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'
# check_eq NAME EXPECTED KTEXT_ARGS... — trimmed stdout equals EXPECTED.
check_eq() {
local name="$1" expected="$2"
shift 2
local out status
out=$("$KTEXT" "$@" 2>/dev/null)
status=$?
out=$(printf '%s' "$out" | sed -e 's/[ \t]*$//' | grep -v '^$')
if [ $status -eq 0 ] && [ "$out" = "$expected" ]; then
echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
else
echo "${red}FAIL${reset} $name"
echo " expected: [$expected]"; echo " got: [$out] (exit $status)"
FAIL=$((FAIL+1))
fi
}
# check_error NAME PATTERN KTEXT_ARGS... — nonzero exit, PATTERN in the
# message. Wrap-insensitive: error prose is justified to 80 columns, so a
# phrase may wrap anywhere.
check_error() {
local name="$1" pattern="$2"
shift 2
local out status
out=$("$KTEXT" "$@" 2>&1)
status=$?
if [ $status -eq 0 ]; then
echo "${red}FAIL${reset} $name — expected an error but ktext succeeded"
FAIL=$((FAIL+1)); return
fi
if printf '%s' "$out" | tr '\n' ' ' | tr -s ' ' | grep -qF "$pattern"; then
echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
else
echo "${red}FAIL${reset} $name — expected error to contain [$pattern]"
echo " got: $(printf '%s' "$out" | sed 's/\x1b\[[0-9;]*m//g' | tr '\n' ' ' | head -c 200)"
FAIL=$((FAIL+1))
fi
}
echo "${bold}@@@state tests${reset}"
echo "=============="
echo
echo "-- setting and substituting --"
check_eq " 1. :value sets; *name* substitutes in a body" \
"[hello]" \
--klammersets none -s '@@@state V :value hello @@@ @@f : [*V*] @@ @f@' -d
# Documents TODAY'S behaviour: a top-level *name* of a document-declared
# variable is left as written (bodies substitute; the top level does not).
check_eq " 2. a top-level *name* does not substitute (current behaviour)" \
"[*V*]" \
--klammersets none -s '@@@state V :value hello @@@ [*V*]' -d
check_eq " 3. an environment variable substitutes" \
"[probe-value]" \
--klammersets none -s '@@f : [*KT_STATE_PROBE*] @@ @f@' -d
echo
echo "-- replacing --"
check_eq " 4. :replace replaces an existing value" \
"[b]" \
--klammersets none \
-s '@@@state V :value a @@@ @@@state V :replace b @@@ @@f : [*V*] @@ @f@' -d
check_error " 5. redefining without :replace is an error naming the remedy" \
"Use ':replace <new-value>' to replace the current value" \
--klammersets none -s '@@@state V :value a @@@ @@@state V :value b @@@' -d
echo
echo "-- the undefined variable --"
check_error " 6. an undefined *name* in a body is a located error" \
'Variable "Missing" not defined' \
--klammersets none -s '@@f : [*Missing*] @@ @f@' -d
check_error " 7. ... that lists the frames it searched" \
"(searched:" \
--klammersets none -s '@@f : [*Missing*] @@ @f@' -d
echo
echo "=============="
echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}"
[ $FAIL -eq 0 ]