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)
680 lines
26 KiB
C++
680 lines
26 KiB
C++
#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;
|
|
|
|
std::regex Klammer::name_re = std::regex(R"((\w+)(?:\.((?:\w+|\*)(?:,(?:\w+|\*))*))?)");
|
|
|
|
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. 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_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);
|
|
}
|
|
}
|
|
}
|
|
// "*" 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 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_names, general_declared };
|
|
}
|
|
|
|
std::tuple<Katom, Parameter_set, katom_list, Locator>
|
|
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);
|
|
// "::" 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);
|
|
}
|
|
// 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);
|
|
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(
|
|
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] =
|
|
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
|
|
|
|
// 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& [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()) {
|
|
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; });
|
|
int dcount = written_count(definitions);
|
|
if (dcount != 1) {
|
|
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 ||
|
|
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()) {
|
|
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 \".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";
|
|
// "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
|
|
|
|
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)) {
|
|
// 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;
|
|
}
|
|
|
|
|
|
// 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 {};
|
|
result += "@" + m_name + signature_text() + description_text() + option_set_text();
|
|
result = add_margin(result, margin) + "\n";
|
|
return result;
|
|
}
|
|
|
|
|