#include #include #include #include #include "argtype_set.h" #include "error.h" #include "show.h" #include "log.h" #include "util.h" #include "character.h" #include "katom.h" Parameter_set& Argtype_set::parameters() { static Parameter_set instance("name | desc :pattern .* :python_cast str :default :alone"); return instance; } Argtype_set::Argtype_set() { (void)(void)K::log(2); Locator loc = current_locator(); for (auto [name, desc, pattern, python_cast, python_format] : base_argtypes) { add(name, desc, pattern, "", "", python_cast, python_format, loc); } // rest is a parameterized type (rest(N)); an unparameterized use is // one-dimensional. m_types["rest"].m_parameter = "1"; // A bool option written alone is true: :number means :number true. // This is a declaration like any other type's :alone, not an engine // special case for booleans. m_types["bool"].m_alone = "true"; } std::string Argtype_set::replace_symbols(const std::string& pattern, const Locator& loc) { std::smatch match {}; std::regex symbol_pat(R"('(\w+)')"); std::string expanded { pattern }; for (const std::string& symbol : find_all(pattern, symbol_pat, 0)) { std::string name { symbol.begin()+1, symbol.end()-1 }; if (m_types.find(name) != m_types.end()) { expanded = string_replace( expanded, symbol, R"((?:)" + m_types[name].m_pattern + R"())"); } else { std::stringstream ss; ss << "Argtype symbol " << symbol << " not defined.\n\n" << "Defined argtypes:\n"; ss << describe(); throw Definition_error(ss.str(), loc, false); } } return expanded; } void Argtype_set::check_for_existing_definition( const std::string& name, const Locator& loc) { if (count(m_names.begin(), m_names.end(), name) > 0) { std::stringstream ss {}; ss << "Argument type '" << name << "' is already defined at " << m_types[name].m_loc; throw Definition_error(ss.str(), loc); } } void Argtype_set::add(const std::string& name, const std::string& desc, const std::string& pattern, const std::string& default_value, const std::string& alone_value, const std::string& python_cast, modify_string_f python_format, const Locator& loc) { // (void)K::log(3, name, ":", abbrev(string_replace(desc, "\n", "/"), 50), pattern); check_for_existing_definition(name, loc); std::string expanded_pattern = replace_symbols(pattern, loc); m_name_size = std::max(m_name_size, name.size()); // For display m_pattern_size = std::max(m_pattern_size, expanded_pattern.size()); try { m_types[name] = Argtype(name, desc, pattern, expanded_pattern, default_value, alone_value, python_cast, python_format, loc); } catch (const std::regex_error& e) { std::stringstream ss {}; ss << "The pattern for argument type \"" << name << "\" is not a valid regular expression (" << e.what() << "):\n" << " " << pattern << "\n"; if (pattern != expanded_pattern) { ss << "expanded to:\n " << expanded_pattern << "\n"; } throw Definition_error(ss.str(), loc, false); } if (!default_value.empty() && !std::regex_match(default_value, m_types[name].m_regex)) { std::stringstream ss {}; ss << "The default value \"" << default_value << "\" for argument type \"" << name << "\" does not match its own pattern:\n" << " " << pattern << "\n"; throw Definition_error(ss.str(), loc, false); } if (!alone_value.empty()) { // A pattern that matches running text cannot delimit a bare option // name from the text after it: an option's value runs to the next // bar or option name, so ":opt some words" would silently take // "some words" as the value and the alone value would never be // reached. A pattern that rejects multi-word text raises a clean // argument error there instead. The test is behavioral rather // than a comparison against matches_all()'s one literal pattern, // because an @@@argtype written without a :pattern gets ".*", // which is equally unable to delimit. if (std::regex_match(std::string("one two"), m_types[name].m_regex)) { std::stringstream ss {}; ss << "The argument type \"" << name << "\" cannot declare an :alone value " << "because its pattern matches running text:\n" << " " << pattern << "\n\n" << "An :alone value is used when an option name is written without a " << "value. A type that matches running text cannot tell a bare option " << "name from one whose value follows it, so the text after the name " << "would be taken as the value instead.\n"; throw Definition_error(ss.str(), loc, false); } if (!std::regex_match(alone_value, m_types[name].m_regex)) { std::stringstream ss {}; ss << "The alone value \"" << alone_value << "\" for argument type \"" << name << "\" does not match its own pattern:\n" << " " << pattern << "\n"; throw Definition_error(ss.str(), loc, false); } } m_names.push_back(name); } void Argtype_set::add(std::vector::iterator begin, std::vector::iterator end, std::vector& katoms) { (void)K::log(3); auto [positional, optional, rest] = argument_split(begin + 1, end - 1); //, Argtype_set::parameters.m_positional.size()); check_for_existing_definition(positional[0][0].m_text, begin->m_loc); auto values = Argtype_set::parameters().value_map(positional, optional, rest, begin->m_loc); // std::for_each(begin, end+1, [](Katom& k) { k.m_type = katom_t::replaced; }); add(values["name"], values["desc"], values["pattern"], values["default"], values["alone"], values["python_cast"], modify_string_f{}, begin->m_loc); modify_type(katom_t::replaced, begin, end); auto next_iter = end; ignore_whitespace(next_iter, katoms); } Argtype Argtype_set::get(const std::string& name, const Locator& loc) const { if (is_not_in(name, m_names)) { std::stringstream msg {}; msg << "Type '" << name << "' is not an argument type"; throw Argument_error(msg.str(), loc); } return m_types.at(name); } std::string Argtype_set::eval( const std::string& value, const std::string& type_name, const Locator& loc) { std::regex re = m_types[type_name].m_regex; std::smatch match {}; if (std::regex_match(value, match, re)) { return value; } else { //return "NO MATCH"; std::stringstream ss {}; ss << "Argument \"" << value << "\" does not match the pattern for \"" << type_name << "\"\n"; throw Definition_error(ss.str(), loc); } } // The values a type supplies when an argument does not give one: the // default (the option was not written at all) and the alone value (the // option name was written without a value). Shown only when declared. static std::string values_note(const Argtype& type) { std::stringstream note {}; if (!type.m_default.empty()) { note << " [default: " << type.m_default << "]"; } if (!type.m_alone.empty()) { note << " [alone: " << type.m_alone << "]"; } return note.str(); } std::string Argtype_set::describe(bool long_form, int indent_width) const { std::string indent(' ', indent_width); std::size_t name_width = std::accumulate( m_names.begin(), m_names.end(), 0, [&] (size_t w, const std::string& name) { return std::max(w, name.size()); }); std::stringstream result {}; for (const auto& name : m_names) { std::string label { "Regex:" }; int pat_width = name_width + 2 + indent_width + label.size(); result << indent << std::setw(name_width) << name << sp_arrow; if (long_form) { result << m_types.at(name).m_desc << "\n"; result << std::setw(pat_width) << "regex: " << m_types.at(name).m_symbolic_pattern; if (m_types.at(name).m_symbolic_pattern != m_types.at(name).m_pattern) result << sp_arrow << m_types.at(name).m_pattern; result << "\n"; } else { // result << abbrev(m_types.at(name).m_desc) << "\n"; result << regex_split(m_types.at(name).m_desc, std::regex("\\n"), true)[0] << values_note(m_types.at(name)) << "\n"; } } if (long_form) { result << "\nA previously defined type can be included in the definition of a new type\n" << "by surrounding the name of the existing type in single quotation marks.\n"; } return result.str(); }