diff --git a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj
index 71e0701f1c..3556e9712a 100644
--- a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj
+++ b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj
@@ -121,6 +121,7 @@
+
@@ -425,6 +426,12 @@
true
true
+
+ true
+ true
+ true
+ true
+
true
true
diff --git a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters
index 873dc89ab2..7f33b0571d 100644
--- a/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters
+++ b/Subtrees/beast/Builds/VisualStudio2012/beast.vcxproj.filters
@@ -734,6 +734,9 @@
beast_core\diagnostic
+
+ beast_core\diagnostic
+
@@ -1147,6 +1150,9 @@
beast_core\diagnostic
+
+ beast_core\diagnostic
+
diff --git a/Subtrees/beast/modules/beast_core/beast_core.cpp b/Subtrees/beast/modules/beast_core/beast_core.cpp
index e3ae5c0f5b..b90b480d03 100644
--- a/Subtrees/beast/modules/beast_core/beast_core.cpp
+++ b/Subtrees/beast/modules/beast_core/beast_core.cpp
@@ -150,6 +150,7 @@ namespace beast
#include "diagnostic/beast_FPUFlags.cpp"
#include "diagnostic/beast_LeakChecked.cpp"
#include "diagnostic/beast_ProtectedCall.cpp"
+#include "diagnostic/beast_SemanticVersion.cpp"
#include "diagnostic/beast_UnitTest.cpp"
#include "diagnostic/beast_UnitTestUtilities.cpp"
diff --git a/Subtrees/beast/modules/beast_core/beast_core.h b/Subtrees/beast/modules/beast_core/beast_core.h
index fc0a336125..aff325acba 100644
--- a/Subtrees/beast/modules/beast_core/beast_core.h
+++ b/Subtrees/beast/modules/beast_core/beast_core.h
@@ -307,6 +307,7 @@ namespace beast
#include "text/beast_LocalisedStrings.h"
#include "text/beast_NewLine.h"
#include "text/beast_StringArray.h"
+#include "diagnostic/beast_SemanticVersion.h"
#include "text/beast_StringPairArray.h"
#include "text/beast_StringPool.h"
#include "text/beast_TextDiff.h"
diff --git a/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.cpp b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.cpp
new file mode 100644
index 0000000000..2cf324a418
--- /dev/null
+++ b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.cpp
@@ -0,0 +1,519 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of Beast: https://github.com/vinniefalco/Beast
+ Copyright 2013, Vinnie Falco
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+SemanticVersion::SemanticVersion ()
+ : majorVersion (0)
+ , minorVersion (0)
+ , patchVersion (0)
+{
+}
+
+bool SemanticVersion::parse (String input)
+{
+ // May not have leading or trailing whitespace
+ if (input.trim () != input)
+ return false;
+
+ // Must have major version number
+ if (! chopUInt (&majorVersion, std::numeric_limits ::max (), input))
+ return false;
+
+ if (! chop (".", input))
+ return false;
+
+ // Must have minor version number
+ if (! chopUInt (&minorVersion, std::numeric_limits ::max (), input))
+ return false;
+
+ if (! chop (".", input))
+ return false;
+
+ // Must have patch version number
+ if (! chopUInt (&patchVersion, std::numeric_limits ::max (), input))
+ return false;
+
+ // May have pre-release identifier list
+ if (chop ("-", input))
+ {
+ chopIdentifiers (&preReleaseIdentifiers, false, input);
+
+ // Must not be empty
+ if (preReleaseIdentifiers.size () <= 0)
+ return false;
+ }
+
+ // May have metadata identifier list
+ if (chop ("+", input))
+ {
+ chopIdentifiers (&metaData, true, input);
+
+ // Must not be empty
+ if (metaData.size () <= 0)
+ return false;
+ }
+
+ // May not have anything left
+ if (input.length () > 0)
+ return false;
+
+ return true;
+}
+
+String SemanticVersion::print () const
+{
+ String s;
+
+ s << String (majorVersion) << "." << String (minorVersion) << "." << String (patchVersion);
+
+ if (preReleaseIdentifiers.size () > 0)
+ s << "-" << printIdentifiers (preReleaseIdentifiers);
+
+ if (metaData.size () > 0)
+ s << "+" << printIdentifiers (metaData);
+
+ return s;
+}
+
+int SemanticVersion::compare (SemanticVersion const& rhs) const noexcept
+{
+ if (majorVersion > rhs.majorVersion)
+ return 1;
+ else if (majorVersion < rhs.majorVersion)
+ return -1;
+
+ if (minorVersion > rhs.minorVersion)
+ return 1;
+ else if (minorVersion < rhs.minorVersion)
+ return -1;
+
+ if (patchVersion > rhs.patchVersion)
+ return 1;
+ else if (patchVersion < rhs.patchVersion)
+ return -1;
+
+ if (isPreRelease () || rhs.isPreRelease ())
+ {
+ // Pre-releases have a lower precedence
+ if (isRelease () && rhs.isPreRelease ())
+ return 1;
+ else if (isPreRelease () && rhs.isRelease ())
+ return -1;
+
+ // Compare pre-release identifiers
+ for (int i = 0; i < bmax (preReleaseIdentifiers.size (), rhs.preReleaseIdentifiers.size ()); ++i)
+ {
+ // A larger list of identifiers has a higher precedence
+ if (i >= rhs.preReleaseIdentifiers.size ())
+ return 1;
+ else if (i >= preReleaseIdentifiers.size ())
+ return -1;
+
+ String const& left (preReleaseIdentifiers [i]);
+ String const& right (rhs.preReleaseIdentifiers [i]);
+
+ // Numeric identifiers have lower precedence
+ if (! isNumeric (left) && isNumeric (right))
+ return 1;
+ else if (isNumeric (left) && ! isNumeric (right))
+ return -1;
+
+ if (isNumeric (left))
+ {
+ bassert (isNumeric (right));
+
+ int const iLeft (left.getIntValue ());
+ int const iRight (right.getIntValue ());
+
+ if (iLeft > iRight)
+ return 1;
+ else if (iLeft < iRight)
+ return -1;
+ }
+ else
+ {
+ bassert (! isNumeric (right));
+
+ int result = left.compareLexicographically (right);
+
+ if (result != 0)
+ return result;
+ }
+ }
+ }
+
+ // metadata is ignored
+
+ return 0;
+}
+
+bool SemanticVersion::isNumeric (String const& s)
+{
+ return String (s.getIntValue ()) == s;
+}
+
+bool SemanticVersion::chop (String const& what, String& input)
+{
+ if (input.startsWith (what))
+ {
+ input = input.substring (what.length ());
+
+ return true;
+ }
+
+ return false;
+}
+
+String SemanticVersion::printIdentifiers (StringArray const& list)
+{
+ String s;
+
+ if (list.size () > 0)
+ {
+ s << list [0];
+
+ for (int i = 1; i < list.size (); ++i)
+ s << "." << list [i];
+ }
+
+ return s;
+}
+
+bool SemanticVersion::chopUInt (int* value, int limit, String& input)
+{
+ // Must not be empty
+ if (input.length () <= 0)
+ return false;
+
+ int firstNonDigit = 0;
+ for (; firstNonDigit < input.length (); ++firstNonDigit)
+ {
+ if (! CharacterFunctions::isDigit (input [firstNonDigit]))
+ break;
+ }
+
+ String const s = input.substring (0, firstNonDigit);
+
+ // Must not be empty
+ if (s.length () <= 0)
+ return false;
+
+ int const n = s.getIntValue ();
+
+ // Must not have leading zeroes
+ if (String (n) != s)
+ return false;
+
+ // Must not be out of range
+ if (n < 0 || n > limit)
+ return false;
+
+ input = input.substring (s.length ());
+
+ *value = n;
+
+ return true;
+}
+
+bool SemanticVersion::chopIdentifier (String* value, bool allowLeadingZeroes, String& input)
+{
+ // Must not be empty
+ if (input.length () <= 0)
+ return false;
+
+ // Must not have a leading 0
+ if (! allowLeadingZeroes && input [0] == '0')
+ return false;
+
+ // Find the first character that cannot be part of an identifier
+ int i;
+ for (i = 0; i < input.length (); ++i)
+ {
+ static char const* validSet =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
+
+ if (! String (validSet).contains (String::charToString (input [i])))
+ break;
+ }
+
+ // Must not be empty
+ if (i <= 0)
+ return false;
+
+ *value = input.substring (0, i);
+
+ input = input.substring (i);
+
+ return true;
+}
+
+bool SemanticVersion::chopIdentifiers (StringArray* value, bool allowLeadingZeroes, String& input)
+{
+ if (input.length () <= 0)
+ return false;
+
+ for (;;)
+ {
+ String s;
+
+ if (! chopIdentifier (&s, allowLeadingZeroes, input))
+ return false;
+
+ value->add (s);
+
+ if (! chop (".", input))
+ break;
+ }
+
+ return true;
+}
+
+//------------------------------------------------------------------------------
+
+class SemanticVersionTests : public UnitTest
+{
+public:
+ SemanticVersionTests () : UnitTest ("SemanticVersion", "beast")
+ {
+ }
+
+ void checkPass (String const& input, bool shouldPass = true)
+ {
+ SemanticVersion v;
+
+ if (shouldPass )
+ {
+ expect (v.parse (input));
+ expect (v.print () == input);
+ }
+ else
+ {
+ expect (! v.parse (input));
+ }
+ }
+
+ void checkFail (String const& input)
+ {
+ checkPass (input, false);
+ }
+
+ // check input and input with appended metadata
+ void checkMeta (String const& input, bool shouldPass)
+ {
+ checkPass (input, shouldPass);
+
+ checkPass (input + "+a", shouldPass);
+ checkPass (input + "+1", shouldPass);
+ checkPass (input + "+a.b", shouldPass);
+ checkPass (input + "+ab.cd", shouldPass);
+
+ checkFail (input + "!");
+ checkFail (input + "+");
+ checkFail (input + "++");
+ checkFail (input + "+!");
+ checkFail (input + "+.");
+ checkFail (input + "+a.!");
+ }
+
+ void checkMetaFail (String const& input)
+ {
+ checkMeta (input, false);
+ }
+
+ // check input, input with appended release data,
+ // input with appended metadata, and input with both
+ // appended release data and appended metadata
+ //
+ void checkRelease (String const& input, bool shouldPass = true)
+ {
+ checkMeta (input, shouldPass);
+
+ checkMeta (input + "-1", shouldPass);
+ checkMeta (input + "-a", shouldPass);
+ checkMeta (input + "-a1", shouldPass);
+ checkMeta (input + "-a1.b1", shouldPass);
+ checkMeta (input + "-ab.cd", shouldPass);
+ checkMeta (input + "--", shouldPass);
+
+ checkMetaFail (input + "+");
+ checkMetaFail (input + "!");
+ checkMetaFail (input + "-");
+ checkMetaFail (input + "-!");
+ checkMetaFail (input + "-.");
+ checkMetaFail (input + "-a.!");
+ checkMetaFail (input + "-0.a");
+ }
+
+ // Checks the major.minor.version string alone and with all
+ // possible combinations of release identifiers and metadata.
+ //
+ void check (String const& input, bool shouldPass = true)
+ {
+ checkRelease (input, shouldPass);
+ }
+
+ void ncheck (String const& input)
+ {
+ check (input, false);
+ }
+
+ void testParse ()
+ {
+ beginTestCase ("parse");
+
+ check ("0.0.0");
+ check ("1.2.3");
+ check ("2147483647.2147483647.2147483647"); // max int
+
+ // negative values
+ ncheck ("-1.2.3");
+ ncheck ("1.-2.3");
+ ncheck ("1.2.-3");
+
+ // missing parts
+ ncheck ("");
+ ncheck ("1");
+ ncheck ("1.");
+ ncheck ("1.2");
+ ncheck ("1.2.");
+ ncheck (".2.3");
+
+ // whitespace
+ ncheck (" 1.2.3");
+ ncheck ("1 .2.3");
+ ncheck ("1.2 .3");
+ ncheck ("1.2.3 ");
+
+ // leading zeroes
+ ncheck ("01.2.3");
+ ncheck ("1.02.3");
+ ncheck ("1.2.03");
+ }
+
+ static StringArray ids ()
+ {
+ return StringArray ();
+ }
+
+ static StringArray ids (String const& s1)
+ {
+ StringArray v;
+ v.add (s1);
+ return v;
+ }
+
+ static StringArray ids (String const& s1, String const& s2)
+ {
+ StringArray v;
+ v.add (s1);
+ v.add (s2);
+ return v;
+ }
+
+ static StringArray ids (String const& s1, String const& s2, String const& s3)
+ {
+ StringArray v;
+ v.add (s1);
+ v.add (s2);
+ v.add (s3);
+ return v;
+ }
+
+ // Checks the decomposition of the input into appropriate values
+ void checkValues (String const& input,
+ int majorVersion,
+ int minorVersion,
+ int patchVersion,
+ StringArray const& preReleaseIdentifiers = StringArray (),
+ StringArray const& metaData = StringArray ())
+ {
+ SemanticVersion v;
+
+ expect (v.parse (input));
+
+ expect (v.majorVersion == majorVersion);
+ expect (v.minorVersion == minorVersion);
+ expect (v.patchVersion == patchVersion);
+
+ expect (v.preReleaseIdentifiers == preReleaseIdentifiers);
+ expect (v.metaData == metaData);
+ }
+
+ void testValues ()
+ {
+ checkValues ("0.1.2", 0, 1, 2);
+ checkValues ("1.2.3", 1, 2, 3);
+ checkValues ("1.2.3-rc1", 1, 2, 3, ids ("rc1"));
+ checkValues ("1.2.3-rc1.debug", 1, 2, 3, ids ("rc1", "debug"));
+ checkValues ("1.2.3-rc1.debug.asm", 1, 2, 3, ids ("rc1", "debug", "asm"));
+ checkValues ("1.2.3+full", 1, 2, 3, ids (), ids ("full"));
+ checkValues ("1.2.3+full.prod", 1, 2, 3, ids (), ids ("full", "prod"));
+ checkValues ("1.2.3+full.prod.x86", 1, 2, 3, ids (), ids ("full", "prod", "x86"));
+ checkValues ("1.2.3-rc1.debug.asm+full.prod.x86", 1, 2, 3,
+ ids ("rc1", "debug", "asm"), ids ("full", "prod", "x86"));
+ }
+
+ // makes sure the left version is less than the right
+ void checkLessInternal (String const& lhs, String const& rhs)
+ {
+ SemanticVersion left;
+ SemanticVersion right;
+
+ expect (left.parse (lhs));
+ expect (right.parse (rhs));
+
+ expect (left.compare (left) == 0);
+ expect (right.compare (right) == 0);
+ expect (left.compare (right) < 0);
+ expect (right.compare (left) > 0);
+
+ expect (left < right);
+ expect (right > left);
+ expect (left == left);
+ expect (right == right);
+ }
+
+ void checkLess (String const& lhs, String const& rhs)
+ {
+ checkLessInternal (lhs, rhs);
+ checkLessInternal (lhs + "+meta", rhs);
+ checkLessInternal (lhs, rhs + "+meta");
+ checkLessInternal (lhs + "+meta", rhs + "+meta");
+ }
+
+ void testCompare ()
+ {
+ checkLess ("1.0.0-alpha", "1.0.0-alpha.1");
+ checkLess ("1.0.0-alpha.1", "1.0.0-alpha.beta");
+ checkLess ("1.0.0-alpha.beta", "1.0.0-beta");
+ checkLess ("1.0.0-beta", "1.0.0-beta.2");
+ checkLess ("1.0.0-beta.2", "1.0.0-beta.11");
+ checkLess ("1.0.0-beta.11", "1.0.0-rc.1");
+ checkLess ("1.0.0-rc.1", "1.0.0");
+ checkLess ("0.9.9", "1.0.0");
+ }
+
+ void runTest ()
+ {
+ testParse ();
+ testValues ();
+ testCompare ();
+ }
+};
+
+static SemanticVersionTests semanticVersionTests;
diff --git a/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.h b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.h
new file mode 100644
index 0000000000..e24d46f666
--- /dev/null
+++ b/Subtrees/beast/modules/beast_core/diagnostic/beast_SemanticVersion.h
@@ -0,0 +1,74 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of Beast: https://github.com/vinniefalco/Beast
+ Copyright 2013, Vinnie Falco
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#ifndef BEAST_SEMANTICVERSION_H_INCLUDED
+#define BEAST_SEMANTICVERSION_H_INCLUDED
+
+/** A Semantic Version number.
+
+ Identifies the build of a particular version of software using
+ the Semantic Versioning Specification described here:
+
+ http://semver.org/
+*/
+class BEAST_API SemanticVersion
+{
+public:
+ int majorVersion;
+ int minorVersion;
+ int patchVersion;
+ StringArray preReleaseIdentifiers;
+ StringArray metaData;
+
+ SemanticVersion ();
+
+ /** Parse a semantic version string.
+ The parsing is as strict as possible.
+ @return `true` if the string was parsed.
+ */
+ bool parse (String input);
+
+ /** Produce a string from semantic version components. */
+ String print () const;
+
+ inline bool isRelease () const noexcept { return preReleaseIdentifiers.size () <= 0; }
+ inline bool isPreRelease () const noexcept { return ! isRelease (); }
+
+ /** Compare this against another version.
+ The comparison follows the rules as per the specification.
+ */
+ int compare (SemanticVersion const& rhs) const noexcept;
+
+ inline bool operator== (SemanticVersion const& other) const noexcept { return compare (other) == 0; }
+ inline bool operator!= (SemanticVersion const& other) const noexcept { return compare (other) != 0; }
+ inline bool operator>= (SemanticVersion const& other) const noexcept { return compare (other) >= 0; }
+ inline bool operator<= (SemanticVersion const& other) const noexcept { return compare (other) <= 0; }
+ inline bool operator> (SemanticVersion const& other) const noexcept { return compare (other) > 0; }
+ inline bool operator< (SemanticVersion const& other) const noexcept { return compare (other) < 0; }
+
+private:
+ static bool isNumeric (String const& s);
+ static String printIdentifiers (StringArray const& list);
+ static bool chop (String const& what, String& input);
+ static bool chopUInt (int* value, int limit, String& input);
+ static bool chopIdentifier (String* value, bool allowLeadingZeroes, String& input);
+ static bool chopIdentifiers (StringArray* value, bool preRelease, String& input);
+};
+
+#endif