Target coverage: a klammer states the targets it serves

kdesc gains --coverage, which reports for every klammer the set of targets it
can render to, and — the point of it — which klammers' coverage cannot be
derived and must therefore be declared.  Three rules: coverage is DERIVED
where the definitions determine it (a general body of klammer calls covers
the intersection of what those klammers cover, by a greatest fixpoint after
loading), DECLARED where the engine cannot interpret what decides it (an
@eval body, whose targets are undecidable), and UNKNOWN where nothing is
written — which never means "deliberately unavailable".

Two new spellings in a definition's name.  A comma-separated target list,
"@@table.html,tex :: ...", gives one body several targets; it is surface
syntax, expanded at registration, and each member goes through the
redefinition rules on its own.  And "@@date.* :: ..." writes the general
target out, asserting that the klammer works for EVERY target including ones
not yet defined — a stronger claim than a list of the targets defined today,
and the one target declaration that could be mechanically falsified.

The Standard Klammer Set was swept accordingly: it now has no general
definitions at all, every klammer names the targets it serves, six use ".*",
and tex and pdf are at zero undecided.

kdesc's flags are reorganised on two rules: a flag reached for often gets a
single letter (-k klammers, -t targets, -c characters, -i input), a more
specialised topic a multi-letter name (--argtypes, --katoms, --rewrite,
--optionsets, --coverage, --klammerset, --font); and -v says how much to show
about PROCESSING, never what the RESULT contains — so the katom regex column
is "--katoms full" and the coverage detail "--coverage all".  NOTE: "-k" now
lists klammers (optionally filtered by a name/description search); the katom
table moved to "--katoms".

Fixes carried along: an option written with no value crashed the command with
SIGSEGV instead of reporting the mistake; two required positional arguments
never parsed; kdesc and kdiag printed an error and exited 0; and definition
diagnostics counted registrations rather than what was written, so one line
could be reported as two definitions and then printed twice.

Four new test suites: target_list, coverage, command_option, kdesc.

(from dev 46f54080bd9a)
This commit is contained in:
2026-08-12 17:20:23 +02:00
parent 61987c8b1f
commit 59c1599bc9
49 changed files with 1960 additions and 431 deletions

View File

@@ -145,7 +145,8 @@ void Argv::opt(const std::string& name, const std::string& desc, const std::stri
update_width(arg);
}
void Argv::var(const std::string& name, const std::string& desc)
void Argv::var(const std::string& name, const std::string& desc,
const std::string& parameter)
{
(void)K::log(2, name, desc);
Arg arg {};
@@ -153,6 +154,9 @@ void Argv::var(const std::string& name, const std::string& desc)
arg.m_name = name;
arg.m_desc = get_regex_desc(desc);
arg.m_syntax = arg.symbol();
if (!parameter.empty()) {
arg.m_syntax += " [<" + parameter + ">]";
}
m_args[name] = arg;
m_names.push_back(name);
m_var_names.push_back(name);
@@ -291,18 +295,23 @@ void Argv::parse_optional(strings_t& words, string_map& named_args)
// std::cout << " Found: " << words[index] << "\n";
//std::vector<std::string> opt_args = {};
index++;
/*
// An option declared with opt() takes a value, so the word after
// the flag must exist. Written last with nothing after it -- a
// bare "kdesc -v", or "ktext doc.kt -t" -- this read past the end
// of the vector and the command died with SIGSEGV, naming
// nothing. (Two bounds checks used to sit here, commented out;
// they would have returned a HALF-PARSED option rather than
// reporting the mistake, so this reports it instead.) A valueless
// option is declared with flag(), not opt().
if (index >= words.size()) {
break;
throw Argument_error(
"The option " + flag_name(opt) + " needs a value: " +
m_args[opt].m_syntax + ". Enter \"" +
file_basename(command_name) + "\" for the list of arguments.",
Locator(), false);
}
*/
std::string opt_arg = words[index] + " ";
index++;
/*
if (index >= words.size()) {
break;
}
*/
std::regex opt_regex = m_args[opt].m_rgx;
// std::cout << "Regex match? " << std::regex_match(trim(opt_arg), opt_regex) << "\n";
@@ -346,7 +355,19 @@ void Argv::parse_positional(const std::string& command, //strings_t words,
throw Argument_error(ss.str(), Locator(), false);
}
named_args[req] = substring;
pos_args = rest;
// Drop the whitespace that separated this positional from the next.
// regex_split_prefix() requires its match at position 0 and returns
// the remainder verbatim, so without this the SECOND required
// argument is always "not found": its pattern is offered " second"
// and cannot match a leading space.
//
// Trimming here rather than at the top of the loop is deliberate: the
// first positional still receives pos_args exactly as before, so a
// command with ONE required argument -- which is every command that
// ships (ktext's `filenames`, kdiag's `input`) -- parses
// bit-identically. Only the case that never worked changes.
size_t next = rest.find_first_not_of(" \t");
pos_args = (next == std::string::npos) ? "" : rest.substr(next);
}
// std::cout << "Remaining words: " << words << "\n" << pos_args << "\n";
}
@@ -436,7 +457,13 @@ void Argv::parse(int argc, char* argv[], bool full_parse)
{
(void)K::log(2);
command_name = argv[0];
describe();
// No describe() here. A call sat at this point, before any value has
// been assigned, so it could only ever print a table of "<none>" -- and
// because set_verbose_level() parses a throwaway Argv before the real
// one, EVERY command would print that table on every run. Silencing
// Argv::describe() itself was the wrong half of the fix: it also silenced
// the callers that legitimately want it (kdesc -v, argv_test). The
// caller decides; parse() does not print.
auto input_args = classify_arguments(argc, argv, full_parse);
// std::cout << "parse() classify:\n" << input_args << "\n";
@@ -610,10 +637,11 @@ void Argv::describe()
std::stringstream ss {};
ss << " "<< std::setfill(' ') << std::setw(width)
<< m_args[name].symbol() << " : " << value;
// The line was built and then dropped, so this printed nothing at
// all -- which is why "kdesc -v 1" showed no arguments and argv_test,
// whose whole job is to display what Argv parsed, was silent. The
// callers already decide whether to call it (the commands gate it on
// verbose_level > 0), so it prints unconditionally here.
std::cout << ss.str() << "\n";
}
/*
if (verbose_level == 1) {
std::cout << "\n";
}
*/
}