2026-07-18 18:48:23 +02:00
|
|
|
#!/bin/bash
|
|
|
|
|
#
|
|
|
|
|
# deftype_test.sh — Regression tests for the four klammer definition modes
|
|
|
|
|
# and the 9-combination redefinition transition table.
|
|
|
|
|
#
|
|
|
|
|
# The four syntactic definition modes (increasing colon count = decreasing
|
|
|
|
|
# binding strength, the "conservation of syntax" ladder):
|
|
|
|
|
#
|
|
|
|
|
# : create, defines its own parameters — error if already defined
|
|
|
|
|
# :: create, inherits parameters from `.k` — error if already defined
|
|
|
|
|
# ::: override, inherits parameters from `.k` — replaces existing (warns)
|
|
|
|
|
# :::: default, defines its own parameters — silently replaced by create
|
|
|
|
|
#
|
|
|
|
|
# At the transition level (mac/deftype.cpp) `:` and `::` are both `def_create`;
|
|
|
|
|
# the three redefinition modes are create / override (`:::`) / default (`::::`).
|
|
|
|
|
# `defmode_transition(existing, incoming)` governs all 9 (existing x incoming)
|
|
|
|
|
# combinations. This test pins down every entry of that table plus the basic
|
|
|
|
|
# behavior of each mode, none of which tst/klammer_test.cpp currently covers.
|
|
|
|
|
#
|
Klammerset: the @@@klammerset construct, its search path, and const correctness
The @@@klammerset system command formally declares a klammerset — a
named, logically related group of klammer definitions — with an
operative, idempotent declaration (:requires and :files load in order
at the declaration point, relative to the declaring file). A bare
symbol given to ktext -k, kdesc --input, or :requires resolves to
x/x.k on the search path: the document's directory, then
KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset
lists the available sets. sks/sks.k is the first declared klammerset,
so `-k sks` loads the SKS by name. The engine's lookup classes were
renamed *_set → *_registry to keep the two concepts apart, and the
whole C++ tree now follows standard const-correctness conventions.
tst/ gains klammerset_test.sh (18 cases).
(from dev 64b1abf23e56)
2026-07-30 23:50:07 +02:00
|
|
|
# {replace,warn,message} semantics (mac/klammer_registry.cpp):
|
2026-07-18 18:48:23 +02:00
|
|
|
# !replace && message -> Definition_error (nonzero exit)
|
|
|
|
|
# !replace && empty -> silently keep the existing definition
|
|
|
|
|
# replace && warn -> emit a warning, then replace
|
|
|
|
|
# replace && !warn -> replace silently
|
|
|
|
|
#
|
|
|
|
|
# Usage: ./deftype_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/deftype_test_err.$$
|
|
|
|
|
|
|
|
|
|
red=$'\033[31m'
|
|
|
|
|
green=$'\033[32m'
|
|
|
|
|
bold=$'\033[1m'
|
|
|
|
|
reset=$'\033[0m'
|
|
|
|
|
|
|
|
|
|
# strip leading/trailing blank lines and trailing whitespace
|
2026-07-19 17:44:03 +02:00
|
|
|
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] }'; }
|
2026-07-18 18:48:23 +02:00
|
|
|
|
|
|
|
|
# check_eq NAME EXPECTED KTEXT_ARGS... — exit 0, stdout==EXPECTED, and NO warning.
|
|
|
|
|
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 printf '%s' "$err" | grep -qiF "warning"; then
|
|
|
|
|
echo "${red}FAIL${reset} $name — unexpected warning"; 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
|
|
|
|
|
}
|
|
|
|
|
|
An output policy for the three commands, and @cond as a true special form
A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
2026-08-16 01:37:59 +02:00
|
|
|
# check_warn NAME EXPECTED KTEXT_ARGS... — exit 0, stdout==EXPECTED, the
|
|
|
|
|
# override REPORTED at "-v 1" and SILENT at the default verbosity.
|
|
|
|
|
#
|
|
|
|
|
# It was a warning until 2026-08-15. ":::" exists to override, and a klammer
|
|
|
|
|
# set the user did not write may be overridden by design (TODO #33), so the
|
|
|
|
|
# notice fired on the sanctioned use of a feature: a warning nobody can act on
|
|
|
|
|
# is not a warning. It is still worth reporting, because the definition being
|
|
|
|
|
# replaced usually lives in another file in another klammerset, which the user
|
|
|
|
|
# cannot see from what they wrote -- so it is "-v 1" (see the output policy in
|
|
|
|
|
# CLAUDE.md).
|
2026-07-18 18:48:23 +02:00
|
|
|
check_warn() {
|
|
|
|
|
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"; FAIL=$((FAIL+1)); return
|
|
|
|
|
fi
|
An output policy for the three commands, and @cond as a true special form
A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
2026-08-16 01:37:59 +02:00
|
|
|
if [ -s "$ERR" ]; then
|
|
|
|
|
echo "${red}FAIL${reset} $name — the default run was not silent: $(head -1 "$ERR")"
|
|
|
|
|
FAIL=$((FAIL+1)); return
|
|
|
|
|
fi
|
|
|
|
|
"$KTEXT" "$@" -v 1 >/dev/null 2>"$ERR"
|
|
|
|
|
err=$(cat "$ERR")
|
|
|
|
|
if ! printf '%s' "$err" | grep -qiF "overridden"; then
|
|
|
|
|
echo "${red}FAIL${reset} $name — no override reported at -v 1"; FAIL=$((FAIL+1)); return
|
2026-07-18 18:48:23 +02:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# check_error NAME PATTERN KTEXT_ARGS... — nonzero exit and PATTERN in the message.
|
|
|
|
|
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 echo "$out" | grep -qF "$pattern"; then
|
|
|
|
|
echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
|
|
|
|
|
else
|
|
|
|
|
echo "${red}FAIL${reset} $name — expected error to contain [$pattern]"
|
|
|
|
|
echo " output: $(echo "$out" | head -2)"; FAIL=$((FAIL+1))
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "${bold}Klammer definition-mode tests${reset}"
|
|
|
|
|
echo "============================="
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
# --- Each mode defines and applies in isolation ---
|
|
|
|
|
echo "-- isolated modes --"
|
|
|
|
|
check_eq " 1. : create defines + applies" "cq" -s '@@x s : c*s* @@ @x q @' -d
|
|
|
|
|
check_eq " 2. :: instance inherits from .k" "iq" -s '@@x.k s : d @@ @@x :: i*s* @@ @x q @' -d
|
|
|
|
|
check_error " 3. :: without a .k declaration is an error" "no declarations" -s '@@x :: i*s* @@ @x q @' -d
|
|
|
|
|
check_eq " 4. :::: default defines + applies" "eq" -s '@@x s :::: e*s* @@ @x q @' -d
|
|
|
|
|
check_eq " 5. ::: override alone becomes the def" "oq" -s '@@x.k s : d @@ @@x ::: o*s* @@ @x q @' -d
|
|
|
|
|
|
|
|
|
|
# --- The 9-entry transition table: (existing x incoming) ---
|
|
|
|
|
echo
|
|
|
|
|
echo "-- transition table (existing + incoming) --"
|
|
|
|
|
check_error " 6. create + create = error" "already defined" -s '@@x s : one*s* @@ @@x s : two*s* @@ @x q @' -d
|
|
|
|
|
check_warn " 7. create + override = warn+repl" "twoq" -s '@@x.k s : d @@ @@x :: one*s* @@ @@x ::: two*s* @@ @x q @' -d
|
|
|
|
|
check_eq " 8. create + default = keep exist" "oneq" -s '@@x s : one*s* @@ @@x s :::: two*s* @@ @x q @' -d
|
|
|
|
|
check_error " 9. override + create = error" "already overridden" -s '@@x.k s : d @@ @@x ::: one*s* @@ @@x :: two*s* @@ @x q @' -d
|
|
|
|
|
check_warn "10. override + override = warn+repl" "twoq" -s '@@x.k s : d @@ @@x ::: one*s* @@ @@x ::: two*s* @@ @x q @' -d
|
|
|
|
|
check_eq "11. override + default = keep exist" "oneq" -s '@@x.k s : d @@ @@x ::: one*s* @@ @@x :::: two*s* @@ @x q @' -d
|
|
|
|
|
check_eq "12. default + create = replace (silent)" "twoq" -s '@@x s :::: one*s* @@ @@x s : two*s* @@ @x q @' -d
|
|
|
|
|
check_warn "13. default + override = warn+repl" "twoq" -s '@@x.k s : d @@ @@x :::: one*s* @@ @@x ::: two*s* @@ @x q @' -d
|
|
|
|
|
check_error "14. default + default = error" "already defined" -s '@@x s :::: one*s* @@ @@x s :::: two*s* @@ @x q @' -d
|
|
|
|
|
|
|
|
|
|
rm -f "$ERR"
|
|
|
|
|
|
|
|
|
|
echo
|
|
|
|
|
echo "============================="
|
|
|
|
|
echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}"
|
|
|
|
|
[ $FAIL -eq 0 ]
|