360 lines
16 KiB
C++
360 lines
16 KiB
C++
|
|
#include <sstream>
|
||
|
|
|
||
|
|
#include "option_set_registry.h"
|
||
|
|
#include "argument_set.h"
|
||
|
|
#include "error.h"
|
||
|
|
#include "katom_list.h"
|
||
|
|
#include "klammer.h"
|
||
|
|
#include "log.h"
|
||
|
|
#include "show.h"
|
||
|
|
#include "target_registry.h"
|
||
|
|
#include "util.h"
|
||
|
|
|
||
|
|
// --- Registration ---------------------------------------------------------
|
||
|
|
|
||
|
|
void Option_set_registry::add(
|
||
|
|
const Argtype_registry& argtypes, const std::string& name,
|
||
|
|
katom_iter begin, katom_iter end, katom_list& katoms)
|
||
|
|
{
|
||
|
|
(void)K::log(3, *begin, *(end - 1));
|
||
|
|
restore_initial_type(begin, end);
|
||
|
|
katom_iter definition_begin = begin + 1;
|
||
|
|
katom_iter definition_end = end - 1;
|
||
|
|
auto [deftype, parameters, body, loc] = parse_definition_katoms(
|
||
|
|
name, Target_registry::optionset_name, argtypes, *this,
|
||
|
|
definition_begin, definition_end);
|
||
|
|
|
||
|
|
if (deftype.m_initial_type == katom_t::klammer_instance) {
|
||
|
|
throw Definition_error(
|
||
|
|
"The option set " + q_(name) + " is declared with \"::\", which takes its "
|
||
|
|
"parameters from a \".k\" declaration. An option set IS a declaration: "
|
||
|
|
"it declares its parameters itself, after \":\".",
|
||
|
|
begin->m_loc);
|
||
|
|
}
|
||
|
|
defmode_t incoming_mode = defmode_from_katom(deftype.m_initial_type);
|
||
|
|
if (has(name)) {
|
||
|
|
const Option_set& current = m_option_sets.at(name);
|
||
|
|
const auto& result = defmode_transition(current.m_defmode, incoming_mode);
|
||
|
|
// The transition table is the single statement of the redefinition
|
||
|
|
// policy, for klammers and option sets alike; only the noun in its
|
||
|
|
// messages is specific to what is being redefined.
|
||
|
|
std::string message = string_replace(result.message, "Klammer NAME", "Option set NAME");
|
||
|
|
message = string_replace(message, "klammer NAME", "option set NAME");
|
||
|
|
message = string_replace(message, "NAME", q_(name + ".o"));
|
||
|
|
message = string_replace(message, "AT", current.m_loc.desc());
|
||
|
|
if (!result.replace) {
|
||
|
|
if (message.empty()) { // a default silently superseded
|
||
|
|
modify_type(katom_t::replaced, begin, end);
|
||
|
|
auto next_iter = end;
|
||
|
|
ignore_whitespace(next_iter, katoms);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
throw Definition_error(message, begin->m_loc);
|
||
|
|
}
|
||
|
|
if (result.warn) {
|
||
|
|
warning(message, begin->m_loc);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Optional parameters only. A positional parameter is not writer-facing
|
||
|
|
// -- the author never types its name -- so there is nothing for an option
|
||
|
|
// set to standardize, and a set of them would be a klammer signature
|
||
|
|
// rather than a shared vocabulary.
|
||
|
|
if (!parameters.m_positional.empty() || !parameters.m_rest.empty()) {
|
||
|
|
std::vector<std::string> names {};
|
||
|
|
for (const auto& p : parameters.m_positional) names.push_back(p.m_name);
|
||
|
|
for (const auto& p : parameters.m_rest) names.push_back(p.m_name);
|
||
|
|
throw Definition_error(
|
||
|
|
"The option set " + q_(name) + " declares the positional "
|
||
|
|
+ plural("parameter", static_cast<int>(names.size())) + " "
|
||
|
|
+ join(names, ", ") + ".\n"
|
||
|
|
"An option set declares only optional parameters -- names written with "
|
||
|
|
"a leading \":\".",
|
||
|
|
begin->m_loc, false);
|
||
|
|
}
|
||
|
|
if (parameters.m_optional.empty()) {
|
||
|
|
throw Definition_error(
|
||
|
|
"The option set " + q_(name) + " declares no parameters.\n"
|
||
|
|
"The form is: @@" + name + ".o :name.argtype default ... : <description> @@",
|
||
|
|
begin->m_loc, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
// The members as written: these katoms are what is spliced into the
|
||
|
|
// parameter list of a declaration that uses the set.
|
||
|
|
auto [positional_katoms, member_katoms] =
|
||
|
|
parameter_split(parameters.m_katoms.cbegin(), parameters.m_katoms.cend());
|
||
|
|
(void)positional_katoms; // already rejected above
|
||
|
|
// The two views of the members -- as katoms and as parsed parameters --
|
||
|
|
// are used together when the set is spliced into a parameter list, and
|
||
|
|
// are paired by position.
|
||
|
|
if (member_katoms.size() != parameters.m_optional.size()) {
|
||
|
|
throw Internal_error(
|
||
|
|
"The option set " + q_(name) + " parsed " + std::to_string(parameters.m_optional.size())
|
||
|
|
+ " parameters from " + std::to_string(member_katoms.size()) + " declarations",
|
||
|
|
begin->m_loc);
|
||
|
|
}
|
||
|
|
|
||
|
|
Option_set option_set(name, to_string(body, true), parameters, member_katoms, begin->m_loc);
|
||
|
|
option_set.m_defmode = incoming_mode;
|
||
|
|
if (has(name)) {
|
||
|
|
// A redefinition keeps the users recorded so far: they used the name,
|
||
|
|
// and the name is what they are bound to.
|
||
|
|
option_set.m_users = m_option_sets.at(name).m_users;
|
||
|
|
} else {
|
||
|
|
m_names.push_back(name);
|
||
|
|
}
|
||
|
|
m_option_sets[name] = option_set;
|
||
|
|
|
||
|
|
modify_type(katom_t::replaced, begin, end);
|
||
|
|
auto next_iter = end;
|
||
|
|
ignore_whitespace(next_iter, katoms);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Option_set_registry::has(const std::string& name) const
|
||
|
|
{
|
||
|
|
return m_option_sets.count(name) > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
const Option_set& Option_set_registry::get(const std::string& name, const Locator& loc) const
|
||
|
|
{
|
||
|
|
auto it = m_option_sets.find(name);
|
||
|
|
if (it == m_option_sets.end()) {
|
||
|
|
throw Definition_error("The option set " + q_(name) + " is not declared", loc);
|
||
|
|
}
|
||
|
|
return it->second;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Option_set_registry::add_user(const std::string& set_name, const std::string& klammer_name)
|
||
|
|
{
|
||
|
|
auto it = m_option_sets.find(set_name);
|
||
|
|
if (it != m_option_sets.end() && !is_in(klammer_name, it->second.m_users)) {
|
||
|
|
it->second.m_users.push_back(klammer_name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string Option_set_registry::available() const
|
||
|
|
{
|
||
|
|
return m_names.empty() ? "(none are declared)" : join(m_names, ", ");
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string Option_set_registry::describe(int margin) const
|
||
|
|
{
|
||
|
|
std::string result {};
|
||
|
|
for (const auto& name : m_names) {
|
||
|
|
result += m_option_sets.at(name).describe(margin) + "\n";
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- Use in a parameter list ----------------------------------------------
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// The end of the application opening at `begin`: one past its closing
|
||
|
|
// delimiter. Depth is counted over klammer applications only, which is all
|
||
|
|
// that can nest inside an option-set use.
|
||
|
|
katom_iter application_end(katom_iter begin, katom_iter end)
|
||
|
|
{
|
||
|
|
int depth = 0;
|
||
|
|
for (auto k = begin; k != end; ++k) {
|
||
|
|
if (begin_klammer_apply(*k)) {
|
||
|
|
++depth;
|
||
|
|
} else if (end_klammer_apply(*k)) {
|
||
|
|
if (--depth == 0) return k + 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
throw Definition_error(
|
||
|
|
"The klammer " + q_(trim_char(begin->m_text, '@')) +
|
||
|
|
" in a parameter list is not closed", begin->m_loc);
|
||
|
|
}
|
||
|
|
|
||
|
|
// The parameter name in an option-name katom, which carries the type and any
|
||
|
|
// target as written: ":number.bool" declares the parameter named "number".
|
||
|
|
std::string option_name(const Katom& katom)
|
||
|
|
{
|
||
|
|
std::string name(katom.m_text, 1);
|
||
|
|
auto type = name.find('.');
|
||
|
|
return type == std::string::npos ? name : name.substr(0, type);
|
||
|
|
}
|
||
|
|
|
||
|
|
// How a definition is named in a diagnostic: a general definition has no
|
||
|
|
// target to name, and the pseudo-targets read better with their suffix.
|
||
|
|
std::string definition_name(const std::string& klammer_name, const std::string& target_name)
|
||
|
|
{
|
||
|
|
return target_name == Target_registry::general_name
|
||
|
|
? q_(klammer_name) : q_(klammer_name + "." + target_name);
|
||
|
|
}
|
||
|
|
|
||
|
|
// A klammer application in a parameter list is never legal. Which of the two
|
||
|
|
// ways it is wrong decides what the writer has to do about it, so the message
|
||
|
|
// says which.
|
||
|
|
[[noreturn]] void reject_application(
|
||
|
|
const std::string& name, const std::string& klammer_name,
|
||
|
|
const std::string& target_name, const Option_set_registry& option_sets,
|
||
|
|
const Locator& loc)
|
||
|
|
{
|
||
|
|
std::stringstream ss {};
|
||
|
|
if (option_sets.has(name) && target_name == Target_registry::optionset_name) {
|
||
|
|
ss << "The option set " << q_(name) << " is used in the declaration of the option "
|
||
|
|
<< "set " << q_(klammer_name) << ".\n"
|
||
|
|
<< "An option set is used only in the parameter list of a \".k\" declaration, "
|
||
|
|
<< "so a set does not include another set: a klammer that needs two "
|
||
|
|
<< "vocabularies names two sets, and each set stays a vocabulary that can "
|
||
|
|
<< "be learned whole.";
|
||
|
|
} else if (option_sets.has(name)) {
|
||
|
|
ss << "The option set " << q_(name) << " is used in the parameter list of "
|
||
|
|
<< definition_name(klammer_name, target_name) << ".\n"
|
||
|
|
<< "An option set may be used only in the parameter list of a \".k\" "
|
||
|
|
<< "declaration, which is where a klammer's interface is declared once "
|
||
|
|
<< "for all of its targets. Declare "
|
||
|
|
<< q_(klammer_name + ".k") << " and give each target's definition as "
|
||
|
|
<< "an instance (\"::\"), which inherits the declared parameters.";
|
||
|
|
} else {
|
||
|
|
ss << "The klammer " << q_(name) << " is applied in the parameter list of "
|
||
|
|
<< definition_name(klammer_name, target_name) << ".\n"
|
||
|
|
<< "A klammer application in a parameter list is not allowed: it is "
|
||
|
|
<< "resolved after the parameters are parsed, so the parameter list it "
|
||
|
|
<< "was meant to contribute is not there when the list is read. An "
|
||
|
|
<< "option set, declared with a \".o\" target, is how parameters are "
|
||
|
|
<< "shared between klammers. Declared option sets: "
|
||
|
|
<< option_sets.available() << ".";
|
||
|
|
}
|
||
|
|
throw Definition_error(ss.str(), loc, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
// The default written for each member at the use site:
|
||
|
|
// @caption_args :number false :caption_side top @
|
||
|
|
// Only defaults may be given -- names and types belong to the set.
|
||
|
|
std::map<std::string, std::string> use_site_defaults(
|
||
|
|
const Option_set& option_set, katom_iter begin, katom_iter end)
|
||
|
|
{
|
||
|
|
auto [positional, optional, rest] = argument_split(begin + 1, end - 1, 0);
|
||
|
|
if (!positional.empty() || active(rest)) {
|
||
|
|
throw Definition_error(
|
||
|
|
"The use of the option set " + q_(option_set.m_name) +
|
||
|
|
" gives a value that is not an option.\n"
|
||
|
|
"A set's names and types are fixed where the set is declared; only a "
|
||
|
|
"default may be given where it is used, written as \":name value\".",
|
||
|
|
begin->m_loc, false);
|
||
|
|
}
|
||
|
|
std::map<std::string, std::string> defaults {};
|
||
|
|
for (const auto& option : optional) {
|
||
|
|
std::string name = option_name(option[0]);
|
||
|
|
if (name.size() + 1 != option[0].m_text.size()) {
|
||
|
|
throw Definition_error(
|
||
|
|
"The use of the option set " + q_(option_set.m_name) + " gives a type for \":"
|
||
|
|
+ name + "\".\n"
|
||
|
|
"A set's names and types are declared where the set is; only a default "
|
||
|
|
"may be given where it is used.",
|
||
|
|
option[0].m_loc, false);
|
||
|
|
}
|
||
|
|
const Parameter* member = option_set.find(name);
|
||
|
|
if (member == nullptr) {
|
||
|
|
throw Definition_error(
|
||
|
|
"The option set " + q_(option_set.m_name) + " has no parameter \":"
|
||
|
|
+ name + "\".\n It declares: " + option_set.member_names(),
|
||
|
|
option[0].m_loc, false);
|
||
|
|
}
|
||
|
|
if (defaults.count(name) > 0) {
|
||
|
|
throw Definition_error(
|
||
|
|
"A default for \":" + name + "\" is given more than once in the use of "
|
||
|
|
"the option set " + q_(option_set.m_name),
|
||
|
|
option[0].m_loc);
|
||
|
|
}
|
||
|
|
std::string value = trim(to_string(katom_list(option.begin() + 1, option.end())));
|
||
|
|
// A bare option name means the argument type's :alone value, exactly
|
||
|
|
// as it does where an argument is written.
|
||
|
|
if (value.empty() && !member->m_argtype.m_alone.empty()) {
|
||
|
|
value = member->m_argtype.m_alone;
|
||
|
|
}
|
||
|
|
// Validated here, at definition time: an invalid default must not
|
||
|
|
// wait for an application that happens not to supply the argument.
|
||
|
|
Parameter_set::validate(*member, value, option[0].m_loc);
|
||
|
|
defaults[name] = value;
|
||
|
|
}
|
||
|
|
return defaults;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
option_set_uses_t expand_option_sets(
|
||
|
|
katom_list& parameters, const std::string& klammer_name,
|
||
|
|
const std::string& target_name, Option_set_registry& option_sets)
|
||
|
|
{
|
||
|
|
option_set_uses_t uses {};
|
||
|
|
// Every parameter name in the list, and where it came from, so that a
|
||
|
|
// collision between two sets (or between a set and a name written here)
|
||
|
|
// can name both origins rather than just the name.
|
||
|
|
std::map<std::string, std::string> origin {};
|
||
|
|
auto declare = [&](const std::string& name, const std::string& from, const Locator& loc) {
|
||
|
|
auto previous = origin.find(name);
|
||
|
|
if (previous != origin.end()) {
|
||
|
|
throw Definition_error(
|
||
|
|
"The parameter \":" + name + "\" of " + q_(klammer_name) +
|
||
|
|
" is declared twice:\n " + previous->second + "\n " + from,
|
||
|
|
loc, false);
|
||
|
|
}
|
||
|
|
origin[name] = from;
|
||
|
|
};
|
||
|
|
|
||
|
|
katom_list result {};
|
||
|
|
result.reserve(parameters.size());
|
||
|
|
for (auto k = parameters.begin(); k != parameters.end(); ) {
|
||
|
|
if (!begin_klammer_apply(*k)) {
|
||
|
|
if (k->m_type == katom_t::option_name) {
|
||
|
|
declare(option_name(*k), "declared in the parameter list", k->m_loc);
|
||
|
|
}
|
||
|
|
result.push_back(*k);
|
||
|
|
++k;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
std::string name = trim_char(k->m_text, '@');
|
||
|
|
auto end = application_end(k, parameters.end());
|
||
|
|
if (target_name != Target_registry::declare_name || !option_sets.has(name)) {
|
||
|
|
reject_application(name, klammer_name, target_name, option_sets, k->m_loc);
|
||
|
|
}
|
||
|
|
const Option_set& option_set = option_sets.get(name, k->m_loc);
|
||
|
|
std::map<std::string, std::string> defaults = use_site_defaults(option_set, k, end);
|
||
|
|
|
||
|
|
// m_members and m_parameters.m_optional are the same members in the
|
||
|
|
// same order -- one is the katoms as declared, the other what they
|
||
|
|
// parsed to -- so the parameter's name is taken from the parsed side
|
||
|
|
// rather than re-derived from the katom text (":number.bool").
|
||
|
|
for (size_t i = 0; i < option_set.m_members.size(); ++i) {
|
||
|
|
const katom_list& member = option_set.m_members[i];
|
||
|
|
const std::string& member_name = option_set.m_parameters.m_optional[i].m_name;
|
||
|
|
declare(member_name, "from the option set " + q_(name) + ", "
|
||
|
|
+ option_set.m_loc.desc(), k->m_loc);
|
||
|
|
if (!result.empty()) {
|
||
|
|
result.push_back(Katom(" ", katom_t::space, member[0].m_loc));
|
||
|
|
}
|
||
|
|
// The name katom comes from the set, so the parameter's location
|
||
|
|
// is where it is declared; an overriding default comes from here.
|
||
|
|
result.push_back(member[0]);
|
||
|
|
auto given = defaults.find(member_name);
|
||
|
|
if (given == defaults.end()) {
|
||
|
|
result.insert(result.end(), member.begin() + 1, member.end());
|
||
|
|
} else if (!given->second.empty()) {
|
||
|
|
result.push_back(Katom(" ", katom_t::space, k->m_loc));
|
||
|
|
result.push_back(Katom(given->second, katom_t::text, k->m_loc));
|
||
|
|
}
|
||
|
|
uses[member_name] = {name, given != defaults.end()};
|
||
|
|
}
|
||
|
|
option_sets.add_user(name, klammer_name);
|
||
|
|
k = end;
|
||
|
|
}
|
||
|
|
parameters = result;
|
||
|
|
return uses;
|
||
|
|
}
|
||
|
|
|
||
|
|
void stamp_option_set_uses(Parameter_set& parameters, const option_set_uses_t& uses)
|
||
|
|
{
|
||
|
|
if (uses.empty()) return;
|
||
|
|
for (Parameter& parameter : parameters.m_optional) {
|
||
|
|
auto use = uses.find(parameter.m_name);
|
||
|
|
if (use == uses.end()) continue;
|
||
|
|
parameter.m_option_set = use->second.m_set;
|
||
|
|
parameter.m_default_overridden = use->second.m_overridden;
|
||
|
|
}
|
||
|
|
}
|