#!/bin/bash # # katomize_test.sh — Katomization: what abuts a klammer delimiter, and the # katom rewrite rules that decide it. # # SKS-independent by construction: every case runs with --klammersets none # and defines the one klammer it needs inline, because what is under test is # where katom boundaries fall, not what any klammer renders. # # WHAT ABUTS "@" (Andy, 2026-09-03; notes/Klammertext_improvements.md §4.3). # An author writes a klammer inside brackets, or with a sentence's # punctuation after it, and expects the delimiter not to swallow the # neighbour. The rule is a WHITELIST of characters that may abut, and the # whitelist is safe for a structural reason worth stating once: # # None of ( [ { ) ] } . , : ; ? ! ' can appear in a definition_name # ([a-zA-Z][a-zA-Z0-9_.]*) or in an abbreviated argument, so a delimiter # meeting one of them has no second reading. # # That is also why LETTERS can never join the list, and why §4.3's "clean # rule" (a delimiter terminates the preceding token unconditionally) cannot # be unconditional: "x@" is a legitimate token — the named closing # delimiter — so "x@i" is ambiguous in the grammar, not merely unhandled. # Case 20 pins that as CURRENT BEHAVIOUR rather than as a defect. # # Usage: ./katomize_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} red=$'\033[31m' green=$'\033[32m' bold=$'\033[1m' reset=$'\033[0m' # An inline klammer, so nothing here depends on the SKS. It wraps its # argument in < > so a swallowed bracket is visible in the result rather # than merely absent. DEF='@@w.* arg : <*arg*> @@' # check NAME EXPECTED INPUT — render INPUT and compare the whole output. check() { local name="$1" expected="$2" input="$3" local out # A space between the definition and the input: without it the "@@" # ending the definition abuts the input's first character, which is # exactly the class of thing under test and breaks the fixture instead # of the feature. The space does not survive into the output. out=$("$KTEXT" --klammersets none -s "$DEF $input" -d 2>/dev/null) 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 } # check_warn NAME INPUT — the render must warn "Word not parsed". Used for # the cases that are NOT in the whitelist, so a later widening of the rule # has to come here and say so deliberately. check_warn() { local name="$1" input="$2" local err err=$("$KTEXT" --klammersets none -s "$DEF $input" -d 2>&1 >/dev/null \ | tr '\n' ' ' | tr -s ' ') case "$err" in *"Word not parsed"*) echo "${green}PASS${reset} $name"; PASS=$((PASS+1));; *) echo "${red}FAIL${reset} $name — expected a \"Word not parsed\" warning" echo " got: [$err]"; FAIL=$((FAIL+1));; esac } echo echo "${bold}Katomization: what may abut a klammer delimiter${reset}" echo "=================================================================" echo echo "-- trailing punctuation after each of the four application forms --" check " 1. abbreviated form, period" '.' '@w-x.' check " 2. abbreviated form, comma" ',' '@w-x,' check " 3. abbreviated form, semicolon" ';' '@w-x;' check " 4. abbreviated form, colon" ':' '@w-x:' check " 5. abbreviated form, question mark" '?' '@w-x?' check " 6. abbreviated form, exclamation" '!' '@w-x!' # The apostrophe is in the whitelist and must stay: "@w x @'s" is ordinary # English prose and worked before the 2026-09-03 widening. Andy confirmed # it explicitly when the bracket characters were added. check " 7. apostrophe (possessive) is in the list" "'s" "@w-x's" check " 8. bare close, period" '.' '@w x @.' check " 9. named close, period" '.' '@w x w@.' check "10. named close, comma" ',' '@w x w@,' echo echo "-- brackets around an application (( was honoured; [ and { added 2026-09-03) --" check "11. parentheses, abbreviated" '()' '(@w-x)' check "12. square brackets, abbreviated" '[]' '[@w-x]' check "13. braces, abbreviated" '{}' '{@w-x}' check "14. parentheses, bare close" '()' '(@w x @)' check "15. square brackets, bare close" '[]' '[@w x @]' check "16. braces, bare close" '{}' '{@w x @}' check "17. square brackets, named close" '[]' '[@w x w@]' check "18. no spurious space is introduced" '[].' '[@w-x].' echo echo "-- the boundary of the whitelist: what is NOT allowed to abut --" # A hyphen after an OPENING name introduces an argument, so it cannot be # text. After a CLOSING delimiter it is unambiguous and could be allowed; # it is not, today, and this case records that rather than endorsing it. check_warn "19. hyphen after a close is still unhandled" '@w x @-y' # The contradiction that bounds the whole rule: "x@" is the named closing # delimiter, so a letter before "@" has two readings and no precedence rule # can separate them. This is the language, not a gap in the whitelist. check_warn "20. a letter before \"@\" stays ambiguous" 'y@w x @' echo echo "-- the rules are AUTHOR-MODE: they never reach @@ or @@@ text --" # A rewrite rule exists to spare an author typing. A designer writing "@@" # or a programmer writing "@@@" needs no such convenience, and in fact needs # none: those forms are whole katom TYPES. Enforced in find_rewrite(), so # no rule can reach them however it is written. # no_rewrite NAME INPUT -- kdiag must report no rule firing at all. no_rewrite() { local name="$1" input="$2" out out=$(kdiag --rewrite "$input" 2>&1 | grep -c "Rewrite \[") if [ "$out" = "0" ]; then echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} $name — $out rule(s) fired on definition-mode text" FAIL=$((FAIL+1)) fi } no_rewrite "21. @@name fires no rule" '@@w.k' no_rewrite "22. a target list fires no rule" '@@w.k,tex' no_rewrite "23. @@@ fires no rule" '@@@target' no_rewrite "24. a named definition close" 'w@@' # Was the ONE violation: "no argument klammer" found "@w@" inside "@@w@@" # and split it -- into the right katoms, but by coincidence. The form is # malformed (a definition needs :, ::, ::: or ::::), so reporting it is # better than silently splitting it. no_rewrite "25. @@w@@ fires no rule either" '@@w@@' check_warn "26. ... and is reported, not split" '@@w@@' # A word can MIX TIERS, so the test is not "starts with @@". A definition # closed flush against a whitespace operator is one word and the rules MUST # see it -- this is real usage, from tst/modulepath_test.sh's fixture, and # the first version of the guard broke it. check "27. @@ glued to #- still splits" '' '@@v.* : @@#-@v@' echo echo "-- what abuts a closing ]# --" # A removal block ends at its "]#"; what follows is the writer's. Until # 2026-10-02 mark_ignored_katoms() read find_span_katoms()'s half-open end # as inclusive and removed the katom abutting the close as well: a sentence # ending "#[ note ]#." lost its period. A following space or newline is # removed by design, which is what hid it. check "28. punctuation after ]# survives" 'a . c' 'a #[gone]#. c' check "29. a letter after ]# survives" 'a x c' 'a #[gone]#x c' check "30. a space after ]# is removed" 'a . c' 'a #[gone]# . c' check "31. nested blocks, punctuation after" 'a . c' 'a #[ #[in]# out ]#. c' echo echo "=================================================================" echo "${bold}Results: $PASS passed, $FAIL failed${reset}" [ $FAIL -eq 0 ] && exit 0 || exit 1