Files
klammertext/mac/klammer.cpp

680 lines
26 KiB
C++
Raw Normal View History

#include <algorithm>
#include "klammer.h"
#include "show.h"
#include "log.h"
#include "argument_set.h"
#include "eval.h"
#include "util.h"
#include "character.h"
using namespace std::literals;
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)
2026-08-12 17:20:23 +02:00
std::regex Klammer::name_re = std::regex(R"((\w+)(?:\.((?:\w+|\*)(?:,(?:\w+|\*))*))?)");
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)
2026-08-12 17:20:23 +02:00
std::tuple<std::string, strings_t, bool>
parse_name(const Target_registry& targets, const Katom& name_katom)
{
std::string name_with_target = trim_char(name_katom.m_text, '@');
std::smatch match {};
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)
2026-08-12 17:20:23 +02:00
if (!std::regex_match(name_with_target, match, Klammer::name_re)) {
throw Parsing_error(
"The klammer name \"" + name_with_target + "\" is not correctly defined. "
"The form is \"<klammer-name>\" for general klammers or \"<klammer-name>.<target-name>\" "
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)
2026-08-12 17:20:23 +02:00
"for a specialized target. Several targets that share one body are written as a "
"comma-separated list: \"<klammer-name>.<target-name>,<target-name>\". The klammer "
"defined as \"<klammer-name>.k\" specifies the "
"arguments and contains a description of the klammer in the definition body.",
name_katom.m_loc);
}
std::string klammer_name = match[1];
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)
2026-08-12 17:20:23 +02:00
std::string target_part = match[2];
if (target_part.empty()) {
// No suffix at all: the general target, but not a STATEMENT about
// coverage. This is the writer's macro form -- someone defining a
// repeated phrase is not building a klammer set -- so the third
// result is false and the coverage analysis treats the body on its
// merits rather than as an assertion.
return { klammer_name, { Target_registry::general_name }, false };
}
strings_t target_names = regex_split(target_part, std::regex(","));
strings_t seen {};
for (const auto& target_name : target_names) {
if (!targets.has(target_name)) {
throw Target_error(
"The target \"" + target_name + "\" in klammer definition \"" + name_with_target + "\" "
"is not defined. Enter \"kdesc -t\" to see the targets defined by the Standard Klammer Set.",
name_katom.m_loc);
}
if (is_in(target_name, seen)) {
throw Target_error(
"The target \"" + target_name + "\" is named more than once in klammer definition \"" +
name_with_target + "\".",
name_katom.m_loc);
}
seen.push_back(target_name);
}
// A ".k" declaration states ONE interface for every target, and a ".o"
// declares an option set; neither produces output, so neither has any
// meaning as a member of a list of output targets.
if (target_names.size() > 1) {
for (const auto& reserved :
{ Target_registry::declare_name, Target_registry::optionset_name }) {
if (is_in(reserved, target_names)) {
throw Target_error(
"The klammer definition \"" + name_with_target + "\" names \"" + reserved +
"\" in a list of targets. A \"." + reserved + "\" definition declares an "
"interface for all targets rather than producing output for one, so it must "
"be written on its own.",
name_katom.m_loc);
}
}
}
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)
2026-08-12 17:20:23 +02:00
// "*" written out is an ASSERTION: this klammer works for every target,
// including targets that do not exist yet. A list of "all the targets
// defined today" cannot say that, and the difference matters the moment a
// new target is added. It may not appear IN a list -- "all targets and
// also html" is either redundant or a misunderstanding.
bool general_declared = is_in(Target_registry::general_name, target_names);
if (general_declared && target_names.size() > 1) {
throw Target_error(
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)
2026-08-12 17:20:23 +02:00
"The klammer definition \"" + name_with_target + "\" names \"" +
Target_registry::general_name + "\" in a list of targets. \"" +
Target_registry::general_name + "\" already means every target, so it "
"must be written on its own.",
name_katom.m_loc);
}
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)
2026-08-12 17:20:23 +02:00
return { klammer_name, target_names, general_declared };
}
std::tuple<Katom, Parameter_set, katom_list, Locator>
2026-08-06 13:11:37 +02:00
parse_definition_katoms(
const std::string& klammer_name, const std::string& target_name,
const Argtype_registry& argtypes, Option_set_registry& option_sets,
katom_iter& begin, katom_iter& end)
{
(void)K::log(3, *begin, *(end - 1));
katom_iter deftype = std::find_if(
begin, end, [](const Katom& k) { return is_deftype(k.m_type); });
if (deftype == end) {
throw Argument_error(
"The klammer definition does not contain a definition separator that specifies\n"
"how the klammer should be defined. The klammer syntax is:\n\n"
" @@<name>[.<target>] <parameters> : <body> @@ definition\n"
" @@<name>[.<target>] :: <body> @@ instance (uses .k parameters)\n"
" @@<name>[.<target>] ::: <body> @@ override existing definition\n"
" @@<name>[.<target>] <parameters> :::: <body> @@ default (can be overridden)",
begin->m_loc, false);
}
katom_list parameter_katoms(begin, deftype);
// Resolve text-removal markers (#, #[...]#, ##) in the parameter list.
// The interior of a @@...@@ definition is held verbatim until now (so a
// literal klammer body receives its content untouched), which means the
// parameter declarations still contain any removal markers the writer
// used. Apply removal here, to the parameters only -- the body is left
// verbatim by add_target_definition.
mark_ignored_katoms(parameter_katoms);
std::erase_if(parameter_katoms,
[](const Katom& k) { return k.m_type == katom_t::ignored; });
parameter_katoms = trim_whitespace(parameter_katoms);
2026-08-06 13:11:37 +02:00
// "::" and ":::" take their parameters from the ".k" declaration, so a
// parameter list written with them is a mistake -- EXCEPT for an option
// set, which has no separate declaration to inherit from: a set is its
// own declaration, so an override restates what it declares.
bool inherits_parameters = target_name != Target_registry::optionset_name;
if (inherits_parameters &&
(deftype->m_type == katom_t::klammer_instance ||
deftype->m_type == katom_t::klammer_override) &&
!parameter_katoms.empty()) {
std::string sym = deftype->m_type == katom_t::klammer_instance ? "::" : ":::";
throw Definition_error(
"The \"" + klammer_name + "\" klammer uses the \"" + sym + "\" symbol but defines parameters.",
begin->m_loc);
}
2026-08-06 13:11:37 +02:00
// Replace the option sets used in the parameter list with the parameters
// they declare. This is where the difference between an option set and a
// klammer lies: a set is resolved HERE, as the parameter list is read,
// rather than in the fixed-point apply loop, so what it contributes is
// present when the list is parsed. Any other klammer application in a
// parameter list is rejected by the same pass.
option_set_uses_t option_set_uses =
expand_option_sets(parameter_katoms, klammer_name, target_name, option_sets);
Parameter_set parameters(parameter_katoms, argtypes);
2026-08-06 13:11:37 +02:00
stamp_option_set_uses(parameters, option_set_uses);
katom_list body_katoms(deftype + 1, end - 1);
body_katoms = trim_whitespace(body_katoms);
return { *deftype, parameters, body_katoms, begin->m_loc };
}
void Klammer::add_target_definition(
2026-08-06 13:11:37 +02:00
const std::string& target_name, const Argtype_registry& argtypes,
Option_set_registry& option_sets, katom_iter begin, katom_iter end)
{
(void)K::log(3, *begin, *(end-1));
auto [deftype, parameters, body, loc] =
2026-08-06 13:11:37 +02:00
parse_definition_katoms(m_name, target_name, argtypes, option_sets, begin, end);
// msg() << "Klammer " << m_name << " add: " << target_name << "\n";
// parameters.describe_parameters();
std::regex variable_re(R"(\*(\w+)\*)");
int i = 0;
variable_map_t varmap {};
for (const auto& k : body) {
std::smatch match {};
std::string txt = k.m_text;
while (std::regex_search(txt, match, variable_re) &&
(k.m_type == katom_t::karg || k.m_type == katom_t::text)) {
//std::cout << " txt: " << txt << "\n";
std::string varname = match[1];
//m_varmap[target_name][varname].push_back(i);
varmap[varname].push_back(i);
txt = std::regex_replace(txt, std::regex(R"(\*)" + varname + R"(\*)"), "");
}
i++;
}
// m_body[target_name] = body;
m_defs.push_back({target_name, deftype.m_initial_type, parameters, body, varmap, begin->m_loc});
m_defloc[target_name] = begin->m_loc;
m_defmode[target_name] = defmode_from_katom(deftype.m_initial_type);
// std::cout << "Klammer " << m_name << "." << target_name << ": " << m_defloc[target_name] << "\n";
}
void Klammer::remove_target_definition(const std::string& target_name)
{
std::erase_if(m_defs,
[&target_name](const auto& def) { return def.target == target_name; });
m_defloc.erase(target_name);
m_defmode.erase(target_name);
m_body.erase(target_name);
m_varmap.erase(target_name);
}
// Rationalize multiple definitions
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)
2026-08-12 17:20:23 +02:00
// ONE definition in the source can register more than once. A target that
// "provides" another registers both (the SKS's pdf includes tex, so
// "@@fraktur.tex : ..." becomes a tex definition and a pdf one), and so does
// every member of a comma-separated target list. Counting or listing those
// registrations reports work the author did not do: "2 definitions" for a
// single line, followed by that same line printed twice -- which sends the
// reader hunting for a second definition that does not exist.
//
// These two report what was WRITTEN. Registrations are grouped by source
// location, and a location that produced several targets names them, so the
// count and the listing agree with the file.
using location_group = std::pair<std::string, strings_t>;
std::vector<location_group> group_by_location(const auto& components)
{
std::vector<location_group> groups {};
for (const auto& c : components) {
std::string loc = c.loc.desc();
auto it = std::find_if(groups.begin(), groups.end(),
[&loc](const location_group& g) { return g.first == loc; });
if (it == groups.end()) {
groups.push_back({loc, {c.target}});
} else {
it->second.push_back(c.target);
}
}
return groups;
}
int written_count(const auto& components)
{
return static_cast<int>(group_by_location(components).size());
}
std::string error_list(const std::string& label, const auto& components, const std::string& after="")
{
std::stringstream ss {};
ss << label << ":\n";
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)
2026-08-12 17:20:23 +02:00
for (const auto& [loc, targets] : group_by_location(components)) {
ss << " " << loc;
if (targets.size() > 1) {
ss << " (targets " << join(targets, ", ") << ")";
}
ss << "\n";
}
ss << after;
return ss.str();
}
auto Klammer::target_defs(const std::vector<std::string>& target_names)
{
std::vector<Klammer::components> defs {};
for (const auto& target : target_names) {
auto target_defs = collect_if(
m_defs, [&target](const auto& def) { return def.target == target; });
defs.insert(defs.end(), target_defs.begin(), target_defs.end());
}
return defs;
}
auto Klammer::instance_defs()
{
return collect_if(
m_defs,
[] (const auto& def) {
return def.deftype == katom_t::klammer_instance
|| def.deftype == katom_t::klammer_override; });
}
void Klammer::disallow_instances() //Klammer::components declaration)
{
auto instances = instance_defs();
if (!instances.empty()) {
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)
2026-08-12 17:20:23 +02:00
int icount = written_count(instances);
std::stringstream ss {};
ss << "There " << to_be(icount) << " " << icount << " "
<< plural("instance", icount) << " (defined by \"::\"), but "
<< "no declarations (defined by a \".k\" target)";
throw Definition_error(
error_list(ss.str(), instances), instances[0].loc, false);
}
}
bool Klammer::copy_to_instances(const Target_registry& targets)
{
auto instances = instance_defs();
if (!instances.empty()) {
auto definitions = collect_if(
m_defs,
[] (const auto& def) {
return def.deftype != katom_t::klammer_instance
&& def.deftype != katom_t::klammer_override; });
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)
2026-08-12 17:20:23 +02:00
int dcount = written_count(definitions);
if (dcount != 1) {
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)
2026-08-12 17:20:23 +02:00
int icount = written_count(instances);
std::stringstream ss {};
ss << "There " << to_be(icount) << " " << icount << " "
<< plural("instance", icount) << " (defined by \"::\"), but "
<< dcount << " "<< plural("definition", dcount) << " (defined by \":\")";
throw Definition_error(
error_list(ss.str(), definitions), definitions[0].loc, false);
} else {
copy_components(definitions[0].parameters, m_defs, targets);
return true;
}
} else {
return false;
}
}
void Klammer::copy_components(
const Parameter_set& parameters, const std::vector<Klammer::components>& cs,
const Target_registry& targets)
{
(void)K::log(4);
for (const auto& target_name : targets.m_names) {
if (target_name == Target_registry::declare_name ||
2026-08-06 13:11:37 +02:00
target_name == Target_registry::general_name ||
target_name == Target_registry::optionset_name) {
continue;
}
m_parameters = parameters;
}
for (const auto& c : cs) {
m_body[c.target] = c.body;
m_body_generic[c.target] = (c.target == Target_registry::general_name);
m_varmap[c.target] = c.varmap;
}
}
// Verify that there is no more than one general klammer definition
void Klammer::check_for_multiple_general_klammers()
{
auto general_klammers = target_defs({Target_registry::general_name});
if (general_klammers.size() > 1) {
throw Definition_error(
error_list(
"There is more than one general klammer (a klammer in which no target is defined)",
general_klammers),
general_klammers[0].loc, false);
}
}
void Klammer::check_for_declaration_and_definitions()
{
auto declares = target_defs({Target_registry::declare_name});
if (!declares.empty()) {
std::vector<Klammer::components> definitions {};
for (const auto& def : m_defs) {
if (def.target != Target_registry::declare_name) {
if (def.deftype == katom_t::klammer_definition ||
def.deftype == katom_t::klammer_default) {
definitions.push_back(def);
}
}
}
if (!definitions.empty()) {
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)
2026-08-12 17:20:23 +02:00
int defsize = written_count(definitions);
std::string desc = defsize == 1 ? "a definition that declares its own parameters"
: std::to_string(defsize) + " definitions that declare their own parameters";
throw Definition_error(
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)
2026-08-12 17:20:23 +02:00
error_list("A klammer has both a \".k\" declaration and " + desc +
".\nWrite \"::\" instead of \":\" so the definition takes its parameters "
"from the declaration",
definitions),
declares[0].loc, false);
}
}
}
// If a general definition exists, use it for targets not defined, but check signatures
void Klammer::copy_general_klammer_to_undefined(const Target_registry& targets)
{
(void)K::log(4);
auto general_klammers = target_defs({Target_registry::general_name});
auto declares = target_defs({Target_registry::declare_name});
// Check matching signatures (though this case already handled)
if (general_klammers.size() == 1) {
if (declares.empty() && !m_parameters.m_katoms.empty()) {
auto general_parameters = general_klammers[0].parameters;
if (general_parameters != m_parameters) {
// m_parameters.describe_parameters();
throw Definition_error(
error_list("The general parameters are different than the defined parameters",
general_klammers),
m_parameters.m_katoms[0].m_loc, false);
}
}
const auto& [target, deftype, parameters, body, varmap, loc] = general_klammers[0];
if (m_parameters.m_katoms.empty()) {
m_parameters = parameters;
}
for (const auto& target_name : targets.m_names) {
// std::cout << "General copy, considering " << target_name << "\n";
2026-08-06 13:11:37 +02:00
// "k" and "o" declare interfaces rather than produce output, so a
// general body is never copied to them.
if (m_body.count(target_name) == 0 &&
target_name != Target_registry::declare_name &&
target_name != Target_registry::optionset_name) {
// std::cout << " Copying to " << target_name << "\n";
m_body[target_name] = body;
m_body_generic[target_name] = true; // general body -> writer content
m_defloc[target_name] = loc;
m_varmap[target_name] = varmap;
}
}
}
}
// Three declaration cases: none, one, many
2026-08-06 13:11:37 +02:00
namespace {
// A compact, plain-text rendering of ONE definition's parameter list, for
// diagnostics that must show how two definitions differ.
// Klammer::signature_text() cannot serve here: it renders the rationalized
// m_parameters, which is exactly what does not exist yet when the per-target
// lists disagree.
std::string parameter_signature(const Parameter_set& parameters)
{
std::string result {};
auto add = [&result](const std::string& s) {
if (!result.empty()) result += " ";
result += s;
};
auto typed = [](const Parameter& p) {
return p.m_argtype.m_name == default_argtype
? p.m_name : p.m_name + "." + p.m_argtype.m_name;
};
bool first = true;
for (const auto& pos : parameters.m_positional) {
add(first ? typed(pos) : "| " + typed(pos));
first = false;
}
for (const auto& rest : parameters.m_rest)
add(typed(rest));
for (const auto& opt : parameters.m_optional)
add(":" + typed(opt)
+ (opt.m_default.empty() ? "" : " " + opt.m_default));
return result.empty() ? "(no parameters)" : result;
}
} // namespace
void Klammer::no_declarations(const Target_registry& targets)
{
(void)K::log(4);
// std::cout << boldblack << "No declarations\n" << black;
check_for_multiple_general_klammers();
std::vector<Klammer::components> defs {};
std::vector<std::string> target_names = targets.applicable();
std::vector<Parameter_set> all_parameter_sets {};
// Are all parameters the same?
for (const auto& def : m_defs) {
if (std::ranges::find(target_names, def.target) != target_names.end()) {
// std::cout << " Found: " << def.target << "\n";
all_parameter_sets.push_back(def.parameters);
} else {
// std::cout << " Not found: " << def.target << "\n";
}
}
if (!all_equal<Parameter_set>(all_parameter_sets)) {
2026-08-06 13:11:37 +02:00
// Show each target's own parameter list, not just its location: with
// more than two targets the designer would otherwise have to diff the
// definitions by hand to find which one drifted.
std::stringstream ss {};
ss << "The parameters of klammer \"" << m_name
<< "\" are not the same for every target,\n"
"and there is no declaration (.k) target to define them once:\n";
for (const auto& def : m_defs) {
if (std::ranges::find(target_names, def.target) == target_names.end())
continue;
std::string target = def.target;
target.resize(std::max(target.size(), size_t(6)), ' ');
ss << " " << target << " " << parameter_signature(def.parameters)
<< "\n " << def.loc.desc() << "\n";
}
ss << "Use a .k target to declare the parameters and describe the klammer,\n"
"and \"::\" with no parameters for each target's definition.";
throw Definition_error(ss.str(), m_defs[0].loc, false);
} else {
// std::cout << " All equal\n";
copy_components(m_defs[0].parameters, m_defs, targets);
}
copy_general_klammer_to_undefined(targets);
}
void Klammer::one_declaration(const Target_registry& targets, const Klammer::components& declare)
{
(void)K::log(4);
// std::cout << boldblack << "One declaration\n" << black;
check_for_multiple_general_klammers();
check_for_declaration_and_definitions();
copy_components(declare.parameters, m_defs, targets);
copy_general_klammer_to_undefined(targets);
}
void Klammer::many_declarations(const std::vector<Klammer::components>& declares)
{
(void)K::log(4);
// std::cout << boldblack << "Many declarations\n" << black;
throw Definition_error(
error_list("More than one declaration (.k) klammer", declares),
declares[0].loc, false);
}
void Klammer::rationalize(const Target_registry& targets)
{
(void)K::log(3, m_name);
auto declares = target_defs({Target_registry::declare_name});
auto declare_count = declares.size();
if (declare_count == 0) {
disallow_instances();
if (!copy_to_instances(targets)) {
no_declarations(targets);
}
} else if (declare_count == 1) {
one_declaration(targets, declares[0]);
} else {
many_declarations(declares);
}
}
std::string klammer_name_from_katom(const std::string& s, const Locator& loc)
{
std::regex rgx(R"(@(\w+).*)");
std::smatch match {};
if (std::regex_match(s, match, rgx)) {
return match[1];
} else {
throw Definition_error("The form of the klammer name " + q_(s) + " is not correct", loc);
}
}
void label(const std::string& s)
{
int w = 13;
std::cout << std::right << std::setw(w) << std::setfill(' ') << s << ": ";
}
void show_args(const std::string& label_text, const std::vector<Argument>& arguments)
{
if (!arguments.empty()) {
label(label_text);
for (const auto& a : arguments) {
std::cout << a << " ";
}
std::cout << '\n';
}
}
strings_t Klammer::get_target_names() const
{
strings_t names {};
for (const auto& [target, body] : m_body) {
std::stringstream ss {};
// ss << name << target.m_loc.m_line;
ss << target;
names.push_back(ss.str());
}
return names; // return join(names, ","s);
}
strings_t Klammer::get_locations()
{
strings_t locs {};
for (auto [target, locator] : m_defloc) {
std::cout << target << right_arrow << locator << "\n";
}
return {};
}
std::string Klammer::signature_text() const
{
std::string result {};
bool has_pos = false;
bool has_opt = false;
for (const auto& pos : m_parameters.m_positional) {
result += pos.m_name;
std::string type = pos.m_argtype.m_name;
if (type != default_argtype) {
result += "." + type;
}
result += " | ";
has_pos = true;
}
if (result.size() >= 2)
result.resize(result.size() - 2);
auto opt_count = m_parameters.m_optional.size();
if (opt_count <= 3 && !has_pos) {
result += " ";
}
for (const auto& opt : m_parameters.m_optional) {
if (opt_count > 3) {
result += "\n :" + opt.m_name;
} else {
result += ":" + opt.m_name;
}
std::string type = opt.m_argtype.m_name;
if (type != default_argtype) {
result += "." + type;
}
// result += ":" + opt.m_name + "." + opt.m_argtype.m_name;
if (!opt.m_default.empty()) {
result += " " + italic_on() + opt.m_default + italic_off();
}
result += " ";
has_opt = true;
}
result = trim_right(result);
if (has_pos) {
result = " " + result;
}
if (opt_count > 3) {
result += "\n";
} else if (has_opt or has_pos) {
result += " ";
}
result += "@\n";
return result;
}
std::string Klammer::description_text() const
{
std::string result = " [" + m_name + ": no description]";
if (m_body.contains("k")) {
result = to_string(m_body.at("k"), true);
result = justify(result, 80, 1);
}
return result;
}
2026-08-06 13:11:37 +02:00
// Which option sets this klammer's parameters came from, and which of their
// defaults it overrode. A reader of the signature sees the effective
// interface; this says how much of it the klammer shares with other klammers,
// which is the reason for declaring a set in the first place.
std::string Klammer::option_set_text() const
{
std::vector<std::string> sets {};
std::map<std::string, std::vector<std::string>> overridden {};
for (const auto& opt : m_parameters.m_optional) {
if (opt.m_option_set.empty()) continue;
if (!is_in(opt.m_option_set, sets)) {
sets.push_back(opt.m_option_set);
}
if (opt.m_default_overridden) {
overridden[opt.m_option_set].push_back(":" + opt.m_name);
}
}
if (sets.empty()) return "";
std::vector<std::string> descriptions {};
for (const auto& set : sets) {
std::string desc = set;
if (overridden.count(set) > 0) {
desc += " (" + join(overridden[set], ", ") + " defaulted here)";
}
descriptions.push_back(desc);
}
return "\n Option sets: " + join(descriptions, ", ");
}
std::string Klammer::describe(int margin) const
{
std::string result {};
2026-08-06 13:11:37 +02:00
result += "@" + m_name + signature_text() + description_text() + option_set_text();
result = add_margin(result, margin) + "\n";
return result;
}