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:
128
mac/klammer.cpp
128
mac/klammer.cpp
@@ -10,34 +10,82 @@
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
std::regex Klammer::name_re = std::regex(R"((\w+)(?:\.(\w+))?)");
|
||||
std::regex Klammer::name_re = std::regex(R"((\w+)(?:\.((?:\w+|\*)(?:,(?:\w+|\*))*))?)");
|
||||
|
||||
std::tuple<std::string, std::string>
|
||||
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 {};
|
||||
|
||||
|
||||
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>\" "
|
||||
"for a specialized target. The klammer defined as \"<klammer-name>.k\" specifies the "
|
||||
"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];
|
||||
std::string target_name = match[2];
|
||||
if (target_name.empty()) {
|
||||
target_name = Target_registry::general_name;
|
||||
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 };
|
||||
}
|
||||
if (!targets.has(target_name)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
// "*" 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(
|
||||
"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.",
|
||||
"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);
|
||||
}
|
||||
return { klammer_name, target_name };
|
||||
return { klammer_name, target_names, general_declared };
|
||||
}
|
||||
|
||||
std::tuple<Katom, Parameter_set, katom_list, Locator>
|
||||
@@ -146,12 +194,50 @@ void Klammer::remove_target_definition(const std::string& target_name)
|
||||
|
||||
// Rationalize multiple definitions
|
||||
|
||||
// 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";
|
||||
for (const auto& c : components) {
|
||||
ss << " " << c.loc.desc() << "\n";
|
||||
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();
|
||||
@@ -182,7 +268,7 @@ void Klammer::disallow_instances() //Klammer::components declaration)
|
||||
{
|
||||
auto instances = instance_defs();
|
||||
if (!instances.empty()) {
|
||||
int icount = instances.size();
|
||||
int icount = written_count(instances);
|
||||
std::stringstream ss {};
|
||||
ss << "There " << to_be(icount) << " " << icount << " "
|
||||
<< plural("instance", icount) << " (defined by \"::\"), but "
|
||||
@@ -201,9 +287,9 @@ bool Klammer::copy_to_instances(const Target_registry& targets)
|
||||
[] (const auto& def) {
|
||||
return def.deftype != katom_t::klammer_instance
|
||||
&& def.deftype != katom_t::klammer_override; });
|
||||
int dcount = definitions.size();
|
||||
int dcount = written_count(definitions);
|
||||
if (dcount != 1) {
|
||||
int icount = instances.size();
|
||||
int icount = written_count(instances);
|
||||
std::stringstream ss {};
|
||||
ss << "There " << to_be(icount) << " " << icount << " "
|
||||
<< plural("instance", icount) << " (defined by \"::\"), but "
|
||||
@@ -268,11 +354,13 @@ void Klammer::check_for_declaration_and_definitions()
|
||||
}
|
||||
}
|
||||
if (!definitions.empty()) {
|
||||
auto defsize = definitions.size();
|
||||
std::string desc = defsize == 1 ? "a definition" :
|
||||
std::to_string(defsize) + " definitions";
|
||||
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(
|
||||
error_list("A klammer has both a declaration (.k) as well as " + desc + "\n(instances are defined by \"::\")",
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user