#include #include #include #include "show.h" #include "log.h" #include "katom.h" #include "argtype_set.h" #include "argument_set.h" #include "util.h" bool operator==(Parameter_set lhs, Parameter_set rhs) { return as_string(lhs.m_katoms.begin(), lhs.m_katoms.end(), true) == as_string(rhs.m_katoms.begin(), rhs.m_katoms.end(), true); } std::regex parameter_regex(bool optional=false) { // The type component may carry a numeric type parameter, e.g. // rows.rest(2) (see resolve_argtype below). In the optional // three-part form the type may be empty (:paper_size..tex). std::string type = R"(\w+(?:\(\d+\))?)"; std::string opt_type = R"(\w*(?:\(\d+\))?)"; std::string pattern = R"((?:([A-Za-z]\w*))|(?:([A-Za-z]\w*)\.()" + type + R"())|(?:([A-Za-z]\w*)\.()" + type + R"()\.(\w+)))"; if (optional) { pattern = R"((?::([A-Za-z]\w*))|(?::([A-Za-z]\w*)\.()" + type + R"())|(?::([A-Za-z]\w*)\.()" + opt_type + R"()\.(\w+)))"; } // (void)K::log(3, pattern); return std::regex(pattern); } // Look up an argument type, specializing a parameterized use such as // rest(2): the base type is copied, its type parameter set, and its // display name extended, so kdesc signatures show rows.rest(2). static Argtype resolve_argtype( const std::string& type_text, const Argtype_set& argtypes, const Locator& loc) { static const std::regex parameterized(R"((\w+)\((\d+)\))"); std::smatch match {}; if (std::regex_match(type_text, match, parameterized)) { Argtype argtype = argtypes.get(match[1], loc); argtype.m_parameter = match[2]; argtype.m_name += "(" + std::string(match[2]) + ")"; return argtype; } return argtypes.get(type_text, loc); } // True for the rest type in any parameterization (rest, rest(2), ...). static bool is_rest(const Argtype& argtype) { return argtype.m_name == "rest" || argtype.m_name.rfind("rest(", 0) == 0; } Parameter_set::Parameter_set(const std::string parameter_string) { (void)K::log(3); Argtype_set argtypes {}; parse_parameters( katomize(line_split(parameter_string), Locator().str()), argtypes); } Parameter_set::Parameter_set(const std::vector& katoms) : m_katoms(katoms) { (void)K::log(3); Argtype_set argtypes {}; parse_parameters(m_katoms, argtypes); } Parameter_set::Parameter_set(const std::vector& katoms, const Argtype_set& argtypes) : m_katoms(katoms) { (void)K::log(3); parse_parameters(m_katoms, argtypes); } // Parameter parsing Parameter parse_positional_parameter(const katom_list& katoms, const Argtype_set& argtypes) { // (void)K::log(3, katoms); if (katoms.size() > 1) { throw Argument_error("Multiple katoms for positional argument: " + as_string(katoms.begin(), katoms.end(), true) + "\nPositional arguments are separated by the bar (|) character.", katoms[0].m_loc, false); } Katom k = katoms[0]; std::string name = k.m_text; std::smatch match {}; if (!std::regex_match(name, match, parameter_regex())) { throw Argument_error( "The structure of the word \"" + name + "\" is not correct for a positional parameter", k.m_loc); } else { std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]); std::string match_type = std::string(match[3]) + std::string(match[5]); std::string match_target = match[6]; if (match_type.empty()) { match_type = "string"; } return Parameter(match_name, resolve_argtype(match_type, argtypes, k.m_loc), k.m_loc); } } Parameter parse_optional_parameter(const katom_list& katoms, const Argtype_set& argtypes) { //(void)K::log(3); Katom k = katoms[0]; std::string default_value {}; if (katoms.size() > 1) { default_value = to_string(katoms.cbegin() + 1, katoms.cend(), true); } std::string name = k.m_text; std::smatch match {}; if (!std::regex_match(name, match, parameter_regex(true))) { throw Argument_error( "The structure of the word \"" + name + "\" is not correct for an optional parameter", k.m_loc); } else { std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]); std::string match_type = std::string(match[3]) + std::string(match[5]); std::string match_target = match[6]; if (match_type.empty()) { match_type = "string"; } Parameter parameter(match_name, resolve_argtype(match_type, argtypes, k.m_loc), k.m_loc, true, default_value); // Two-level default resolution: a default declared in the // parameter list wins; otherwise the argument type's :default // fills in. Both are validated here, at definition time, so an // invalid default cannot reach an application. if (default_value.empty() && !parameter.m_argtype.m_default.empty()) { parameter.m_default = parameter.m_argtype.m_default; parameter.m_default_from_type = true; } else if (!default_value.empty()) { Parameter_set::validate(parameter, default_value, k.m_loc); } return parameter; } } void check_for_missing_parameter(const katom_list& katoms) { (void)K::log(3, katoms.size()); // Yeah, yeah, "algorithms." auto ki = katoms.begin(); while (ki < katoms.end() - 1) { ki = std::find_if(ki, katoms.end(), [](const Katom& k) { return k.m_type == katom_t::bar; }); if (ki == katoms.end()) { break; } auto kstart = ki; ki = std::find_if(ki + 1, katoms.end(), [](const Katom& k) { return !k.is_whitespace(); }); if (ki == katoms.end()) { throw Argument_error( "A parameter list ends with a bar character", kstart->m_loc); } auto type_after_bar = ki->m_type; if (type_after_bar == katom_t::bar) { throw Argument_error( "A parameter name was missing between two bar characters", kstart->m_loc); } else if (type_after_bar == katom_t::option_name) { throw Argument_error( "A parameter name was missing between a bar character and an option name", kstart->m_loc); } ++ki; } } bool is_boundary(katom_list::const_iterator ki) { return ki->m_type == katom_t::option_name || ki->m_type == katom_t::bar; } std::vector> function_symbol_parts(katom_list::const_iterator kbegin, katom_list::const_iterator kend) { std::vector> parts; katom_list part {}; auto ki = kbegin; while (ki != kend && ki->is_whitespace()) { ki++; } if (ki == kend) { return {}; } if (ki->m_type != katom_t::option_name) { part.push_back(Katom("|", katom_t::bar, kbegin->m_loc)); } while (ki < kend) { if (!part.empty() && is_boundary(ki)) { parts.push_back(trim(part)); part = {}; } part.push_back(*ki); ki++; } if (!part.empty()) { parts.push_back(trim(part)); } // std::cout << "PARTS:\n"; // for (size_t i = 0; i < parts.size(); i++) { // std::cout << i << sp_arrow << parts[i] << "\n"; // } return parts; } katom_list trim_part(katom_list part) { return trim(part, {katom_t::space, katom_t::newline, katom_t::bar}); } std::tuple parameter_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend) { (void)K::log(3); //, "begin:", *kbegin, "end:", *(kend - 1)); // "distance:", std::distance(kbegin, kend)); auto parts = function_symbol_parts(kbegin, kend); // std::cout << "parts: " << parts << "\n"; katom_lists positional {}; katom_lists optional {}; for (auto p : parts) { if (p[0].m_type == katom_t::option_name) { optional.push_back(p); } else { positional.push_back(trim_part(p)); } } return {positional, optional}; } void Parameter_set::parse_parameters(const katom_list& katoms, const Argtype_set& argtypes) { (void)K::log(3, trim(katoms)); if (katoms.empty()) { return; } check_for_missing_parameter(katoms); auto [positional, optional] = parameter_split(katoms.cbegin(), katoms.cend()); for (auto req : positional) { auto pos = parse_positional_parameter(req, argtypes); if (is_rest(pos.m_argtype)) { m_rest.push_back(pos); } else { m_positional.push_back(pos); } } for (auto opt : optional) { auto param = parse_optional_parameter(opt, argtypes); if (std::ranges::count(m_optional_names, param.m_name) > 0) { throw Argument_error( "Optional parameter \":" + param.m_name + "\" already defined", katoms[0].m_loc); } m_optional.push_back(param); m_optional_names.push_back(param.m_name); } if (!m_rest.empty()) { m_positional_count = m_positional.size(); } } void describe_arguments( std::string label, std::vector> positional, std::vector> optional, std::vector rest) { std::cout << label << ":\n" << " positional: " << positional << "\n" << " optional: " << optional << "\n" << " rest: " << rest << "\n"; } void Parameter_set::describe_parameters() { (void)K::log(3); std::cout << " positional: "; if (!m_positional.empty()) { for (auto p : m_positional) { std::cout << p << " "; } } else { std::cout << "[none]"; } std::cout << "\n optional: "; if (!m_optional.empty()) { for (auto p : m_optional) { std::cout << p << " "; } } else { std::cout << "[none]"; } std::cout << "\n rest: "; if (!m_rest.empty()) { std::cout << kall << m_rest << kreset << "\n"; } else { std::cout << "[none]"; } std::cout << "\n"; } std::tuple argument_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend, long unsigned int positional_limit) { // (void)K::log(3, "begin:", *(kbegin+1), "end:", *(kend - 1), // "distance:", std::distance(kbegin, kend), "limit:", positional_limit); (void)K::log(3); // msg() << std::pair(kbegin, kend) << "\n"; auto parts = function_symbol_parts(kbegin, kend); katom_lists positional {}; katom_lists optional {}; katom_list rest {}; for (auto p : parts) { if (p[0].m_type == katom_t::option_name) { optional.push_back(p); } else if (positional.size() < positional_limit) { positional.push_back(trim_part(p)); } else { // Parts were trimmed, so adjacent parts would abut their bar // katoms (a row separator "||" next to an empty cell's "|" // would serialize as "|||"). A space keeps the writer's // bar/double-bar distinction parseable. if (!rest.empty()) { rest.push_back(Katom(" ", katom_t::space, p[0].m_loc)); } rest.insert(rest.end(), p.begin(), p.end()); } } return {positional, optional, trim_part(rest)}; } // Parameter/argument mapping void Parameter_set::check_positional(const katom_lists& positional_arguments, const Locator& loc) { (void)K::log(3, "required:", m_positional.size(), positional_arguments.size()); //, positional_arguments); auto positional_count = m_positional.size(); auto given_count = positional_arguments.size(); if (positional_count > given_count) { // std::cout << "Given less than required\n"; std::vector missing(m_positional.begin() + given_count, m_positional.end()); //std::cout << "missing: " << missing << "\n"; auto missing_count = missing.size(); std::stringstream ss {}; ss << "Positional " << plural("argument", missing_count) << " " << to_be(missing_count) << " missing:\n"; std::cout << ss.str(); for (auto arg : missing) { ss << " " << arg.m_name << "\n"; } //std::cout << ss.str(); throw Argument_error(ss.str(), loc, false); } else if (positional_count < given_count) { //std::cout << "DESCRIBE\n"; //describe_parameters(); std::stringstream ss {}; ss << "Too many positional arguments were given; " << positional_count << " needed but " << given_count << " given"; throw Argument_error(ss.str(), loc); } } std::map Parameter_set::check_optional(const katom_lists& optional_arguments, const Locator& loc) { (void)K::log(3, optional_arguments.size()); std::vector optional_names_used {}; std::map values {}; for (const auto& opt : optional_arguments) { std::string name(opt[0].m_text, 1); if (std::ranges::count(m_optional_names, name) == 0) { throw Argument_error("Optional argument \":" + name + "\" not defined", loc); } if (std::ranges::count(optional_names_used, name) > 0) { throw Argument_error("Optional argument \":" + name + "\" already provided " + "with a value of:\n" + values[name], loc, false); } katom_list value_katoms(opt.begin()+1, opt.end()); std::string value = trim(to_string(value_katoms)); values[name] = value; optional_names_used.push_back(name); } return values; } const std::map Parameter_set::value_map( const katom_lists& positional, const katom_lists& optional, const katom_list& rest, const Locator& loc) { (void)K::log(3, "positional:", positional.size(), "optional:", optional.size(), "rest:", rest.size()); std::map values {}; check_positional(positional, loc); for (size_t i = 0; i < m_positional.size(); ++i) { auto param = m_positional[i]; std::string arg = as_string(positional[i].begin(), positional[i].end(), true); values[param.m_name] = arg; } auto optional_values = check_optional(optional, loc); for (auto [key, value] : optional_values) { // check_optional returns only the options that were actually // written, so an empty value here means the name was written alone // (":number" rather than ":number 10") — distinct from the option // being absent, which is filled from the default below. The // argument type supplies the alone value; bool declares "true", // which is what makes a bare boolean option mean true. const Parameter* parameter = find(key); if (value.empty() && parameter && !parameter->m_argtype.m_alone.empty()) { value = parameter->m_argtype.m_alone; } values[key] = value; } for (auto opt : m_optional) { values.try_emplace(opt.m_name, opt.m_default); } if (active(rest)) { if (!m_rest.empty()) { values[m_rest[0].m_name] = as_string(rest.begin(), rest.end(), true); } else { std::stringstream ss {}; ss << "More positional arguments were given (" << positional.size() + rest.size() << ") than defined (" << m_positional.size() << ")"; throw Argument_error(ss.str(), loc); } } for (const auto& [name, value] : values) { const Parameter* parameter = find(name); if (parameter) { validate(*parameter, value, loc); } } return values; } // Check an argument value against its argument type's pattern. An empty // value (an unsupplied optional argument without a default) is not checked. // The error message includes the argument type's description from its // @@@argtype definition, so the .k description text is what the writer // sees when a complicated value (e.g. a table line specification) is wrong. // The value-size limit guards against std::regex stack overflow: the // libstdc++ executor recurses per character, so a pattern applied to a // very large value crashes. Typed argument values are short; large // values are content (rest, :text) whose types match everything and are // excluded by matches_all() anyway. const size_t validation_size_limit = 4096; void Parameter_set::validate( const Parameter& parameter, const std::string& value, const Locator& loc) { if (value.empty() || value.size() > validation_size_limit) { return; } const Argtype& argtype = parameter.m_argtype; if (argtype.matches_all()) { return; } if (!std::regex_match(value, argtype.m_regex)) { std::stringstream ss {}; ss << "The value \"" << value << "\" given for the argument \"" << parameter.m_name << "\" does not match the \"" << argtype.m_name << "\" argument type:\n\n" << trim(argtype.m_desc) << "\n"; throw Argument_error(ss.str(), loc, false); } } const Parameter* Parameter_set::find(const std::string& name) const { for (const auto& params : {&m_positional, &m_optional, &m_rest}) { for (const Parameter& p : *params) { if (p.m_name == name) { return &p; } } } return nullptr; } // Parameter/argument substitution std::string replace_arguments( const std::map& values, const std::string& parameterized_text, const Locator& loc) { (void)K::log(3); std::string result = parameterized_text; for (auto [name, value] : values) { result = string_replace(result, '*' + name + '*', value); } auto matches = find_all(result, std::regex(R"((\*.*?\*))")); std::vector unmatched; unmatched.reserve(matches.size()); std::copy(matches.begin(), matches.end(), std::back_inserter(unmatched)); auto unmatched_count = unmatched.size(); if (unmatched_count > 0) { std::stringstream ss {}; ss << "Undefined " << plural("argument", unmatched_count) << " in klammer:\n"; for (const auto& arg : unmatched) { ss << " " << arg << "\n"; } ss << "To prevent the \"*\" character from specifying an argument, " << "precede it with the \"^\" character."; throw Argument_error(ss.str(), loc, false); } return result; }