tst/ now carries all three engine regression suites (cond, deftype, escape) and the Makefile runs them. The README gains a Provenance section: this repository is a curated snapshot of the private development tree, stamped with the development commit it was assembled from. (from dev f6463478da4c)
133 lines
7.9 KiB
Bash
Executable File
133 lines
7.9 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# escape_test.sh — Regression tests for the Klammermachine's target character
|
|
# escaping MECHANISM.
|
|
#
|
|
# Kept SKS-INDEPENDENT on purpose: tst/ tests the engine only (mac/target.cpp,
|
|
# mac/machine.cpp), not the SKS. A target is a MACHINE construct — declared
|
|
# with the @@@target system command, not owned by any klammer set — so the
|
|
# idiomatic engine-level test defines its own fixture target inline and loads
|
|
# no klammer set (`-k none`); it does not "avoid" the SKS so much as have no
|
|
# need of it. Target `t` here escapes `& -> AMP`, `_ -> UND`, `\ -> BSL`
|
|
# (arbitrary tokens, easy to assert). The SKS's own targets (tex, html) and
|
|
# the specific characters they declare are exercised by the SKS suite.
|
|
#
|
|
# The `:escape` parameter on `@@@target` declares characters special in a
|
|
# target's output and their replacements. Escaping must reach writer content
|
|
# wherever it appears — including the body of a GENERAL klammer (no target
|
|
# suffix), which is target-agnostic writer text — while leaving target-native
|
|
# content alone:
|
|
#
|
|
# - general klammer body -> writer content, ESCAPE it
|
|
# - target-specific body (.t) -> already in target form, LEAVE it
|
|
# - ^'...'^ literal span -> raw target markup, LEAVE it
|
|
# - nested target-native klammer -> e.g. @n@ -> \newline, LEAVE it
|
|
# - @eval/@read/@cond arg span -> code/path/predicate, LEAVE it
|
|
# - klammer-producing @eval result -> klammer output, LEAVE it
|
|
#
|
|
# Cases 8-9 pin a regression: escaping a general klammer body once corrupted
|
|
# the @eval CODE inside it (an underscore in "offer.Price_list(K)" became a
|
|
# KTESC marker -> Python AttributeError). Only the general body's own literal
|
|
# writer text is escaped; @eval/@read/@cond argument spans (code, filenames,
|
|
# predicates) are skipped, and the Klammertext a nested @eval produces is
|
|
# klammer output, not writer text, so it is never escaped. Mechanism:
|
|
# Klammer::m_body_generic + the hook in Machine::apply_klammer; see the
|
|
# "Target character escaping" section of CLAUDE.md.
|
|
#
|
|
# Usage: ./escape_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/escape_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}Target character escaping tests (Machine mechanism, fixture target 't')${reset}"
|
|
echo "======================================================================="
|
|
echo
|
|
|
|
# A self-contained fixture target, defined inline via the @@@target system
|
|
# command, escaping & _ \ to distinct tokens. -k none loads no klammer set,
|
|
# so nothing below depends on the SKS.
|
|
T='@@@target t | test target :escape & AMP _ UND \ BSL @@@'
|
|
|
|
check_eq " 1. top-level text: & escaped" 'A AMP B' -k none -t t -s "$T A & B"
|
|
check_eq " 2. general klammer body: & escaped" 'A AMP B' -k none -t t -s "$T @@g : A & B @@ @g@"
|
|
check_eq " 3. general klammer body: _ escaped" 'AUNDB' -k none -t t -s "$T @@g : A_B @@ @g@"
|
|
check_eq " 4. general klammer body: backslash escaped" 'aBSLb' -k none -t t -s "$T @@g : a\\b @@ @g@"
|
|
check_eq " 5. target-specific body: NOT escaped" 'A & B' -k none -t t -s "$T @@g.k : d @@ @@g.t :: A & B @@ @g@"
|
|
check_eq " 6. general body ^'...'^ literal: NOT escaped" 'a&b' -k none -t t -s "$T @@g : ^'a&b'^ @@ @g@"
|
|
check_eq " 7. nested target-native klammer survives escape" 'X AMP Y \newline Z' -k none -t t -s "$T @@n.t : \\newline @@ @@g : X & Y @n@ Z @@ @g@"
|
|
# 8-9: @eval inside a general body. Code (with underscores) must not be
|
|
# escaped or Python breaks; the Klammertext it returns is klammer output and
|
|
# must not be escaped either. chr(64) builds a literal '@' so the returned
|
|
# klammer call is not parsed as one in this source line.
|
|
check_eq " 8. general body @eval code NOT escaped" '3' -k none -t t -s "$T @@g : @eval (1).__add__(2) @ @@ @g@"
|
|
check_eq " 9. general body @eval klammer result NOT escaped" '\textbf{hi}' -k none -t t -s "$T @@b.k s : d @@ @@b.t :: \\textbf{*s*} @@ @@g : @eval chr(64)+'b hi '+chr(64) @ @@ @g@"
|
|
# 10-11: an @eval result that still holds klammers is a GENERATOR (Klammertext
|
|
# with data) -- its writer text is escaped for the target before the klammers
|
|
# are applied; a result with no klammers is a RENDERER (final markup) -- left
|
|
# untouched. The signal is "does the read-back result contain a klammer".
|
|
check_eq "10. @eval generator: klammer result's data escaped" '[a AMP b]' -k none -t t -s "$T @@wrap z : [*z*] @@ @@g : @eval chr(64)+'wrap a & b '+chr(64) @ @@ @g@"
|
|
check_eq "11. @eval renderer: final markup NOT escaped" 'raw & markup' -k none -t t -s "$T @@g : @eval 'raw & markup' @ @@ @g@"
|
|
|
|
# 12-18: quoted KLAMMERTEXT specials (^@ ^| ^# ^^ ^: ^*) and ^'...'^ literal
|
|
# regions. The katomizer strips the "^"; hide_special_katoms() and
|
|
# mark_literal_katoms() then hold the character as a KTESC marker so it stays
|
|
# inert through sub-Machine re-katomization and the @eval result read-back,
|
|
# resolving to the literal character at final processing. Regression: after
|
|
# the marker mechanism replaced the old hide/restore pass, a quoted "@"
|
|
# leaked as a bare apply-end katom into re-read text ("A klammer ends
|
|
# without a beginning"). Exact-match expectations also guard against KTESC
|
|
# markers leaking into output.
|
|
check_eq "12. quoted @ | # resolve to the characters" 'x @ | # y' -k none -t t -s "$T x ^@ ^| ^# y"
|
|
check_eq "13. quoted ^ : * resolve to the characters" 'x ^ : * y' -k none -t t -s "$T x ^^ ^: ^* y"
|
|
check_eq "14. ^'...'^ region: specials stay literal" 'a @ | b' -k none -t t -s "$T a ^' @ | '^ b"
|
|
check_eq "15. general body: quoted @ resolves" 'x @ y' -k none -t t -s "$T @@g : x ^@ y @@ @g@"
|
|
# 16: inside an @eval argument span a quoted special reaches the CODE as the
|
|
# character (the span is skipped by hide_special_katoms, like the escape pass).
|
|
check_eq "16. @eval code: quoted : reaches shell as ':'" 'x:y' -k none -t t -s "$T @@g : @eval :shell echo x^:y @ @@ @g@"
|
|
# 17: an @eval result emitting the two characters ^ @ is re-read as a quoted
|
|
# special and survives to the output as a literal @ (the generator idiom for
|
|
# a literal @; a bare @ in a result is a parse error by design).
|
|
check_eq "17. @eval result ^@ survives read-back as @" '@' -k none -t t -s "$T @@g : @eval chr(94)+chr(64) @ @@ @g@"
|
|
# 18: a bare-Python :after_apply phase receives the RESOLVED result text
|
|
# (K_result) and its return is taken as raw target text, not re-read as
|
|
# Klammertext -- a resolved @ in the result must not be re-parsed.
|
|
check_eq "18. :after_apply phase: raw result, @ intact" 'A @ B' -k none -t u -s '@@@target u | up :after_apply string.capwords @@@ a ^@ b'
|
|
|
|
rm -f "$ERR"
|
|
|
|
echo
|
|
echo "============================================"
|
|
echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}"
|
|
[ $FAIL -eq 0 ]
|