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.
223 lines
9.9 KiB
Bash
Executable File
223 lines
9.9 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# target_list_test.sh — A klammer definition may name several targets.
|
|
#
|
|
# @@table.html,tex :: <body> @@
|
|
#
|
|
# One body, several targets. The list is SURFACE SYNTAX: the registry makes
|
|
# one definition per target named, so nothing downstream of registration knows
|
|
# a list was written. That is what these tests pin down — in particular that
|
|
# each member goes through the redefinition transition table on its own, so a
|
|
# list overlapping an existing definition is decided per target rather than
|
|
# all-or-nothing.
|
|
#
|
|
# Why the feature exists: a klammer whose body is an `@eval` cannot have its
|
|
# coverage derived (deciding which targets a Python function answers for is
|
|
# undecidable), so its targets must be DECLARED. Writing one definition per
|
|
# target would then duplicate the body. See notes/target_coverage.md.
|
|
#
|
|
# Two patterns are involved and must not be confused (mac/ktype.h):
|
|
# definition_begin_name — runs through commas; used ONLY for "@@<name>"
|
|
# definition_name — no comma; klammer applications, option names,
|
|
# "*arg*" variables, and the closing delimiters
|
|
# Case 14 guards the second: a comma next to an application is writer text.
|
|
#
|
|
# These are engine tests, so they use --klammersets none and define their own targets
|
|
# inline: a target is a Machine construct (@@@target), not owned by any
|
|
# klammer set.
|
|
#
|
|
# Usage: ./target_list_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/target_list_test_err.$$
|
|
|
|
red=$'\033[31m'
|
|
green=$'\033[32m'
|
|
bold=$'\033[1m'
|
|
reset=$'\033[0m'
|
|
|
|
# ta, tb, tc are independent; td INCLUDES tc, so tc "provides" td and a
|
|
# definition for tc is copied to td (mac/klammer_registry.cpp).
|
|
TARGETS='@@@target ta | Target A @@@
|
|
@@@target tb | Target B @@@
|
|
@@@target tc | Target C @@@
|
|
@@@target td | Target D :includes tc @@@
|
|
'
|
|
|
|
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 TARGET EXPECTED SRC — exit 0, stdout==EXPECTED, no warning.
|
|
check_eq() {
|
|
local name="$1" target="$2" expected="$3" src="$4"
|
|
local out status err
|
|
out=$("$KTEXT" --klammersets none -s "$TARGETS$src" -t "$target" -d 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"
|
|
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
|
|
}
|
|
|
|
# check_warn NAME TARGET EXPECTED SRC — exit 0, stdout==EXPECTED, the override
|
|
# SILENT at the default verbosity and REPORTED at "-v 1" (2026-08-15; see
|
|
# tst/deftype_test.sh's check_warn for why it is no longer a warning).
|
|
check_warn() {
|
|
local name="$1" target="$2" expected="$3" src="$4"
|
|
local out status err
|
|
out=$("$KTEXT" --klammersets none -s "$TARGETS$src" -t "$target" -d 2>"$ERR"); status=$?
|
|
out=$(printf '%s' "$out" | trim)
|
|
if [ $status -ne 0 ]; then
|
|
echo "${red}FAIL${reset} $name — ktext exited $status"; FAIL=$((FAIL+1)); return
|
|
fi
|
|
if [ -s "$ERR" ]; then
|
|
echo "${red}FAIL${reset} $name — the default run was not silent: $(head -1 "$ERR")"
|
|
FAIL=$((FAIL+1)); return
|
|
fi
|
|
"$KTEXT" --klammersets none -s "$TARGETS$src" -t "$target" -d -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
|
|
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 TARGET PATTERN SRC — nonzero exit and PATTERN in the message.
|
|
check_error() {
|
|
local name="$1" target="$2" pattern="$3" src="$4"
|
|
local out status
|
|
out=$("$KTEXT" --klammersets none -s "$TARGETS$src" -t "$target" -d 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 -3)"; FAIL=$((FAIL+1))
|
|
fi
|
|
}
|
|
|
|
echo "${bold}Klammer definition target-list tests${reset}"
|
|
echo "===================================="
|
|
echo
|
|
|
|
# --- One body, several targets ---
|
|
echo "-- the list defines each target named --"
|
|
check_eq " 1. first member gets the body" ta "x B y" \
|
|
'@@f.k : a test @@ @@f.ta,tb :: B @@ x @f@ y'
|
|
check_eq " 2. second member gets the body" tb "x B y" \
|
|
'@@f.k : a test @@ @@f.ta,tb :: B @@ x @f@ y'
|
|
check_error " 3. a target NOT in the list is undefined" tc "not defined for target" \
|
|
'@@f.k : a test @@ @@f.ta,tb :: B @@ x @f@ y'
|
|
check_eq " 4. three members, last one" tc "x B y" \
|
|
'@@f.k : a test @@ @@f.ta,tb,tc :: B @@ x @f@ y'
|
|
check_eq " 5. parameters are inherited from .k for every member" tb "x [q] y" \
|
|
'@@f.k s : a test @@ @@f.ta,tb :: [*s*] @@ x @f q @ y'
|
|
check_eq " 6. a list member propagates to a target that includes it" td "x B y" \
|
|
'@@f.k : a test @@ @@f.ta,tc :: B @@ x @f@ y'
|
|
|
|
# --- Malformed lists ---
|
|
echo
|
|
echo "-- malformed lists --"
|
|
check_error " 7. an unknown target in the list" ta "is not defined" \
|
|
'@@f.ta,nosuch : B @@'
|
|
check_error " 8. a target named twice" ta "more than once" \
|
|
'@@f.ta,ta : B @@'
|
|
check_error " 9. \".k\" in a list" ta "in a list of targets" \
|
|
'@@f.k,ta : a test @@'
|
|
check_error "10. \".o\" in a list" ta "in a list of targets" \
|
|
'@@f.o,ta : an option set @@'
|
|
# "*" is the general target written out: an assertion that the klammer works
|
|
# for EVERY target, including ones not yet defined. In a list it is either
|
|
# redundant or a misunderstanding.
|
|
check_error "10a. \"*\" in a list" ta 'names "*" in a list of targets' \
|
|
'@@f.*,ta : b @@'
|
|
check_eq "10b. \"*\" alone defines every target" tb "x body" \
|
|
'@@f.* : body @@ x @f@'
|
|
check_eq "10c. ... including one it does not name" ta "x body" \
|
|
'@@f.* : body @@ x @f@'
|
|
check_error "11. a trailing comma" ta "is not correctly defined" \
|
|
'@@f.ta, : B @@'
|
|
check_error "12. an empty member" ta "is not correctly defined" \
|
|
'@@f.ta,,tb : B @@'
|
|
|
|
# --- The transition table applies per member, not to the list ---
|
|
echo
|
|
echo "-- redefinition, decided per member --"
|
|
check_error "13. create + list create collides on the shared member" ta "already defined" \
|
|
'@@f.ta : one @@ @@f.ta,tb : two @@ x @f@'
|
|
# The list's default is silently ignored for ta (which already has a create)
|
|
# and creates tb. This is the case a whole-list decision would get wrong.
|
|
check_eq "14. existing create + list default: existing kept" ta "x one" \
|
|
'@@f.ta : one @@ @@f.ta,tb :::: two @@ x @f@'
|
|
check_eq "15. existing create + list default: other member defined" tb "x two" \
|
|
'@@f.ta : one @@ @@f.ta,tb :::: two @@ x @f@'
|
|
check_eq "16. list default + create for one member: replaced" ta "x real" \
|
|
'@@f.ta,tb :::: def @@ @@f.ta : real @@ x @f@'
|
|
check_eq "17. list default + create for one member: other keeps default" tb "x def" \
|
|
'@@f.ta,tb :::: def @@ @@f.ta : real @@ x @f@'
|
|
check_warn "18. list override warns and replaces (first member)" ta "x over" \
|
|
'@@f.k : a test @@ @@f.ta :: one @@ @@f.tb :: two @@ @@f.ta,tb ::: over @@ x @f@'
|
|
check_warn "19. list override warns and replaces (second member)" tb "x over" \
|
|
'@@f.k : a test @@ @@f.ta :: one @@ @@f.tb :: two @@ @@f.ta,tb ::: over @@ x @f@'
|
|
|
|
# --- The comma is a target separator ONLY after "@@" ---
|
|
echo
|
|
echo "-- a comma elsewhere is writer text --"
|
|
check_eq "20. a comma in a body is text" ta "x a,b" \
|
|
'@@f.ta : a,b @@ x @f@'
|
|
check_eq "21. a comma after an argument substitution" ta "x q,tail" \
|
|
'@@f.ta s : *s*,tail @@ x @f q @'
|
|
check_eq "22. a comma after an application" ta "x q, y" \
|
|
'@@f.ta s : *s* @@ x @f q @, y'
|
|
check_eq "23. a general definition is unaffected" ta "x B" \
|
|
'@@f : B @@ x @f@'
|
|
|
|
# --- Diagnostics count what was WRITTEN, not how often it registered ---
|
|
echo
|
|
echo "-- one definition, several registrations --"
|
|
# tc provides td, so "@@f.tc : ..." registers twice from one line. Before
|
|
# this was grouped, the message said "2 definitions" and then printed the one
|
|
# line the author wrote twice, sending them to look for a second.
|
|
check_error "24. the count is of written definitions" ta \
|
|
"and a definition that declares its own parameters" \
|
|
'@@f.k s : d @@ @@f.tc : [*s*] @@'
|
|
check_error "25. ... and the location names its targets" ta "(targets tc, td)" \
|
|
'@@f.k s : d @@ @@f.tc : [*s*] @@'
|
|
# Two definitions in two places are still two.
|
|
check_error "26. distinct places still count separately" ta \
|
|
"2 definitions that declare their own parameters" \
|
|
'@@f.k s : d @@ @@f.ta : a*s* @@ @@f.tb : b*s* @@'
|
|
# The remedy, named in the message rather than implied.
|
|
check_error "27. the message says what to write instead" ta \
|
|
'Write "::" instead of ":"' \
|
|
'@@f.k s : d @@ @@f.tc : [*s*] @@'
|
|
|
|
rm -f "$ERR"
|
|
|
|
echo
|
|
echo "===================================="
|
|
echo "Results: ${PASS} passed, ${FAIL} failed"
|
|
[ "$FAIL" -eq 0 ] || exit 1
|
|
exit 0
|