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.
133 lines
8.1 KiB
Bash
Executable File
133 lines
8.1 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# escape_test.sh — Regression tests for the Klammermachine's target character
|
|
# escaping MECHANISM.
|
|
#
|
|
# Kept SKS-INDEPENDENT on purpose: tst/ tests the engine only (mac/target.cpp,
|
|
# mac/machine.cpp), not the SKS. A target is a MACHINE construct — declared
|
|
# with the @@@target system command, not owned by any klammer set — so the
|
|
# idiomatic engine-level test defines its own fixture target inline and loads
|
|
# no klammer set (`--klammersets none`); it does not "avoid" the SKS so much as have no
|
|
# need of it. Target `t` here escapes `& -> AMP`, `_ -> UND`, `\ -> BSL`
|
|
# (arbitrary tokens, easy to assert). The SKS's own targets (tex, html) and
|
|
# the specific characters they declare are exercised by the SKS suite.
|
|
#
|
|
# The `:escape` parameter on `@@@target` declares characters special in a
|
|
# target's output and their replacements. Escaping must reach writer content
|
|
# wherever it appears — including the body of a GENERAL klammer (no target
|
|
# suffix), which is target-agnostic writer text — while leaving target-native
|
|
# content alone:
|
|
#
|
|
# - general klammer body -> writer content, ESCAPE it
|
|
# - target-specific body (.t) -> already in target form, LEAVE it
|
|
# - ^'...'^ literal span -> raw target markup, LEAVE it
|
|
# - nested target-native klammer -> e.g. @n@ -> \newline, LEAVE it
|
|
# - @eval/@read/@cond arg span -> code/path/predicate, LEAVE it
|
|
# - klammer-producing @eval result -> klammer output, LEAVE it
|
|
#
|
|
# Cases 8-9 pin a regression: escaping a general klammer body once corrupted
|
|
# the @eval CODE inside it (an underscore in "offer.Price_list(K)" became a
|
|
# KTESC marker -> Python AttributeError). Only the general body's own literal
|
|
# writer text is escaped; @eval/@read/@cond argument spans (code, filenames,
|
|
# predicates) are skipped, and the Klammertext a nested @eval produces is
|
|
# klammer output, not writer text, so it is never escaped. Mechanism:
|
|
# Klammer::m_body_generic + the hook in Machine::apply_klammer; see the
|
|
# "Target character escaping" section of CLAUDE.md.
|
|
#
|
|
# Usage: ./escape_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/escape_test_err.$$
|
|
|
|
red=$'\033[31m'
|
|
green=$'\033[32m'
|
|
bold=$'\033[1m'
|
|
reset=$'\033[0m'
|
|
|
|
# strip leading/trailing blank lines and trailing whitespace
|
|
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 EXPECTED KTEXT_ARGS... — exit 0 and stdout==EXPECTED.
|
|
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 [ "$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
|
|
}
|
|
|
|
echo "${bold}Target character escaping tests (Machine mechanism, fixture target 't')${reset}"
|
|
echo "======================================================================="
|
|
echo
|
|
|
|
# A self-contained fixture target, defined inline via the @@@target system
|
|
# command, escaping & _ \ to distinct tokens. --klammersets none loads no klammer set,
|
|
# so nothing below depends on the SKS.
|
|
T='@@@target t | test target :escape & AMP _ UND \ BSL @@@'
|
|
|
|
check_eq " 1. top-level text: & escaped" 'A AMP B' --klammersets none -t t -s "$T A & B"
|
|
check_eq " 2. general klammer body: & escaped" 'A AMP B' --klammersets none -t t -s "$T @@g : A & B @@ @g@"
|
|
check_eq " 3. general klammer body: _ escaped" 'AUNDB' --klammersets none -t t -s "$T @@g : A_B @@ @g@"
|
|
check_eq " 4. general klammer body: backslash escaped" 'aBSLb' --klammersets none -t t -s "$T @@g : a\\b @@ @g@"
|
|
check_eq " 5. target-specific body: NOT escaped" 'A & B' --klammersets none -t t -s "$T @@g.k : d @@ @@g.t :: A & B @@ @g@"
|
|
check_eq " 6. general body ^'...'^ literal: NOT escaped" 'a&b' --klammersets none -t t -s "$T @@g : ^'a&b'^ @@ @g@"
|
|
check_eq " 7. nested target-native klammer survives escape" 'X AMP Y \newline Z' --klammersets none -t t -s "$T @@n.t : \\newline @@ @@g : X & Y @n@ Z @@ @g@"
|
|
# 8-9: @eval inside a general body. Code (with underscores) must not be
|
|
# escaped or Python breaks; the Klammertext it returns is klammer output and
|
|
# must not be escaped either. chr(64) builds a literal '@' so the returned
|
|
# klammer call is not parsed as one in this source line.
|
|
check_eq " 8. general body @eval code NOT escaped" '3' --klammersets none -t t -s "$T @@g : @eval (1).__add__(2) @ @@ @g@"
|
|
check_eq " 9. general body @eval klammer result NOT escaped" '\textbf{hi}' --klammersets none -t t -s "$T @@b.k s : d @@ @@b.t :: \\textbf{*s*} @@ @@g : @eval chr(64)+'b hi '+chr(64) @ @@ @g@"
|
|
# 10-11: an @eval result that still holds klammers is a GENERATOR (Klammertext
|
|
# with data) -- its writer text is escaped for the target before the klammers
|
|
# are applied; a result with no klammers is a RENDERER (final markup) -- left
|
|
# untouched. The signal is "does the read-back result contain a klammer".
|
|
check_eq "10. @eval generator: klammer result's data escaped" '[a AMP b]' --klammersets none -t t -s "$T @@wrap z : [*z*] @@ @@g : @eval chr(64)+'wrap a & b '+chr(64) @ @@ @g@"
|
|
check_eq "11. @eval renderer: final markup NOT escaped" 'raw & markup' --klammersets none -t t -s "$T @@g : @eval 'raw & markup' @ @@ @g@"
|
|
|
|
# 12-18: quoted KLAMMERTEXT specials (^@ ^| ^# ^^ ^: ^*) and ^'...'^ literal
|
|
# regions. The katomizer strips the "^"; hide_special_katoms() and
|
|
# mark_literal_katoms() then hold the character as a KTESC marker so it stays
|
|
# inert through sub-Machine re-katomization and the @eval result read-back,
|
|
# resolving to the literal character at final processing. Regression: after
|
|
# the marker mechanism replaced the old hide/restore pass, a quoted "@"
|
|
# leaked as a bare apply-end katom into re-read text ("A klammer ends
|
|
# without a beginning"). Exact-match expectations also guard against KTESC
|
|
# markers leaking into output.
|
|
check_eq "12. quoted @ | # resolve to the characters" 'x @ | # y' --klammersets none -t t -s "$T x ^@ ^| ^# y"
|
|
check_eq "13. quoted ^ : * resolve to the characters" 'x ^ : * y' --klammersets none -t t -s "$T x ^^ ^: ^* y"
|
|
check_eq "14. ^'...'^ region: specials stay literal" 'a @ | b' --klammersets none -t t -s "$T a ^' @ | '^ b"
|
|
check_eq "15. general body: quoted @ resolves" 'x @ y' --klammersets none -t t -s "$T @@g : x ^@ y @@ @g@"
|
|
# 16: inside an @eval argument span a quoted special reaches the CODE as the
|
|
# character (the span is skipped by hide_special_katoms, like the escape pass).
|
|
check_eq "16. @eval code: quoted : reaches shell as ':'" 'x:y' --klammersets none -t t -s "$T @@g : @eval :shell echo x^:y @ @@ @g@"
|
|
# 17: an @eval result emitting the two characters ^ @ is re-read as a quoted
|
|
# special and survives to the output as a literal @ (the generator idiom for
|
|
# a literal @; a bare @ in a result is a parse error by design).
|
|
check_eq "17. @eval result ^@ survives read-back as @" '@' --klammersets none -t t -s "$T @@g : @eval chr(94)+chr(64) @ @@ @g@"
|
|
# 18: a bare-Python :after_apply phase receives the RESOLVED result text
|
|
# (K_result) and its return is taken as raw target text, not re-read as
|
|
# Klammertext -- a resolved @ in the result must not be re-parsed.
|
|
check_eq "18. :after_apply phase: raw result, @ intact" 'A @ B' --klammersets none -t u -s '@@@target u | up :after_apply string.capwords @@@ a ^@ b'
|
|
|
|
rm -f "$ERR"
|
|
|
|
echo
|
|
echo "============================================"
|
|
echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}"
|
|
[ $FAIL -eq 0 ]
|