Build "spec id->default val str" mapping from string

Add function `ParseDefaultValuesString()` to build the spec id->default
value string mapping required by `SetSpecConstantDefaultValuePass`.
diff --git a/source/opt/set_spec_constant_default_value_pass.cpp b/source/opt/set_spec_constant_default_value_pass.cpp
index 2c62c24..9ea304d 100644
--- a/source/opt/set_spec_constant_default_value_pass.cpp
+++ b/source/opt/set_spec_constant_default_value_pass.cpp
@@ -15,6 +15,8 @@
 #include "set_spec_constant_default_value_pass.h"
 
 #include <cstring>
+#include <cctype>
+#include <string>
 #include <tuple>
 #include <unordered_map>
 #include <vector>
@@ -23,6 +25,7 @@
 #include "util/parse_number.h"
 
 #include "def_use_manager.h"
+#include "make_unique.h"
 #include "type_manager.h"
 #include "types.h"
 
@@ -32,6 +35,7 @@
 namespace {
 using spvutils::NumberType;
 using spvutils::EncodeNumberStatus;
+using spvutils::ParseNumber;
 using spvutils::ParseAndEncodeNumber;
 
 // Given a numeric value in a null-terminated c string and the expected type of
@@ -248,5 +252,58 @@
   return modified;
 }
 
