// license:BSD-3-Clause // copyright-holders:Nathan Woods /**************************************************************************** opresolv.cpp Extensible ranged option resolution handling ****************************************************************************/ #include "opresolv.h" #include "strformat.h" #include #include #include #include #include namespace util { /*************************************************************************** option_resolution ***************************************************************************/ // ------------------------------------------------- // ctor // ------------------------------------------------- option_resolution::option_resolution(const option_guide &guide) { // reserve space for entries m_entries.reserve(guide.entries().size()); // initialize each of the entries; can't use foreach because we need to scan the // ENUM_VALUE entries for (auto iter = guide.entries().begin(); iter != guide.entries().end(); iter++) { // create the entry m_entries.emplace_back(*iter); entry &entry(m_entries.back()); // if this is an enumeration, identify the values if (iter->type() == option_guide::entry::option_type::ENUM_BEGIN) { // enum values begin after the ENUM_BEGIN auto enum_value_begin = iter + 1; auto enum_value_end = enum_value_begin; // and identify all entries of type ENUM_VALUE while (enum_value_end != guide.entries().end() && enum_value_end->type() == option_guide::entry::option_type::ENUM_VALUE) { iter++; enum_value_end++; } // set the range entry.set_enum_value_range(enum_value_begin, enum_value_end); } } } // ------------------------------------------------- // dtor // ------------------------------------------------- option_resolution::~option_resolution() { } // ------------------------------------------------- // lookup_in_specification // ------------------------------------------------- const char *option_resolution::lookup_in_specification(const char *specification, const option_guide::entry &option) { const char *s; s = strchr(specification, option.parameter()); return s ? s + 1 : nullptr; } // ------------------------------------------------- // set_specification - sets the option specification // and mutates values accordingly // ------------------------------------------------- void option_resolution::set_specification(const std::string &specification) { for (auto &entry : m_entries) { // find this entry's info in the specification auto entry_specification = lookup_in_specification(specification.c_str(), entry.m_guide_entry); // parse this entry's specification (e.g. - set up ranges and defaults) entry.parse_specification(entry_specification); // is this a range typed that needs to be defaulted for the first time? if (entry.is_pertinent() && entry.is_ranged() && entry.value().empty()) { entry.set_value(entry.default_value()); } } } // ------------------------------------------------- // find // ------------------------------------------------- option_resolution::entry *option_resolution::find(int parameter) { auto iter = std::find_if( m_entries.begin(), m_entries.end(), [parameter](const entry &e) { return e.parameter() == parameter; }); return iter != m_entries.end() ? &*iter : nullptr; } // ------------------------------------------------- // find // ------------------------------------------------- option_resolution::entry *option_resolution::find(const std::string &identifier) { auto iter = std::find_if( m_entries.begin(), m_entries.end(), [&](const entry &e) { return !strcmp(e.identifier(), identifier.c_str()); }); return iter != m_entries.end() ? &*iter : nullptr; } // ------------------------------------------------- // lookup_int // ------------------------------------------------- int option_resolution::lookup_int(int parameter) { auto entry = find(parameter); assert(entry != nullptr); return entry->value_int(); } // ------------------------------------------------- // lookup_string // ------------------------------------------------- const std::string &option_resolution::lookup_string(int parameter) { auto entry = find(parameter); assert(entry != nullptr); return entry->value(); } // ------------------------------------------------- // error_string // ------------------------------------------------- option_resolution::error option_resolution::get_default(const char *specification, int option_char, int *val) { // NYI return error::INTERNAL; } // ------------------------------------------------- // error_string // ------------------------------------------------- const char *option_resolution::error_string(option_resolution::error err) { switch (err) { case error::SUCCESS: return "The operation completed successfully"; case error::OUTOFMEMORY: return "Out of memory"; case error::PARAMOUTOFRANGE: return "Parameter out of range"; case error::PARAMNOTSPECIFIED: return "Parameter not specified"; case error::PARAMNOTFOUND: return "Unknown parameter"; case error::PARAMALREADYSPECIFIED: return "Parameter specified multiple times"; case error::BADPARAM: return "Invalid parameter"; case error::SYNTAX: return "Syntax error"; case error::INTERNAL: return "Internal error"; } return nullptr; } // ------------------------------------------------- // entry::ctor // ------------------------------------------------- option_resolution::entry::entry(const option_guide::entry &guide_entry) : m_guide_entry(guide_entry), m_is_pertinent(false) { } // ------------------------------------------------- // entry::set_enum_value_range // ------------------------------------------------- void option_resolution::entry::set_enum_value_range(const option_guide::entry *begin, const option_guide::entry *end) { m_enum_value_begin = begin; m_enum_value_end = end; } // ------------------------------------------------- // entry::parse_specification // ------------------------------------------------- void option_resolution::entry::parse_specification(const char *specification) { // clear some items out m_ranges.clear(); m_default_value.clear(); // is this even pertinent? m_is_pertinent = (specification != nullptr) && (specification[0] != '\0'); if (m_is_pertinent) { int value = 0; bool in_range = false; bool in_default = false; bool default_specified = false; bool half_range = false; size_t pos = 0; m_ranges.emplace_back(); while (specification[pos] && !isalpha(specification[pos])) { if (specification[pos] == '-') { // range specifier assert(!in_range && !in_default && "Syntax error in specification"); in_range = true; pos++; m_ranges.back().max = -1; if (!half_range) { m_ranges.back().min = -1; half_range = true; } } else if (specification[pos] == '[') { // begin default value assert(!in_default && !default_specified && "Syntax error in specification"); in_default = true; pos++; } else if (specification[pos] == ']') { // end default value assert(in_default && "Syntax error in specification"); in_default = false; default_specified = true; pos++; m_default_value = numeric_value(value); } else if (specification[pos] == '/') { // value separator assert(!in_default && !in_range && "Syntax error in specification"); pos++; // if we are spitting out ranges, complete the range if (half_range) { m_ranges.emplace_back(); half_range = false; } } else if (specification[pos] == ';') { // basic separator pos++; } else if (isdigit(specification[pos])) { // numeric value */ value = 0; do { value *= 10; value += specification[pos++] - '0'; } while (isdigit(specification[pos])); if (!half_range) { m_ranges.back().min = value; half_range = true; } m_ranges.back().max = value; in_range = false; } else { // invalid character - abort because we cannot recover from this syntax error throw false; } } // we can't have zero length guidelines strings assert(pos > 0); // appease compiler for scenarios where assert() has been preprocessed out (void)(in_range && in_default && default_specified); } } // ------------------------------------------------- // entry::numeric_value // ------------------------------------------------- std::string option_resolution::entry::numeric_value(int value) { return string_format("%d", value); } // ------------------------------------------------- // entry::value // ------------------------------------------------- const std::string &option_resolution::entry::value() const { static std::string empty_string; return is_pertinent() ? m_value : empty_string; } // ------------------------------------------------- // entry::value_int // ------------------------------------------------- int option_resolution::entry::value_int() const { return is_pertinent() ? atoi(m_value.c_str()) : -1; } // ------------------------------------------------- // entry::set_value // ------------------------------------------------- bool option_resolution::entry::set_value(const std::string &value) { // reject the value if this isn't pertinent if (!is_pertinent()) return false; // if this is ranged, check against the ranges if (is_ranged() && find_in_ranges(std::atoi(value.c_str())) == m_ranges.cend()) return false; // looks good! change the value m_value = value; return true; } // ------------------------------------------------- // entry::can_bump_lower // ------------------------------------------------- bool option_resolution::entry::can_bump_lower() const { return !m_ranges.empty() && value_int() > m_ranges.front().min; } // ------------------------------------------------- // entry::can_bump_higher // ------------------------------------------------- bool option_resolution::entry::can_bump_higher() const { return !m_ranges.empty() && value_int() < m_ranges.back().max; } // ------------------------------------------------- // entry::bump_lower // ------------------------------------------------- bool option_resolution::entry::bump_lower() { auto old_value = value_int(); auto current_range = find_in_ranges(old_value); assert(current_range != m_ranges.end()); int new_value; if (old_value > current_range->min) { // decrement within current range new_value = old_value - 1; } else if (current_range != m_ranges.begin()) { // go to the top of the previous range new_value = (current_range - 1)->max; } else { // at the minimum; don't bump new_value = old_value; } set_value(new_value); return new_value != old_value; } // ------------------------------------------------- // entry::bump_higher // ------------------------------------------------- bool option_resolution::entry::bump_higher() { auto old_value = value_int(); auto current_range = find_in_ranges(old_value); assert(current_range != m_ranges.end()); int new_value; if (old_value < current_range->max) { // increment within current range new_value = old_value + 1; } else if (current_range != (m_ranges.end() - 1)) { // go to the bottom of the next range new_value = (current_range + 1)->min; } else { // at the minimum; don't bump new_value = old_value; } set_value(new_value); return new_value != old_value; } // ------------------------------------------------- // entry::find_in_ranges // ------------------------------------------------- option_resolution::entry::rangelist::const_iterator option_resolution::entry::find_in_ranges(int value) const { return std::find_if( m_ranges.begin(), m_ranges.end(), [&](const auto &r) { return r.min <= value && value <= r.max; }); } } // namespace util 80 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391