Option sets: a .o target for shared parameters
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)
This commit is contained in:
127
mac/klammer.cpp
127
mac/klammer.cpp
@@ -41,7 +41,10 @@ parse_name(const Target_registry& targets, const Katom& name_katom)
|
||||
}
|
||||
|
||||
std::tuple<Katom, Parameter_set, katom_list, Locator>
|
||||
parse_definition_katoms(const std::string& klammer_name, const Argtype_registry& argtypes, katom_iter& begin, katom_iter& end)
|
||||
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(
|
||||
@@ -68,7 +71,13 @@ parse_definition_katoms(const std::string& klammer_name, const Argtype_registry&
|
||||
std::erase_if(parameter_katoms,
|
||||
[](const Katom& k) { return k.m_type == katom_t::ignored; });
|
||||
parameter_katoms = trim_whitespace(parameter_katoms);
|
||||
if ((deftype->m_type == katom_t::klammer_instance ||
|
||||
// "::" 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 ? "::" : ":::";
|
||||
@@ -76,7 +85,16 @@ parse_definition_katoms(const std::string& klammer_name, const Argtype_registry&
|
||||
"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);
|
||||
@@ -84,11 +102,12 @@ parse_definition_katoms(const std::string& klammer_name, const Argtype_registry&
|
||||
}
|
||||
|
||||
void Klammer::add_target_definition(
|
||||
const std::string& target_name, const Argtype_registry& argtypes, katom_iter begin, katom_iter end)
|
||||
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, argtypes, begin, end); // targets, begin, end);
|
||||
parse_definition_katoms(m_name, target_name, argtypes, option_sets, begin, end);
|
||||
// msg() << "Klammer " << m_name << " add: " << target_name << "\n";
|
||||
// parameters.describe_parameters();
|
||||
|
||||
@@ -208,7 +227,8 @@ void Klammer::copy_components(
|
||||
(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::general_name ||
|
||||
target_name == Target_registry::optionset_name) {
|
||||
continue;
|
||||
}
|
||||
m_parameters = parameters;
|
||||
@@ -243,7 +263,6 @@ void Klammer::check_for_declaration_and_definitions()
|
||||
if (def.target != Target_registry::declare_name) {
|
||||
if (def.deftype == katom_t::klammer_definition ||
|
||||
def.deftype == katom_t::klammer_default) {
|
||||
msg() << def << "\n";
|
||||
definitions.push_back(def);
|
||||
}
|
||||
}
|
||||
@@ -286,7 +305,11 @@ void Klammer::copy_general_klammer_to_undefined(const Target_registry& targets)
|
||||
}
|
||||
for (const auto& target_name : targets.m_names) {
|
||||
// std::cout << "General copy, considering " << target_name << "\n";
|
||||
if (m_body.count(target_name) == 0 && target_name != Target_registry::declare_name) {
|
||||
// "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
|
||||
@@ -299,6 +322,39 @@ void Klammer::copy_general_klammer_to_undefined(const Target_registry& targets)
|
||||
|
||||
// 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);
|
||||
@@ -318,14 +374,24 @@ void Klammer::no_declarations(const Target_registry& targets)
|
||||
}
|
||||
}
|
||||
if (!all_equal<Parameter_set>(all_parameter_sets)) {
|
||||
// std::cout << " Not all equal\n";
|
||||
throw Definition_error(
|
||||
error_list("There is no declaration (.k) target for klammer \"" + m_name + "\"\n"
|
||||
"but the parameters of all targets are not the same",
|
||||
m_defs,
|
||||
"Use a .k klammer to define the parameters and describe the klammer,\n"
|
||||
"with \"::\" and no parameters for all targets."),
|
||||
m_defs[0].loc, false);
|
||||
// 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);
|
||||
@@ -485,10 +551,39 @@ std::string Klammer::description_text() const
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
result += "@" + m_name + signature_text() + description_text() + option_set_text();
|
||||
result = add_margin(result, margin) + "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user