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
|
|
|
#include <cctype>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
|
|
#include "klammerset_registry.h"
|
|
|
|
|
#include "error.h"
|
|
|
|
|
#include "file.h"
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "util.h"
|
|
|
|
|
#include "show.h"
|
|
|
|
|
#include "katom.h"
|
|
|
|
|
|
|
|
|
|
Klammerset_registry::Klammerset_registry()
|
|
|
|
|
: m_parameters(Parameter_set("symbol | desc :name :author :date :requires :files"))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Klammerset_registry::add(Klammerset klammerset)
|
|
|
|
|
{
|
|
|
|
|
(void)K::log(3, klammerset.m_symbol);
|
|
|
|
|
m_klammersets[klammerset.m_symbol] = klammerset;
|
|
|
|
|
m_symbols.push_back(klammerset.m_symbol);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<Klammerset> Klammerset_registry::add(
|
|
|
|
|
std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms)
|
|
|
|
|
{
|
|
|
|
|
(void)K::log(3, *begin, *(end - 1));
|
|
|
|
|
auto [positional, optional, rest] =
|
|
|
|
|
argument_split(begin + 1, end - 1, m_parameters.m_positional.size());
|
|
|
|
|
auto values = m_parameters.value_map(positional, optional, rest, begin->m_loc);
|
|
|
|
|
|
|
|
|
|
modify_type(katom_t::replaced, begin, end);
|
|
|
|
|
auto next_iter = end;
|
|
|
|
|
ignore_whitespace(next_iter, katoms);
|
|
|
|
|
|
|
|
|
|
std::string symbol = values["symbol"];
|
|
|
|
|
check_symbol(symbol, begin->m_loc);
|
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_declaring_file(symbol, begin->m_loc);
|
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
|
|
|
if (has(symbol)) {
|
|
|
|
|
(void)K::log(2, "Klammerset \"" + symbol + "\" is already loaded; declaration skipped");
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The filename lists follow the standard conventions (spaces allowed,
|
|
|
|
|
// standalone "/" separator). The existence rescue tests names against
|
|
|
|
|
// the declaring file's directory, where relative names are later
|
|
|
|
|
// resolved by Machine::load_klammerset_files.
|
|
|
|
|
fs::path declaring(begin->m_loc.m_filename);
|
|
|
|
|
std::string base_dir =
|
|
|
|
|
fs::exists(declaring) ? declaring.parent_path().string() : "";
|
|
|
|
|
|
|
|
|
|
Klammerset klammerset(symbol, values["desc"], begin->m_loc);
|
|
|
|
|
klammerset.m_name = values["name"];
|
|
|
|
|
klammerset.m_author = values["author"];
|
|
|
|
|
klammerset.m_date = values["date"];
|
|
|
|
|
klammerset.m_requires = resolve_filename_list(values["requires"], base_dir);
|
|
|
|
|
klammerset.m_files = resolve_filename_list(values["files"], base_dir);
|
|
|
|
|
add(klammerset);
|
|
|
|
|
return klammerset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Klammerset_registry::check_symbol(const std::string& symbol, const Locator& loc) const
|
|
|
|
|
{
|
|
|
|
|
bool valid = !symbol.empty() && std::isalpha(static_cast<unsigned char>(symbol[0]));
|
|
|
|
|
for (char c : symbol) {
|
|
|
|
|
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_') {
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!valid) {
|
|
|
|
|
throw Klammerset_error(
|
|
|
|
|
"The klammerset symbol \"" + symbol + "\" is not valid. A symbol begins "
|
|
|
|
|
"with a letter and contains only letters, digits, and underscores.",
|
|
|
|
|
loc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// A klammerset that can be NAMED must live where its name says: symbol X is
|
|
|
|
|
// declared in X/X.k (Andy, 2026-08-15). The convention already governed
|
|
|
|
|
// symbol RESOLUTION; making it a requirement of the declaration too is what
|
|
|
|
|
// turns the symbol into a function of the path -- and that is what lets the
|
|
|
|
|
// already-loaded guard run BEFORE a file is read rather than after, which is
|
|
|
|
|
// the whole of the diamond-":requires" fix. Without it the symbol is known
|
|
|
|
|
// only once the file has been parsed, and by then a second read has already
|
|
|
|
|
// re-executed the declaring file's own definitions.
|
|
|
|
|
//
|
|
|
|
|
// It also continues the reasoning behind symbols-only on the command line
|
|
|
|
|
// (2026-08-14): if every klammerset is reachable by symbol the set of all of
|
|
|
|
|
// them is ENUMERABLE; if every declaration is in X/X.k each one is also
|
|
|
|
|
// IDENTIFIABLE from where it sits.
|
|
|
|
|
//
|
|
|
|
|
// EXEMPT: a declaration that is not in a ".k" file at all. A document may
|
|
|
|
|
// declare a klammerset -- that is how a designer writes one, and kdesc's
|
|
|
|
|
// provenance filter depends on it -- and such a set is local to the document:
|
|
|
|
|
// nothing can ":requires" it, so it has no identity to protect.
|
|
|
|
|
void Klammerset_registry::check_declaring_file(
|
|
|
|
|
const std::string& symbol, const Locator& loc) const
|
|
|
|
|
{
|
|
|
|
|
fs::path declaring(loc.m_filename);
|
|
|
|
|
if (declaring.extension() != ".k") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (declaring.stem() == symbol && declaring.parent_path().filename() == symbol) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
throw Klammerset_error(
|
|
|
|
|
"The klammerset \"" + symbol + "\" must be declared in a file named \""
|
|
|
|
|
+ symbol + "/" + symbol + ".k\", but this declaration is in \""
|
|
|
|
|
+ declaring.filename().string() + "\" (in a directory named \""
|
|
|
|
|
+ declaring.parent_path().filename().string() + "\"). A klammerset that "
|
|
|
|
|
"can be named is identified by where it is, so that a symbol names one "
|
|
|
|
|
"file and one file declares one symbol. A klammerset local to a document "
|
|
|
|
|
"may be declared in the document itself.",
|
|
|
|
|
loc);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
bool Klammerset_registry::has(const std::string& symbol) const
|
|
|
|
|
{
|
|
|
|
|
return m_klammersets.count(symbol) > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Klammerset Klammerset_registry::get(const std::string& symbol, const Locator& loc) const
|
|
|
|
|
{
|
|
|
|
|
if (has(symbol)) {
|
|
|
|
|
return m_klammersets.at(symbol);
|
|
|
|
|
} else {
|
|
|
|
|
throw Klammerset_error("Klammerset " + symbol + " does not exist", loc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- The klammerset search path ---
|
|
|
|
|
|
|
|
|
|
bool is_klammerset_symbol(const std::string& name)
|
|
|
|
|
{
|
|
|
|
|
if (name.empty() || !std::isalpha(static_cast<unsigned char>(name[0]))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (char c : name) {
|
|
|
|
|
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> klammerset_search_dirs(const std::string& local_dir)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> result {};
|
|
|
|
|
// A repeated directory (e.g. the local stage already IS
|
|
|
|
|
// $KLAMMERTEXT_HOME) adds nothing and clutters error messages.
|
|
|
|
|
auto push_unique = [&result](const std::string& dir) {
|
|
|
|
|
std::string canonical = fs::weakly_canonical(dir).string();
|
|
|
|
|
if (!is_in(canonical, result)) {
|
|
|
|
|
result.push_back(canonical);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (!local_dir.empty()) {
|
|
|
|
|
push_unique(local_dir);
|
|
|
|
|
}
|
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
|
|
|
// Read ONCE per process, not once per resolution. A search path decides
|
|
|
|
|
// WHICH FILE a symbol means -- an identity question, not a value one -- so
|
|
|
|
|
// it must not move while a document is being processed: if it did, "which
|
|
|
|
|
// klammerset is X" would depend on evaluation order and "kdesc
|
|
|
|
|
// --klammersets" could not be a complete answer, which is the reason the
|
|
|
|
|
// command line takes symbols only.
|
|
|
|
|
//
|
|
|
|
|
// It could move. The embedded Python shares the process, so an @eval
|
|
|
|
|
// doing os.environ[...] = ... changed what a later getenv here returned;
|
|
|
|
|
// a document could extend its own search path between one declaration and
|
|
|
|
|
// the next. Nothing intended that -- it fell out of a fresh getenv and an
|
|
|
|
|
// in-process interpreter.
|
|
|
|
|
static const std::string paths = [] {
|
|
|
|
|
const char* env = std::getenv("KLAMMERTEXT_KLAMMERSETS");
|
|
|
|
|
if (env && *env) {
|
|
|
|
|
return std::string(env);
|
|
|
|
|
}
|
|
|
|
|
if (const char* home = std::getenv("HOME"); home && *home) {
|
|
|
|
|
return std::string(home) + "/.klammertext/klammersets";
|
|
|
|
|
}
|
|
|
|
|
return std::string();
|
|
|
|
|
}();
|
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
|
|
|
std::stringstream ss(paths);
|
|
|
|
|
std::string dir;
|
|
|
|
|
while (std::getline(ss, dir, ':')) {
|
|
|
|
|
if (!dir.empty()) {
|
|
|
|
|
push_unique(dir);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (const char* kthome = std::getenv(klammertext_home_var.c_str()); kthome && *kthome) {
|
|
|
|
|
push_unique(kthome);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fs::path resolve_klammerset_symbol(
|
|
|
|
|
const std::string& symbol, const std::string& local_dir, const Locator& loc)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> dirs = klammerset_search_dirs(local_dir);
|
|
|
|
|
for (const std::string& dir : dirs) {
|
|
|
|
|
fs::path candidate = fs::path(dir) / symbol / (symbol + ".k");
|
|
|
|
|
if (fs::exists(candidate) && fs::is_regular_file(candidate)) {
|
|
|
|
|
return fs::weakly_canonical(candidate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw Klammerset_error(
|
|
|
|
|
"The klammerset \"" + symbol + "\" was not found. A symbol x names the "
|
|
|
|
|
"declaration file x/x.k in one of the search directories: "
|
|
|
|
|
+ join(dirs, ", ")
|
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
|
|
|
+ ". Enter \"kdesc --klammersets\" to list the available klammersets.",
|
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
|
|
|
loc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string describe_klammerset_search(const std::string& local_dir, int margin)
|
|
|
|
|
{
|
|
|
|
|
std::string tab(margin, ' ');
|
|
|
|
|
std::stringstream ss {};
|
|
|
|
|
std::map<std::string, std::string> first_hit {};
|
|
|
|
|
for (const std::string& dir : klammerset_search_dirs(local_dir)) {
|
|
|
|
|
if (!fs::is_directory(dir)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
for (const auto& entry : fs::directory_iterator(dir)) {
|
|
|
|
|
if (!entry.is_directory()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
std::string symbol = entry.path().filename().string();
|
|
|
|
|
fs::path declaration = entry.path() / (symbol + ".k");
|
|
|
|
|
if (!is_klammerset_symbol(symbol) || !fs::is_regular_file(declaration)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
ss << tab << symbol << sp_arrow << declaration.string();
|
|
|
|
|
if (first_hit.count(symbol)) {
|
|
|
|
|
ss << " (shadowed by " << first_hit[symbol] << ")";
|
|
|
|
|
} else {
|
|
|
|
|
first_hit[symbol] = declaration.string();
|
|
|
|
|
}
|
|
|
|
|
ss << "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (first_hit.empty()) {
|
|
|
|
|
ss << tab << "(no klammersets found)\n";
|
|
|
|
|
}
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string Klammerset_registry::describe(int margin, bool long_format) const
|
|
|
|
|
{
|
|
|
|
|
std::string tab(margin, ' ');
|
|
|
|
|
std::stringstream ss {};
|
|
|
|
|
std::vector<std::string> descs {};
|
|
|
|
|
for (const std::string& symbol : m_symbols) {
|
|
|
|
|
descs.push_back(m_klammersets.at(symbol).m_desc);
|
|
|
|
|
}
|
|
|
|
|
auto symbol_width = max_length(m_symbols);
|
|
|
|
|
auto desc_width = max_length(descs);
|
|
|
|
|
for (const std::string& symbol : m_symbols) {
|
|
|
|
|
const Klammerset& ks = m_klammersets.at(symbol);
|
|
|
|
|
if (long_format) {
|
|
|
|
|
ss << tab << std::setfill(' ') << std::setw(symbol_width) << std::left << symbol << " "
|
|
|
|
|
<< std::setw(desc_width) << std::left << ks.m_desc << " "
|
|
|
|
|
<< ks.m_loc.str() << "\n";
|
|
|
|
|
if (!ks.m_name.empty())
|
|
|
|
|
ss << tab << std::string(symbol_width, ' ') << " name: " << ks.m_name << "\n";
|
|
|
|
|
if (!ks.m_author.empty())
|
|
|
|
|
ss << tab << std::string(symbol_width, ' ') << " author: " << ks.m_author << "\n";
|
|
|
|
|
if (!ks.m_date.empty())
|
|
|
|
|
ss << tab << std::string(symbol_width, ' ') << " date: " << ks.m_date << "\n";
|
|
|
|
|
if (!ks.m_requires.empty())
|
|
|
|
|
ss << tab << std::string(symbol_width, ' ') << " requires: " << join(ks.m_requires, " / ") << "\n";
|
|
|
|
|
if (!ks.m_files.empty())
|
|
|
|
|
ss << tab << std::string(symbol_width, ' ') << " files: " << join(ks.m_files, " / ") << "\n";
|
|
|
|
|
} else {
|
|
|
|
|
ss << tab << std::setfill(' ') << std::setw(symbol_width) << std::left << symbol
|
|
|
|
|
<< sp_arrow << ks << "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
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
|
|
|
|
|
|
|
|
void Klammerset_registry::set_loaded_files(const std::string& symbol, const strings_t& files)
|
|
|
|
|
{
|
|
|
|
|
auto it = m_klammersets.find(symbol);
|
|
|
|
|
if (it != m_klammersets.end()) {
|
|
|
|
|
it->second.m_loaded_files = files;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strings_t Klammerset_registry::loaded_files() const
|
|
|
|
|
{
|
|
|
|
|
strings_t result {};
|
|
|
|
|
for (const auto& [symbol, klammerset] : m_klammersets) {
|
|
|
|
|
result.insert(result.end(),
|
|
|
|
|
klammerset.m_loaded_files.begin(), klammerset.m_loaded_files.end());
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|