+// Returns true if the given char is ':', '\0' or considered as blank space
+// (i.e.: '\n', '\r', '\v', '\t', '\f' and ' ').
+bool IsSeparator(char ch) {
+  return std::strchr(":\0", ch) || std::isspace(ch) != 0;
+}
+
+std::unique_ptr<SetSpecConstantDefaultValuePass::SpecIdToValueStrMap>
+SetSpecConstantDefaultValuePass::ParseDefaultValuesString(const char* str) {
+  if (!str) return nullptr;
+
+  auto spec_id_to_value = MakeUnique<SpecIdToValueStrMap>();
+
+  // The parsing loop, break when points to the end.
+  while (*str) {
+    // Find the spec id.
+    while (std::isspace(*str)) str++;  // skip leading spaces.
+    const char* entry_begin = str;
+    while (!IsSeparator(*str)) str++;
+    const char* entry_end = str;
+    std::string spec_id_str(entry_begin, entry_end - entry_begin);
+    uint32_t spec_id = 0;
+    if (!ParseNumber(spec_id_str.c_str(), &spec_id)) {
+      // The spec id is not a valid uint32 number.
+      return nullptr;
+    }
+    auto iter = spec_id_to_value->find(spec_id);
+    if (iter != spec_id_to_value->end()) {
+      // Same spec id has been defined before
+      return nullptr;
+    }
+    // Find the ':', spaces between the spec id and the ':' are not allowed.
+    if (*str++ != ':') {
+      // ':' not found
+      return nullptr;
+    }
+    // Find the value string
+    const char* val_begin = str;
+    while (!IsSeparator(*str)) str++;
+    const char* val_end = str;
+    if (val_end == val_begin) {
+      // Value string is empty.
+      return nullptr;
+    }
+    // Update the mapping with spec id and value string.
+    (*spec_id_to_value)[spec_id] = std::string(val_begin, val_end - val_begin);
+
+    // Skip trailing spaces.
+    while (std::isspace(*str)) str++;
+  }
+
+  return spec_id_to_value;
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/set_spec_constant_default_value_pass.h b/source/opt/set_spec_constant_default_value_pass.h
index 9ac545f..0958f19 100644
--- a/source/opt/set_spec_constant_default_value_pass.h
+++ b/source/opt/set_spec_constant_default_value_pass.h
@@ -43,6 +43,42 @@
   const char* name() const override { return "set-spec-const-default-value"; }
   bool Process(ir::Module*) override;
 
+  // Parses the given null-terminated C string to get a mapping from Spec Id to
+  // default value strings. Returns a unique pointer of the mapping from spec
+  // ids to spec constant default value strings built from the given |str| on
+  // success. Returns a nullptr if the given string is not valid for building
+  // the mapping.
+  // A valid string for building the mapping should follow the rule below:
+  //
+  //  "<spec id A>:<default value for A> <spec id B>:<default value for B> ..."
+  //  Example:
+  //    "200:0x11   201:3.14   202:1.4728"
+  //
+  //  Entries are separated with blank spaces (i.e.:' ', '\n', '\r', '\t',
+  //  '\f', '\v'). Each entry corresponds to a Spec Id and default value pair.
+  //  Multiple spaces between, before or after entries are allowed. However,
+  //  spaces are not allowed within spec id or the default value string because
+  //  spaces are always considered as delimiter to separate entries.
+  //
+  //  In each entry, the spec id and value string is separated by ':'. Missing
+  //  ':' in any entry is invalid. And it is invalid to have blank spaces in
+  //  between the spec id and ':' or the default value and ':'.
+  //
+  //  <spec id>: specifies the spec id value.
+  //    The text must represent a valid uint32_t number.
+  //    Hex format with '0x' prefix is allowed.
+  //    Empty <spec id> is not allowed.
+  //    One spec id value can only be defined once, multiple default values
+  //      defined for the same spec id is not allowed. Spec ids with same value
+  //      but different formats (e.g. 0x100 and 256) are considered the same.
+  //
+  //  <default value>: the default value string.
+  //    Spaces before and after default value text is allowed.
+  //    Spaces within the text is not allowed.
+  //    Empty <default value> is not allowed.
+  static std::unique_ptr<SpecIdToValueStrMap> ParseDefaultValuesString(
+      const char* str);
+
  private:
   // The mapping from spec ids to their default values to be set.
   const SpecIdToValueStrMap spec_id_to_value_;
diff --git a/test/opt/test_set_spec_const_default_value.cpp b/test/opt/test_set_spec_const_default_value.cpp
index c093901..0d4a1d2 100644
--- a/test/opt/test_set_spec_const_default_value.cpp
+++ b/test/opt/test_set_spec_const_default_value.cpp
@@ -14,12 +14,121 @@
 
 #include "pass_fixture.h"
 
+#include <gmock/gmock.h>
+
 namespace {
 using namespace spvtools;
 
+using testing::Eq;
+
 using SpecIdToValueStrMap =
     opt::SetSpecConstantDefaultValuePass::SpecIdToValueStrMap;
 
+struct DefaultValuesStringParsingTestCase {
+  const char* default_values_str;
+  bool expect_success;
+  SpecIdToValueStrMap expected_map;
+};
+
+using DefaultValuesStringParsingTest =
+    ::testing::TestWithParam<DefaultValuesStringParsingTestCase>;
+
+TEST_P(DefaultValuesStringParsingTest, TestCase) {
+  const auto& tc = GetParam();
+  auto actual_map =
+      opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString(
+          tc.default_values_str);
+  if (tc.expect_success) {
+    EXPECT_NE(nullptr, actual_map);
+    if (actual_map) EXPECT_THAT(*actual_map, Eq(tc.expected_map));
+  } else {
+    EXPECT_EQ(nullptr, actual_map);
+  }
+}
+
+INSTANTIATE_TEST_CASE_P(
+    ValidString, DefaultValuesStringParsingTest,
+    ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
+        // 0. empty map
+        {"", true, SpecIdToValueStrMap{}},
+        // 1. one pair
+        {"100:1024", true, SpecIdToValueStrMap{{100, "1024"}}},
+        // 2. two pairs
+        {"100:1024 200:2048", true,
+         SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}},
+        // 3. spaces between entries
+        {"100:1024 \n \r \t \v \f 200:2048", true,
+         SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}},
+        // 4. \t, \n, \r and spaces before spec id
+        {"   \n \r\t \t \v \f 100:1024", true,
+         SpecIdToValueStrMap{{100, "1024"}}},
+        // 5. \t, \n, \r and spaces after value string
+        {"100:1024   \n \r\t \t \v \f ", true,
+         SpecIdToValueStrMap{{100, "1024"}}},
+        // 6. maximum spec id
+        {"4294967295:0", true, SpecIdToValueStrMap{{4294967295, "0"}}},
+        // 7. minimum spec id
+        {"0:100", true, SpecIdToValueStrMap{{0, "100"}}},
+        // 8. random content without spaces are allowed
+        {"200:random_stuff", true, SpecIdToValueStrMap{{200, "random_stuff"}}},
+        // 9. support hex format spec id (just because we use the
+        // ParseNumber() utility)
+        {"0x100:1024", true, SpecIdToValueStrMap{{256, "1024"}}},
+        // 10. multiple entries
+        {"101:1 102:2 103:3 104:4 200:201 9999:1000 0x100:333", true,
+         SpecIdToValueStrMap{{101, "1"},
+                             {102, "2"},
+                             {103, "3"},
+                             {104, "4"},
+                             {200, "201"},
+                             {9999, "1000"},
+                             {256, "333"}}},
+        // 11. default value in hex float format
+        {"100:0x0.3p10", true, SpecIdToValueStrMap{{100, "0x0.3p10"}}},
+        // 12. default value in decimal float format
+        {"100:1.5e-13", true, SpecIdToValueStrMap{{100, "1.5e-13"}}},
+    }));
+
+INSTANTIATE_TEST_CASE_P(
+    InvalidString, DefaultValuesStringParsingTest,
+    ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
+        // 0. missing default value
+        {"100:", false, SpecIdToValueStrMap{}},
+        // 1. spec id is not an integer
+        {"100.0:200", false, SpecIdToValueStrMap{}},
+        // 2. spec id is not a number
+        {"something_not_a_number:1", false, SpecIdToValueStrMap{}},
+        // 3. only spec id number
+        {"100", false, SpecIdToValueStrMap{}},
+        // 4. same spec id defined multiple times
+        {"100:20 100:21", false, SpecIdToValueStrMap{}},
+        // 5. Multiple definition of an identical spec id in different forms
+        // is not allowed
+        {"0x100:100 256:200", false, SpecIdToValueStrMap{}},
+        // 6. empty spec id
+        {":3", false, SpecIdToValueStrMap{}},
+        // 7. only colon
+        {":", false, SpecIdToValueStrMap{}},
+        // 8. spec id overflow
+        {"4294967296:200", false, SpecIdToValueStrMap{}},
+        // 9. spec id less than 0
+        {"-1:200", false, SpecIdToValueStrMap{}},
+        // 10. nullptr
+        {nullptr, false, SpecIdToValueStrMap{}},
+        // 11. only a number is invalid
+        {"1234", false, SpecIdToValueStrMap{}},
+        // 12. invalid entry separator
+        {"12:34;23:14", false, SpecIdToValueStrMap{}},
+        // 13. invalid spec id and default value separator
+        {"12@34", false, SpecIdToValueStrMap{}},
+        // 14. spaces before colon
+        {"100   :1024", false, SpecIdToValueStrMap{}},
+        // 15. spaces after colon
+        {"100:   1024", false, SpecIdToValueStrMap{}},
+        // 16. spec id represented in hex float format is invalid
+        {"0x3p10:200", false, SpecIdToValueStrMap{}},
+    }));
+
 struct SetSpecConstantDefaultValueTestCase {
   const char* code;
   SpecIdToValueStrMap default_values;