Reduce code complexity by moving to data-driven algorithms.

This commit is contained in:
Marko Zivanovic
2015-10-09 23:30:19 +02:00
parent 59d46ce4f8
commit b6ea8dcc52
2 changed files with 62 additions and 30 deletions
+46 -22
View File
@@ -23,6 +23,8 @@ SOFTWARE.
*/ */
#include <algorithm> #include <algorithm>
#include <functional>
#include <map>
#include "version.h" #include "version.h"
using namespace std; using namespace std;
@@ -31,6 +33,7 @@ namespace {
using namespace version; using namespace version;
// Compare normal version identifiers.
int compare_normal(const Version_data& l, const Version_data& r) { int compare_normal(const Version_data& l, const Version_data& r) {
if (l.major > r.major) return 1; if (l.major > r.major) return 1;
if (l.major < r.major) return -1; if (l.major < r.major) return -1;
@@ -41,46 +44,67 @@ namespace {
return 0; return 0;
} }
int compare_prerel_identifiers(const Prerelease_identifier& l, const Prerelease_identifier& r) { // Compare alphanumeric prerelease identifiers.
if (l.second == Identifier_type::alnum && r.second == Identifier_type::alnum) { inline int cmp_alnum_prerel_ids(const string& l, const string& r) {
auto cmp = l.first.compare(r.first); auto cmp = l.compare(r);
if (cmp == 0) return cmp; if (cmp == 0) {
return cmp;
} else {
return cmp > 0 ? 1 : -1; return cmp > 0 ? 1 : -1;
} else if (l.second == Identifier_type::alnum && r.second == Identifier_type::num) {
return 1;
} else if (l.second == Identifier_type::num && r.second == Identifier_type::alnum) {
return -1;
} else if (l.second == Identifier_type::num && r.second == Identifier_type::num) {
int li = stoi(l.first);
int ri = stoi(r.first);
if (li == ri) return 0;
return li > ri ? 1 : -1;
} }
throw logic_error("unexpected identifier types: " + to_string(static_cast<int>(l.second)) + ", " + }
to_string(static_cast<int>(r.second)));
// Compare numeric prerelease identifiers.
inline int cmp_num_prerel_ids(const string& l, const string& r) {
int li = stoi(l);
int ri = stoi(r);
if (li == ri) return 0;
return li > ri ? 1 : -1;
}
using Prerel_types = pair<Identifier_type, Identifier_type>;
using Prerel_id_comparator = function<int(const string&, const string&)>;
const map<Prerel_types, Prerel_id_comparator> comparators = {
{ { Identifier_type::alnum, Identifier_type::alnum }, cmp_alnum_prerel_ids },
{ { Identifier_type::alnum, Identifier_type::num }, [](const string&, const string&) {return 1;} },
{ { Identifier_type::num, Identifier_type::alnum }, [](const string&, const string&) {return -1;} },
{ { Identifier_type::num, Identifier_type::num }, cmp_num_prerel_ids }
};
// Compare prerelease identifiers based on their types.
int compare_prerel_identifiers(const Prerelease_identifier& l, const Prerelease_identifier& r) {
auto cmp = comparators.at({ l.second, r.second });
return cmp(l.first, r.first);
}
inline int cmp_rel_prerel(const Prerelease_identifiers& l, const Prerelease_identifiers& r) {
if (l.empty() && !r.empty()) return 1;
if (r.empty() && !l.empty()) return -1;
return 0;
} }
} }
namespace version { namespace version {
int Semver200_comparator::compare(const Version_data& l, const Version_data& r) const { int Semver200_comparator::compare(const Version_data& l, const Version_data& r) const {
// Compare normal version components.
int cmp = compare_normal(l, r); int cmp = compare_normal(l, r);
if (cmp != 0) return cmp; if (cmp != 0) return cmp;
// release version is always higher than prerelease // Compare if one version is release and the other prerelease; release is always higher than prerelease.
if (l.prerelease_ids.empty() && !r.prerelease_ids.empty()) return 1; cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids);
if (r.prerelease_ids.empty() && !l.prerelease_ids.empty()) return -1; if (cmp != 0) return cmp;
// compare prerelease by looking at each identifier: numeric ones are compared as numbers, // Compare prerelease by looking at each identifier: numeric ones are compared as numbers,
// alphanum as ASCII strings // alphanum as ASCII strings.
auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size()); auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size());
for (size_t i = 0; i < shorter; i++) { for (size_t i = 0; i < shorter; i++) {
cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]); cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]);
if (cmp != 0) return cmp; if (cmp != 0) return cmp;
} }
// prerels are the same, to the length of the shorter one; // Prerelease identifiers are the same, to the length of the shorter version string;
// if they are the same length, then versions are equal, otherwise, longer wins // if they are the same length, then versions are equal, otherwise, longer one wins.
if (l.prerelease_ids.size() == r.prerelease_ids.size()) return 0; if (l.prerelease_ids.size() == r.prerelease_ids.size()) return 0;
return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1; return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1;
} }
+16 -8
View File
@@ -35,23 +35,28 @@ namespace {
using Validator = function<void(const string&, const char)>; using Validator = function<void(const string&, const char)>;
using State_transition_hook = function<void(string&)>; using State_transition_hook = function<void(string&)>;
/// State transition is described by a character that triggers it, a cstate to transition to and /// State transition is described by a character that triggers it, a state to transition to and
/// optional hook to be invoked on transition. /// optional hook to be invoked on transition.
using Transition = tuple<const char, Parser_state, State_transition_hook>; using Transition = tuple<const char, Parser_state, State_transition_hook>;
using Transitions = vector<Transition>; using Transitions = vector<Transition>;
using State = tuple<Transitions, string&, Validator>; using State = tuple<Transitions, string&, Validator>;
using State_machine = map<Parser_state, State>; using State_machine = map<Parser_state, State>;
// Ranges of characters allowed in prerelease and build identifiers.
const vector<pair<char, char>> allowed_prerel_id_chars = {
{ '0', '9' },{ 'A','Z' },{ 'a','z' },{ '.','.' },{ '-','-' }
};
inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) { inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) {
return make_tuple(c, p, pth); return make_tuple(c, p, pth);
} }
/// Advance parser cstate machine by a single step. /// Advance parser state machine by a single step.
/** /**
Perform single step of parser cstate machine: if character matches one from transition tables - Perform single step of parser state machine: if character matches one from transition tables -
trigger transition to next cstate; otherwise, validate if current token is in legal cstate trigger transition to next state; otherwise, validate if current token is in legal state
(throw Parse_error if not) and then add character to current token; State transition includes (throw Parse_error if not) and then add character to current token; State transition includes
preparing various vars for next cstate and invoking cstate transition hook (if specified) which is preparing various vars for next state and invoking state transition hook (if specified) which is
where whole tokens are validated. where whole tokens are validated.
*/ */
inline void process_char(const char c, Parser_state& cstate, Parser_state& pstate, inline void process_char(const char c, Parser_state& cstate, Parser_state& pstate,
@@ -74,9 +79,13 @@ namespace {
if (tgt.compare(0, 1, "0") == 0) throw version::Parse_error("leading 0 not allowed"); if (tgt.compare(0, 1, "0") == 0) throw version::Parse_error("leading 0 not allowed");
} }
/// Validate prerelease and build version components. /// Validate that prerelease and build version identifiers are comprised of allowed chars only.
inline void prerelease_version_validator(const string&, const char c) { inline void prerelease_version_validator(const string&, const char c) {
if ((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && c != '.' && c != '-') bool res = false;
for (const auto& r : allowed_prerel_id_chars) {
res |= (c >= r.first && c <= r.second);
}
if (!res)
throw version::Parse_error("invalid character encountered: " + string(1, c)); throw version::Parse_error("invalid character encountered: " + string(1, c));
} }
@@ -113,7 +122,6 @@ namespace {
id.clear(); id.clear();
} }
} }
namespace version { namespace version {