A named group of optional parameters, declared once and used by several
klammers, so a writer learns one vocabulary instead of a spelling per
klammer. The "o" target is a pseudo-target beside "k": "k" declares a
klammer's interface and documents it, "o" declares an option interface and
documents it, and neither produces output for any target.
@@caption_args.o :caption :number.bool true :caption_side.side
: Arguments that define a caption for a block element @@
@@code.k :filename @hpos_args :hpos left @ @caption_args :caption_side top @
| text.literal : A source file displayed verbatim @@
A set is used only in the parameter list of a ".k" declaration -- the one
place a klammer's interface is declared once for all of its targets -- and
is resolved as that list is read. Names and types come from the set; a
default may be overridden where it is used. A klammer application in a
parameter list is now a definition-time error.
The SKS gains the sets caption_args and hpos_args (:hpos and :offset), and
@table, @image, @image_grid, @reference and @show gain .k declarations. A
distance is no longer written as a position: :hpos 4em is rejected, and the
same layout is :hpos left :offset 4em. Code listings are numbered by
default, like tables and figures.
New engine sources mac/option_set{,_registry}.{h,cpp}; tst/ ships two more
suites, option_set_test.sh and signature_test.sh (twelve in all).
(from dev 34e536cb0329)
592 lines
22 KiB
C++
592 lines
22 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+))?)");
|
|
|
|
std::tuple<std::string, std::string>
|
|
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 "
|
|
"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;
|
|
}
|
|
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);
|
|
}
|
|
return { klammer_name, target_name };
|
|
}
|
|
|
|
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
|
|
|
|
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";
|
|
}
|
|
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 = instances.size();
|
|
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 = definitions.size();
|
|
if (dcount != 1) {
|
|
int icount = instances.size();
|
|
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()) {
|
|
auto defsize = definitions.size();
|
|
std::string desc = defsize == 1 ? "a definition" :
|
|
std::to_string(defsize) + " definitions";
|
|
throw Definition_error(
|
|
error_list("A klammer has both a declaration (.k) as well as " + desc + "\n(instances are defined by \"::\")",
|
|
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;
|
|
}
|
|
|
|
|