From 398ca786c47178de5648b1052cb4274ab50fd026 Mon Sep 17 00:00:00 2001 From: Marko Zivanovic Date: Tue, 13 Oct 2015 21:05:24 +0200 Subject: [PATCH] Comments improvements; minor header files cleanup. --- include/version.h | 93 ++++++++-------- include/version.inl | 84 ++++++++------- src/Semver200_comparator.cpp | 104 +++++++++--------- src/Semver200_parser.cpp | 199 ++++++++++++++++++----------------- test/semver200_parser_util.h | 4 +- 5 files changed, 249 insertions(+), 235 deletions(-) diff --git a/include/version.h b/include/version.h index 765bc8e..6228f67 100644 --- a/include/version.h +++ b/include/version.h @@ -30,7 +30,7 @@ SOFTWARE. namespace version { - /// Parse_error is thrown on all parsing and validation errors. + /// Any error in parsing or validation of version string will result in Parse_error exception being thrown. class Parse_error : public std::runtime_error { using std::runtime_error::runtime_error; }; @@ -40,7 +40,7 @@ namespace version { Type of identifier affects comparison: alphanumeric identifiers are compared as ASCII strings, while numeric identifiers are compared as numbers. */ - enum class Identifier_type { + enum class Id_type { alnum, ///< Identifier is alphanumerical num ///< Identifier is numeric }; @@ -51,22 +51,22 @@ namespace version { These identifiers can be either numerical or alphanumerical. This structure describes one such identifier. */ - using Prerelease_identifier = std::pair; + using Prerelease_identifier = std::pair; /// Container for all prerelease identifiers for a given version string. using Prerelease_identifiers = std::vector; - /// Build identifier is just a string and has no meaning for the purpose of version precedence. + /// Build identifier is arbitrary string with no special meaning with regards to version precedence. using Build_identifier = std::string; - /// Container for all build identifiers for a given version string. + /// Container for all build identifiers of a given version string. using Build_identifiers = std::vector; /// Description of version broken into parts, as per semantic versioning specification. struct Version_data { - int major; ///< Major version, change only on incompatible API modifications - int minor; ///< Minor version, change on compatible API modifications - int patch; ///< Patch version, change only on bugfixes + int major; ///< Major version, change only on incompatible API modifications. + int minor; ///< Minor version, change on backwards-compatible API modifications. + int patch; ///< Patch version, change only on bugfixes. /// Optional series of prerelease identifiers. Prerelease_identifiers prerelease_ids; @@ -75,40 +75,47 @@ namespace version { Build_identifiers build_ids; }; - /// Forward declaration required for operators' template declarations. - template + // Forward declaration required for operators' template declarations. + template class Basic_version; - /// Compare if left version object is less than the right. - template - bool operator<(const Basic_version&, const Basic_version&); + /// Test if left-hand version operand is of lower precedence than the right-hand version. + template + bool operator<(const Basic_version&, + const Basic_version&); - /// Compare if two version objects are equal. - template - bool operator==(const Basic_version& l, const Basic_version& r); + /// Test if left-hand version operand if of equal precedence as the right-hand version. + template + bool operator==(const Basic_version&, + const Basic_version&); /// Output version object to stream using standard semver format (X.Y.Z-PR+B). - template - std::ostream& operator<<(std::ostream& os, const Basic_version& v); + template + std::ostream& operator<<(std::ostream&, + const Basic_version&); - /// Compare if two version objects are different. - template - bool operator!=(const version::Basic_version& l, const version::Basic_version& r); + /// Test if left-hand version and right-hand version are of different precedence. + template + bool operator!=(const Basic_version&, + const Basic_version&); - /// Compare if left version object is greater than the right. - template - bool operator>(const version::Basic_version& l, const version::Basic_version& r); + /// Test if left-hand version operand is of higher precedence than the right-hand version. + template + bool operator>(const Basic_version&, + const Basic_version&); - /// Compare if left version object is greater than or equal the right. - template - bool operator>=(const version::Basic_version& l, const version::Basic_version& r); + /// Test if left-hand version operand is of higher or equal precedence as the right-hand version. + template + bool operator>=(const Basic_version&, + const Basic_version&); - /// Compare if left version object is less than or equal the right. - template - bool operator<=(const version::Basic_version& l, const version::Basic_version& r); + /// Test if left-hand version operand is of lower or equal precedence as the right-hand version. + template + bool operator<=(const Basic_version&, + const Basic_version&); - /// Generic version description and comparison class. + /// Base class for various version parsing and precedence ordering schemes. /** Basic_version class describes general version object without prescribing parsing, validation and comparison rules. These rules are implemented by supplied Parser and @@ -117,11 +124,11 @@ namespace version { template class Basic_version { public: - /// Construct Basic_version object using P to parse default ("0.0.0") version string and C for comparison. - Basic_version(Parser p, Comparator c); + /// Construct Basic_version object using Parser object to parse default ("0.0.0") version string and Comparator for comparison. + Basic_version(Parser, Comparator); - /// Construct Basic_version object using P to parse version string and C for comparison. - Basic_version(const std::string& v, Parser p, Comparator c); + /// Construct Basic_version object using Parser to parse supplied version string and Comparator for comparison. + Basic_version(const std::string&, Parser, Comparator); /// Construct Basic_version by copying data from another one. Basic_version(const Basic_version&); @@ -129,15 +136,15 @@ namespace version { /// Copy version data from another Basic_version to this one. Basic_version& operator=(const Basic_version&); - const int major() const; - const int minor() const; - const int patch() const; - const std::string prerelease() const; - const std::string build() const; + const int major() const; ///< Get major version. + const int minor() const; ///< Get minor version. + const int patch() const; ///< Get patch version. + const std::string prerelease() const; ///< Get prerelease version string. + const std::string build() const; ///< Get build version string. - friend bool operator< <>(const Basic_version& l, const Basic_version& r); - friend bool operator== <>(const Basic_version& l, const Basic_version& r); - friend std::ostream& operator<< <>(std::ostream& os, const Basic_version& v); + friend bool operator< <>(const Basic_version&, const Basic_version&); + friend bool operator== <>(const Basic_version&, const Basic_version&); + friend std::ostream& operator<< <>(std::ostream&s, const Basic_version&); private: Parser parser_; diff --git a/include/version.inl b/include/version.inl index 30b083d..ef93b66 100644 --- a/include/version.inl +++ b/include/version.inl @@ -33,73 +33,77 @@ namespace version { /// Utility function to splice all vector elements to output stream, using designated separator /// between elements and function object for getting values from vector elements. template - std::ostream& splice(std::ostream& os, const std::vector& v, const std::string& sep, F read) { - if (!v.empty()) { - for (auto it = v.cbegin(); it < v.cend() - 1; ++it) { + std::ostream& splice(std::ostream& os, const std::vector& vec, const std::string& sep, F read) { + if (!vec.empty()) { + for (auto it = vec.cbegin(); it < vec.cend() - 1; ++it) { os << read(*it) << sep; } - os << read(*v.crbegin()); + os << read(*vec.crbegin()); } return os; } } - template - Basic_version::Basic_version(P p, C c) + template + Basic_version::Basic_version(Parser p, Comparator c) : parser_(p), comparator_(c), ver_(parser_.parse("0.0.0")) {}; - template - Basic_version::Basic_version(const std::string& v, P p, C c) + template + Basic_version::Basic_version(const std::string& v, Parser p, Comparator c) : parser_(p), comparator_(c), ver_(parser_.parse(v)) {}; - template - Basic_version::Basic_version(const Basic_version&) = default; + template + Basic_version::Basic_version(const Basic_version&) = default; - template - Basic_version& Basic_version::operator=(const Basic_version&) = default; + template + Basic_version& Basic_version::operator=( + const Basic_version&) = default; - template - const int Basic_version::major() const { + template + const int Basic_version::major() const { return ver_.major; } - template - const int Basic_version::minor() const { + template + const int Basic_version::minor() const { return ver_.minor; } - template - const int Basic_version::patch() const { + template + const int Basic_version::patch() const { return ver_.patch; } - template - const std::string Basic_version::prerelease() const { + template + const std::string Basic_version::prerelease() const { std::stringstream ss; splice(ss, ver_.prerelease_ids, ".", [](const auto& id) { return id.first;}); return ss.str(); } - template - const std::string Basic_version::build() const { + template + const std::string Basic_version::build() const { std::stringstream ss; splice(ss, ver_.build_ids, ".", [](const auto& id) { return id;}); return ss.str(); } - template - bool operator<(const Basic_version& l, const Basic_version& r) { + template + bool operator<(const Basic_version& l, + const Basic_version& r) { return l.comparator_.compare(l.ver_, r.ver_) == -1; } - template - bool operator==(const version::Basic_version& l, const version::Basic_version& r) { + template + bool operator==(const Basic_version& l, + const Basic_version& r) { return l.comparator_.compare(l.ver_, r.ver_) == 0; } - template - std::ostream& operator<<(std::ostream& os, const version::Basic_version& v) { + template + std::ostream& operator<<(std::ostream& os, + const Basic_version& v) { os << v.ver_.major << "." << v.ver_.minor << "." << v.ver_.patch; std::string prl = v.prerelease(); if (!prl.empty()) { @@ -112,27 +116,27 @@ namespace version { return os; } - /// Compare if two version objects are different. - template - inline bool operator!=(const version::Basic_version& l, const version::Basic_version& r) { + template + inline bool operator!=(const Basic_version& l, + const Basic_version& r) { return !(l == r); } - /// Compare if left version object is greater than the right. - template - inline bool operator>(const version::Basic_version& l, const version::Basic_version& r) { + template + inline bool operator>(const Basic_version& l, + const Basic_version& r) { return r < l; } - /// Compare if left version object is greater than or equal the right. - template - inline bool operator>=(const version::Basic_version& l, const version::Basic_version& r) { + template + inline bool operator>=(const Basic_version& l, + const Basic_version& r) { return !(l < r); } - /// Compare if left version object is less than or equal the right. - template - inline bool operator<=(const version::Basic_version& l, const version::Basic_version& r) { + template + inline bool operator<=(const Basic_version& l, + const Basic_version& r) { return !(l > r); } } \ No newline at end of file diff --git a/src/Semver200_comparator.cpp b/src/Semver200_comparator.cpp index 0f73d90..e40b33a 100644 --- a/src/Semver200_comparator.cpp +++ b/src/Semver200_comparator.cpp @@ -25,73 +25,71 @@ SOFTWARE. #include #include #include -#include "common/semver200.h" +#include "semver200.h" using namespace std; -namespace { +namespace version { - using namespace version; + namespace { - // Compare normal version identifiers. - 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.minor > r.minor) return 1; - if (l.minor < r.minor) return -1; - if (l.patch > r.patch) return 1; - if (l.patch < r.patch) return -1; - return 0; - } + // Compare normal version identifiers. + 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.minor > r.minor) return 1; + if (l.minor < r.minor) return -1; + if (l.patch > r.patch) return 1; + if (l.patch < r.patch) return -1; + return 0; + } - // Compare alphanumeric prerelease identifiers. - inline int cmp_alnum_prerel_ids(const string& l, const string& r) { - auto cmp = l.compare(r); - if (cmp == 0) { - return cmp; - } else { - return cmp > 0 ? 1 : -1; + // Compare alphanumeric prerelease identifiers. + inline int cmp_alnum_prerel_ids(const string& l, const string& r) { + auto cmp = l.compare(r); + if (cmp == 0) { + return cmp; + } else { + return cmp > 0 ? 1 : -1; + } + } + + // Compare numeric prerelease identifiers. + inline int cmp_num_prerel_ids(const string& l, const string& r) { + long long li = stoll(l); + long long ri = stoll(r); + if (li == ri) return 0; + return li > ri ? 1 : -1; + } + + using Prerel_type_pair = pair; + using Prerel_id_comparator = function; + const map comparators = { + { { Id_type::alnum, Id_type::alnum }, cmp_alnum_prerel_ids }, + { { Id_type::alnum, Id_type::num }, [](const string&, const string&) {return 1;} }, + { { Id_type::num, Id_type::alnum }, [](const string&, const string&) {return -1;} }, + { { Id_type::num, Id_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; } } - // Compare numeric prerelease identifiers. - inline int cmp_num_prerel_ids(const string& l, const string& r) { - long long li = stoll(l); - long long ri = stoll(r); - if (li == ri) return 0; - return li > ri ? 1 : -1; - } - - using Prerel_types = pair; - using Prerel_id_comparator = function; - const map 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 { - int Semver200_comparator::compare(const Version_data& l, const Version_data& r) const { // Compare normal version components. int cmp = compare_normal(l, r); if (cmp != 0) return cmp; - // Compare if one version is release and the other prerelease; release is always higher than prerelease. + // Compare if one version is release and the other prerelease - release is always higher. cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids); if (cmp != 0) return cmp; diff --git a/src/Semver200_parser.cpp b/src/Semver200_parser.cpp index 14af444..d20f5bb 100644 --- a/src/Semver200_parser.cpp +++ b/src/Semver200_parser.cpp @@ -24,108 +24,113 @@ SOFTWARE. #include #include -#include "common/semver200.h" +#include "semver200.h" + +#ifdef _MSC_VER +// disable symbol name too long warning +#pragma warning(disable:4503) +#endif using namespace std; -namespace { - enum class Parser_state { - major, minor, patch, prerelease, build - }; - - using Validator = function; - using State_transition_hook = function; - /// State transition is described by a character that triggers it, a state to transition to and - /// optional hook to be invoked on transition. - using Transition = tuple; - using Transitions = vector; - using State = tuple; - using State_machine = map; - - // Ranges of characters allowed in prerelease and build identifiers. - const vector> allowed_prerel_id_chars = { - { '0', '9' },{ 'A','Z' },{ 'a','z' },{ '-','-' } - }; - - inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) { - return make_tuple(c, p, pth); - } - - /// Advance parser state machine by a single step. - /** - Perform single step of parser state machine: if character matches one from transition tables - - 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 - preparing various vars for next state and invoking state transition hook (if specified) which is - where whole tokens are validated. - */ - inline void process_char(const char c, Parser_state& cstate, Parser_state& pstate, - const Transitions& transitions, string& target, Validator validate) { - for (const auto& transition : transitions) { - if (c == get<0>(transition)) { - if (get<2>(transition)) get<2>(transition)(target); - pstate = cstate; - cstate = get<1>(transition); - return; - } - } - validate(target, c); - target.push_back(c); - } - - /// Validate normal (major, minor, patch) version components. - inline void normal_version_validator(const string& tgt, const char c) { - if (c < '0' || c > '9') throw version::Parse_error("invalid character encountered: " + string(1, c)); - if (tgt.compare(0, 1, "0") == 0) throw version::Parse_error("leading 0 not allowed"); - } - - /// Validate that prerelease and build version identifiers are comprised of allowed chars only. - inline void prerelease_version_validator(const string&, const char 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)); - } - - inline bool is_identifier_numeric(const string& id) { - return id.find_first_not_of("0123456789") == string::npos; - } - - inline bool check_for_leading_0(const string& str) { - return str.length() > 1 && str[0] == '0'; - } - - /// Validate every individual prerelease identifier, determine it's type and add it to collection. - void prerelease_hook_impl(string& id, version::Prerelease_identifiers& prerelease) { - using namespace version; - if (id.empty()) throw Parse_error("version identifier cannot be empty"); - Identifier_type t = Identifier_type::alnum; - if (is_identifier_numeric(id)) { - t = Identifier_type::num; - if (check_for_leading_0(id)) { - throw Parse_error("numeric identifiers cannot have leading 0"); - } - } - prerelease.push_back(Prerelease_identifier(id, t)); - id.clear(); - } - - /// Validate every individual build identifier and add it to collection. - void build_hook_impl(string& id, Parser_state& pstate, version::Build_identifiers& build, - std::string& prerelease_id, version::Prerelease_identifiers& prerelease) { - // process last token left from parsing prerelease data - if (pstate == Parser_state::prerelease) prerelease_hook_impl(prerelease_id, prerelease); - if (id.empty()) throw version::Parse_error("version identifier cannot be empty"); - build.push_back(id); - id.clear(); - } - -} - namespace version { + namespace { + enum class Parser_state { + major, minor, patch, prerelease, build + }; + + using Validator = function; + using State_transition_hook = function; + /// State transition is described by a character that triggers it, a state to transition to and + /// optional hook to be invoked on transition. + using Transition = tuple; + using Transitions = vector; + using State = tuple; + using State_machine = map; + + // Ranges of characters allowed in prerelease and build identifiers. + const vector> allowed_prerel_id_chars = { + { '0', '9' },{ 'A','Z' },{ 'a','z' },{ '-','-' } + }; + + inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) { + return make_tuple(c, p, pth); + } + + /// Advance parser state machine by a single step. + /** + Perform single step of parser state machine: if character matches one from transition tables - + 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 + preparing various vars for next state and invoking state transition hook (if specified) which is + where whole tokens are validated. + */ + inline void process_char(const char c, Parser_state& cstate, Parser_state& pstate, + const Transitions& transitions, string& target, Validator validate) { + for (const auto& transition : transitions) { + if (c == get<0>(transition)) { + if (get<2>(transition)) get<2>(transition)(target); + pstate = cstate; + cstate = get<1>(transition); + return; + } + } + validate(target, c); + target.push_back(c); + } + + /// Validate normal (major, minor, patch) version components. + inline void normal_version_validator(const string& tgt, const char c) { + if (c < '0' || c > '9') throw Parse_error("invalid character encountered: " + string(1, c)); + if (tgt.compare(0, 1, "0") == 0) throw Parse_error("leading 0 not allowed"); + } + + /// Validate that prerelease and build version identifiers are comprised of allowed chars only. + inline void prerelease_version_validator(const string&, const char c) { + bool res = false; + for (const auto& r : allowed_prerel_id_chars) { + res |= (c >= r.first && c <= r.second); + } + if (!res) + throw Parse_error("invalid character encountered: " + string(1, c)); + } + + inline bool is_identifier_numeric(const string& id) { + return id.find_first_not_of("0123456789") == string::npos; + } + + inline bool check_for_leading_0(const string& str) { + return str.length() > 1 && str[0] == '0'; + } + + /// Validate every individual prerelease identifier, determine it's type and add it to collection. + void prerelease_hook_impl(string& id, Prerelease_identifiers& prerelease) { + using namespace version; + if (id.empty()) throw Parse_error("version identifier cannot be empty"); + Id_type t = Id_type::alnum; + if (is_identifier_numeric(id)) { + t = Id_type::num; + if (check_for_leading_0(id)) { + throw Parse_error("numeric identifiers cannot have leading 0"); + } + } + prerelease.push_back(Prerelease_identifier(id, t)); + id.clear(); + } + + /// Validate every individual build identifier and add it to collection. + void build_hook_impl(string& id, Parser_state& pstate, Build_identifiers& build, + std::string& prerelease_id, Prerelease_identifiers& prerelease) { + // process last token left from parsing prerelease data + if (pstate == Parser_state::prerelease) prerelease_hook_impl(prerelease_id, prerelease); + if (id.empty()) throw Parse_error("version identifier cannot be empty"); + build.push_back(id); + id.clear(); + } + + } + /// Parse semver 2.0.0-compatible string to Version_data structure. /** Version text parser is implemented as a state machine. In each step one successive character from version diff --git a/test/semver200_parser_util.h b/test/semver200_parser_util.h index fbb13b6..861f7a3 100644 --- a/test/semver200_parser_util.h +++ b/test/semver200_parser_util.h @@ -32,8 +32,8 @@ version::Semver200_parser p; const version::Prerelease_identifiers no_rel_ids; const version::Build_identifiers no_build_ids; -#define N Identifier_type::num -#define A Identifier_type::alnum +#define N Id_type::num +#define A Id_type::alnum #define BOOST_PATCH BOOST_VERSION % 100 #define BOOST_MINOR BOOST_VERSION / 100 % 1000