Add reader::spirv::Namer

A Namer is a place for saving and looking up names
based on SPIR-V IDs.

Bug: tint:3
Change-Id: I6aeb2f5f7ba63c2e0a816dcbac88f964beafabc6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17284
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bf0c2e1..2c615df 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -199,6 +199,8 @@
 if(${TINT_BUILD_SPV_READER})
   list(APPEND TINT_LIB_SRCS
     reader/spirv/fail_stream.h
+    reader/spirv/namer.cc
+    reader/spirv/namer.h
     reader/spirv/parser.cc
     reader/spirv/parser.h
     reader/spirv/parser_impl.cc
@@ -313,6 +315,7 @@
 if(${TINT_BUILD_SPV_READER})
   list(APPEND TINT_TEST_SRCS
     reader/spirv/fail_stream_test.cc
+    reader/spirv/namer_test.cc
     reader/spirv/parser_impl_import_test.cc
     reader/spirv/parser_impl_test.cc
     reader/spirv/parser_test.cc
diff --git a/src/reader/spirv/namer.cc b/src/reader/spirv/namer.cc
new file mode 100644
index 0000000..8f316c9
--- /dev/null
+++ b/src/reader/spirv/namer.cc
@@ -0,0 +1,36 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/reader/spirv/namer.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+Namer::Namer(const FailStream& fail_stream) : fail_stream_(fail_stream) {}
+
+Namer::~Namer() = default;
+
+bool Namer::SaveName(uint32_t id, const std::string& name) {
+  if (HasName(id)) {
+    return Fail() << "internal error: ID " << id
+                  << " already has registered name: " << id_to_name_[id];
+  }
+  id_to_name_[id] = name;
+  return true;
+}
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/reader/spirv/namer.h b/src/reader/spirv/namer.h
new file mode 100644
index 0000000..03fc531
--- /dev/null
+++ b/src/reader/spirv/namer.h
@@ -0,0 +1,71 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_READER_SPIRV_NAMER_H_
+#define SRC_READER_SPIRV_NAMER_H_
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+
+#include "src/reader/spirv/fail_stream.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+
+/// A Namer maps SPIR-V IDs to strings.
+class Namer {
+ public:
+  /// Creates a new namer
+  /// @param fail_stream the error reporting stream
+  explicit Namer(const FailStream& fail_stream);
+  /// Destructor
+  ~Namer();
+
+  /// Registers a failure.
+  /// @returns a fail stream to accumulate diagnostics.
+  FailStream& Fail() { return fail_stream_.Fail(); }
+
+  /// @param id the SPIR-V ID
+  /// @returns true if we the given ID already has a registered name.
+  bool HasName(uint32_t id) {
+    return id_to_name_.find(id) != id_to_name_.end();
+  }
+
+  /// @param id the SPIR-V ID
+  /// @returns the name for the ID. It must have been registered.
+  const std::string& GetName(uint32_t id) {
+    return id_to_name_.find(id)->second;
+  }
+
+  /// Records a mapping from the given ID to a name. Emits a failure
+  /// if the ID already has a registered name.
+  /// @param id the SPIR-V ID
+  /// @param name the name to map to the ID
+  /// @returns true if the ID did not have a previously registered name.
+  bool SaveName(uint32_t id, const std::string& name);
+
+ private:
+  FailStream fail_stream_;
+
+  // Maps an ID to its registered name.
+  std::unordered_map<uint32_t, std::string> id_to_name_;
+};
+
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_READER_SPIRV_NAMER_H_
diff --git a/src/reader/spirv/namer_test.cc b/src/reader/spirv/namer_test.cc
new file mode 100644
index 0000000..aa6eeee
--- /dev/null
+++ b/src/reader/spirv/namer_test.cc
@@ -0,0 +1,107 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/reader/spirv/namer.h"
+
+#include <cstdint>
+#include <sstream>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "src/reader/spirv/fail_stream.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+class SpvNamerTest : public testing::Test {
+ public:
+  SpvNamerTest() : fail_stream_(&success_, &errors_) {}
+
+  /// @returns the accumulated diagnostic strings
+  std::string error() { return errors_.str(); }
+
+ protected:
+  std::stringstream errors_;
+  bool success_ = true;
+  FailStream fail_stream_;
+};
+
+TEST_F(SpvNamerTest, NoFailureToStart) {
+  Namer namer(fail_stream_);
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, FailLogsError) {
+  Namer namer(fail_stream_);
+  const bool converted_result = namer.Fail() << "st. johns wood";
+  EXPECT_FALSE(converted_result);
+  EXPECT_EQ(error(), "st. johns wood");
+  EXPECT_FALSE(success_);
+}
+
+TEST_F(SpvNamerTest, NoNameRecorded) {
+  Namer namer(fail_stream_);
+
+  EXPECT_FALSE(namer.HasName(12));
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, SaveNameOnce) {
+  Namer namer(fail_stream_);
+
+  const uint32_t id = 9;
+  EXPECT_FALSE(namer.HasName(id));
+  const bool save_result = namer.SaveName(id, "abbey road");
+  EXPECT_TRUE(save_result);
+  EXPECT_TRUE(namer.HasName(id));
+  EXPECT_EQ(namer.GetName(id), "abbey road");
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, SaveNameTwoIds) {
+  Namer namer(fail_stream_);
+
+  EXPECT_FALSE(namer.HasName(8));
+  EXPECT_FALSE(namer.HasName(9));
+  EXPECT_TRUE(namer.SaveName(8, "abbey road"));
+  EXPECT_TRUE(namer.SaveName(9, "rubber soul"));
+  EXPECT_TRUE(namer.HasName(8));
+  EXPECT_TRUE(namer.HasName(9));
+  EXPECT_EQ(namer.GetName(9), "rubber soul");
+  EXPECT_EQ(namer.GetName(8), "abbey road");
+  EXPECT_TRUE(success_);
+  EXPECT_TRUE(error().empty());
+}
+
+TEST_F(SpvNamerTest, SaveNameFailsDueToIdReuse) {
+  Namer namer(fail_stream_);
+
+  const uint32_t id = 9;
+  EXPECT_TRUE(namer.SaveName(id, "abbey road"));
+  EXPECT_FALSE(namer.SaveName(id, "rubber soul"));
+  EXPECT_TRUE(namer.HasName(id));
+  EXPECT_EQ(namer.GetName(id), "abbey road");
+  EXPECT_FALSE(success_);
+  EXPECT_FALSE(error().empty());
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint