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.
216 lines
7.6 KiB
Bash
Executable File
216 lines
7.6 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# check_test.sh — Static checking of klammer applications ("kdiag --check").
|
|
#
|
|
# The engine applies klammers as it meets them, so it can only complain about
|
|
# what it reaches. Two things it therefore never reaches:
|
|
#
|
|
# * the branch of a @cond that is not selected. @cond is a non-strict
|
|
# special form, so an undefined klammer or a wrong argument count sitting
|
|
# in the unselected branch is invisible -- and stays invisible until the
|
|
# day the predicate flips.
|
|
# * a klammer body that this render does not use, including a body defined
|
|
# for a target other than the one being built.
|
|
#
|
|
# Klammertext can check both without running anything, because katom structure
|
|
# is fixed when a file is read: there are no catcodes, so nothing later can
|
|
# change how text already read divides into katoms. Every application written
|
|
# literally in a document or a klammer body can be located and checked against
|
|
# the registry. That is what check_machine() (mac/check.cpp) does and what
|
|
# these tests pin down.
|
|
#
|
|
# What the checker deliberately does not see is also tested: klammers produced
|
|
# by @eval at run time, and the interiors of @eval/@read argument spans and of
|
|
# literal parameters, which are code, filenames, and raw text -- not
|
|
# applications.
|
|
#
|
|
# Engine tier: kdiag loads no klammerset, so every klammer is defined inline.
|
|
# The one case that needs the SKS reads it explicitly with "@read" -- which is
|
|
# how a klammerset reaches kdiag at all: the symbol mechanism is deliberately
|
|
# absent there, so that kdiag still works when symbol resolution is what broke.
|
|
#
|
|
# Usage: ./check_test.sh
|
|
# Exit code: 0 if all tests pass, 1 otherwise.
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
KDIAG=kdiag
|
|
K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set}
|
|
|
|
red=$'\033[31m'
|
|
green=$'\033[32m'
|
|
bold=$'\033[1m'
|
|
reset=$'\033[0m'
|
|
|
|
# check_finds TEST_NAME PATTERN KDIAG_ARGS...
|
|
# --check must exit nonzero and report PATTERN.
|
|
check_finds() {
|
|
local test_name="$1"
|
|
local pattern="$2"
|
|
shift 2
|
|
|
|
local output status
|
|
output=$("$KDIAG" --process --check "$@" 2>&1)
|
|
status=$?
|
|
|
|
if [ $status -eq 0 ]; then
|
|
echo "${red}FAIL${reset} $test_name — expected a diagnostic, got none"
|
|
FAIL=$((FAIL + 1))
|
|
return
|
|
fi
|
|
if echo "$output" | grep -qF "$pattern"; then
|
|
echo "${green}PASS${reset} $test_name"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "${red}FAIL${reset} $test_name — expected report to contain [$pattern]"
|
|
echo " output: $(echo "$output" | head -4)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
# check_clean TEST_NAME KDIAG_ARGS...
|
|
# --check must exit 0 and report no diagnostics.
|
|
check_clean() {
|
|
local test_name="$1"
|
|
shift
|
|
|
|
local output status
|
|
output=$("$KDIAG" --process --check "$@" 2>&1)
|
|
status=$?
|
|
|
|
if [ $status -eq 0 ] && echo "$output" | grep -q "0 diagnostics"; then
|
|
echo "${green}PASS${reset} $test_name"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "${red}FAIL${reset} $test_name — expected a clean check"
|
|
echo " output: $(echo "$output" | head -4)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
# check_count TEST_NAME N KDIAG_ARGS...
|
|
# --check must report exactly N diagnostics.
|
|
check_count() {
|
|
local test_name="$1"
|
|
local expected="$2"
|
|
shift 2
|
|
|
|
local output got
|
|
output=$("$KDIAG" --process --check "$@" 2>&1)
|
|
got=$(echo "$output" | sed -nE 's/^([0-9]+) diagnostics?,.*/\1/p')
|
|
|
|
if [ "$got" = "$expected" ]; then
|
|
echo "${green}PASS${reset} $test_name"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo "${red}FAIL${reset} $test_name — expected $expected diagnostics, got ${got:-none}"
|
|
echo " output: $(echo "$output" | head -4)"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
GREET='@@greet name : Hello, *name*. @@'
|
|
|
|
echo "${bold}Static klammer checking tests${reset}"
|
|
echo "============================="
|
|
echo
|
|
|
|
# --- What the renderer cannot reach ---
|
|
|
|
check_finds " 1. undefined klammer in an unselected @cond branch (in a body)" \
|
|
"@nosuch is not defined" \
|
|
"@@pick p : @cond *p* | @nosuch x @ | ok @ @@ @pick false @"
|
|
|
|
check_finds " 2. wrong arity in an unselected @cond branch (in a body)" \
|
|
"is given 3" \
|
|
"$GREET @@pick p : @cond *p* | @greet a | b | c @ | ok @ @@ @pick false @"
|
|
|
|
# A top-level @cond branch is now checked like any other. It was the one
|
|
# documented gap in the checker: a @cond written at the top level of a DOCUMENT
|
|
# was resolved when the file was READ, so its unselected branch was gone before
|
|
# anything could look at it. Resolving @cond at APPLICATION time instead
|
|
# (notes/Klammertext_improvements.md §4.2, decided 2026-08-15) closed it -- the
|
|
# branches survive read, so both are visible to a pass that applies nothing.
|
|
check_finds " 2a. a top-level @cond branch is checked too" \
|
|
"@nosuch is not defined" \
|
|
"@cond false | @nosuch x @ | ok @"
|
|
check_finds " 2b. ... including the branch that WOULD be selected" \
|
|
"@nosuch is not defined" \
|
|
"@cond true | @nosuch x @ | ok @"
|
|
|
|
check_finds " 3. undefined klammer in a body that is never applied" \
|
|
"@nosuch is not defined" \
|
|
"@@unused : @nosuch x @ @@ nothing applies it"
|
|
|
|
check_finds " 4. the body it was found in is named" \
|
|
"in body of @unused" \
|
|
"@@unused : @nosuch x @ @@ nothing applies it"
|
|
|
|
# --- Arity ---
|
|
|
|
check_finds " 5. too few positional arguments" \
|
|
"needs 2 positional arguments but is given 1" \
|
|
'@@pair a | b : *a**b* @@ @pair x @'
|
|
|
|
check_finds " 6. too many positional arguments" \
|
|
"takes 1 positional argument but is given 2" \
|
|
"$GREET @greet a | b @"
|
|
|
|
check_finds " 7. undefined optional argument" \
|
|
'has no optional argument ":nope"' \
|
|
"$GREET @greet a :nope 1 @"
|
|
|
|
check_finds " 8. the accepted optional arguments are listed" \
|
|
"It accepts: :loud" \
|
|
'@@greet name :loud : *name* @@ @greet a :nope 1 @'
|
|
|
|
check_clean " 9. a rest argument accepts extra positional arguments" \
|
|
'@@many a | rest.rest : *a* @@ @many x | y | z @'
|
|
|
|
# --- Nesting. A bar or an option name belonging to a nested klammer is not
|
|
# this klammer's; the checker counts at depth 0, as @cond does. ---
|
|
|
|
check_clean "10. nested klammer's bars are not counted as the outer's" \
|
|
'@@frac a | b : *a*/*b* @@ @@one x : [*x*] @@ @one @frac 1 | 2 @ @'
|
|
|
|
check_clean "11. nested klammer's option name is not counted as the outer's" \
|
|
'@@inner a :flag : *a* @@ @@outer x : [*x*] @@ @outer @inner q :flag y @ @'
|
|
|
|
# --- Target coverage ---
|
|
|
|
check_finds "12. klammer not defined for a target" \
|
|
'is not defined for the target "tex"' \
|
|
'@@@target html | HTML output @@@ @@@target tex | TeX output @@@ @@only.html : H @@ @only@'
|
|
|
|
check_clean "13. defined for every target is clean" \
|
|
'@@@target html | HTML output @@@ @@@target tex | TeX output @@@ @@both : B @@ @both@'
|
|
|
|
# --- What the checker deliberately does not see ---
|
|
|
|
# In a body, so the @eval is not evaluated at read time: what is being tested
|
|
# is that the checker does not read the eval's ARGUMENT as an application.
|
|
check_clean "14. @eval argument content is code, not applications" \
|
|
'@@w : @eval len("@nosuch") @ @@'
|
|
|
|
check_clean "15. a literal parameter's content is raw text" \
|
|
'@@lit t.literal : *t* @@ @lit @nosuch x @ lit@'
|
|
|
|
# --- Reporting ---
|
|
|
|
check_count "16. a target-independent fault is reported once, not per target" \
|
|
1 \
|
|
'@@@target html | HTML output @@@ @@@target tex | TeX output @@@ @@g : @nosuch@ @@'
|
|
|
|
check_clean "17. a correct document checks clean" \
|
|
"$GREET @greet World @"
|
|
|
|
# An absolute path: "make -C tst test" runs from tst/, and @read resolves
|
|
# against the current directory.
|
|
check_clean "18. the Standard Klammer Set checks clean" \
|
|
"@read $K/sks/sks.k @"
|
|
|
|
echo
|
|
echo "============================="
|
|
echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}"
|
|
[ $FAIL -eq 0 ]
|