2026-09-07 22:37:05 +02:00
|
|
|
#!/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" '<x>.' '@w-x.'
|
|
|
|
|
check " 2. abbreviated form, comma" '<x>,' '@w-x,'
|
|
|
|
|
check " 3. abbreviated form, semicolon" '<x>;' '@w-x;'
|
|
|
|
|
check " 4. abbreviated form, colon" '<x>:' '@w-x:'
|
|
|
|
|
check " 5. abbreviated form, question mark" '<x>?' '@w-x?'
|
|
|
|
|
check " 6. abbreviated form, exclamation" '<x>!' '@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" "<x>'s" "@w-x's"
|
|
|
|
|
check " 8. bare close, period" '<x>.' '@w x @.'
|
|
|
|
|
check " 9. named close, period" '<x>.' '@w x w@.'
|
|
|
|
|
check "10. named close, comma" '<x>,' '@w x w@,'
|
|
|
|
|
|
|
|
|
|
echo
|
|
|
|
|
echo "-- brackets around an application (( was honoured; [ and { added 2026-09-03) --"
|
|
|
|
|
check "11. parentheses, abbreviated" '(<x>)' '(@w-x)'
|
|
|
|
|
check "12. square brackets, abbreviated" '[<x>]' '[@w-x]'
|
|
|
|
|
check "13. braces, abbreviated" '{<x>}' '{@w-x}'
|
|
|
|
|
check "14. parentheses, bare close" '(<x>)' '(@w x @)'
|
|
|
|
|
check "15. square brackets, bare close" '[<x>]' '[@w x @]'
|
|
|
|
|
check "16. braces, bare close" '{<x>}' '{@w x @}'
|
|
|
|
|
check "17. square brackets, named close" '[<x>]' '[@w x w@]'
|
|
|
|
|
check "18. no spurious space is introduced" '[<x>].' '[@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" '<x>' '@@v.* : <x> @@#-@v@'
|
|
|
|
|
|
2026-10-04 13:18:06 +02:00
|
|
|
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'
|
|
|
|
|
|
2026-09-07 22:37:05 +02:00
|
|
|
echo
|
|
|
|
|
echo "================================================================="
|
|
|
|
|
echo "${bold}Results: $PASS passed, $FAIL failed${reset}"
|
|
|
|
|
[ $FAIL -eq 0 ] && exit 0 || exit 1
|