#include #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) { // Reported, not warned: see the same case in klammer_registry.cpp. (void)K::log(1, message + " at " + begin->m_loc.desc()); } } // 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 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(names.size())) + " " + join(names, ", ") + ".\n\n" "An option set declares only optional parameters -- names written with " "a leading \":\".", begin->m_loc); } if (parameters.m_optional.empty()) { throw Definition_error( "The option set " + q_(name) + " declares no parameters.\n\n" "The form is: @@" + name + ".o :name.argtype default ... : @@", begin->m_loc); } // 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 strings_t& defined_outside) const { std::string result {}; for (const auto& name : m_names) { const Option_set& set = m_option_sets.at(name); if (!defined_outside.empty() && is_in(set.m_loc.m_filename, defined_outside)) { continue; } result += set.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\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\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\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.\n\nDeclared option sets: " << option_sets.available() << "."; } throw Definition_error(ss.str(), loc); } // 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 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\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); } std::map 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\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); } 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 + "\". It declares:\n " + option_set.member_names(), option[0].m_loc); } 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 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); } 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 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; } }