Add a FailStream error reporting helper

Bug: tint:3
Change-Id: Ie0bb2365a7dd6b2a5e9d0960593fac7586b02922
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/16640
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a949180..b357bf3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -312,6 +312,7 @@
 
 if(${TINT_BUILD_SPV_PARSER})
   list (APPEND TINT_TEST_SRCS
+    reader/spv/fail_stream_test.cc
     reader/spv/parser_test.cc
   )
 endif()
diff --git a/src/reader/spv/fail_stream.h b/src/reader/spv/fail_stream.h
new file mode 100644
index 0000000..b9c4f37
--- /dev/null
+++ b/src/reader/spv/fail_stream.h
@@ -0,0 +1,74 @@
+// Copyright 2020 Google LLC
+//
+// 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_SPV_FAIL_STREAM_H_
+#define SRC_READER_SPV_FAIL_STREAM_H_
+
+#include <ostream>
+
+namespace tint {
+namespace reader {
+namespace spv {
+
+/// A FailStream object accumulates values onto a given std::ostream,
+/// and can be used to record failure by writing the false value
+/// to given a pointer-to-bool.
+class FailStream {
+ public:
+  /// Creates a new fail stream
+  /// @param status_ptr where we will write false to indicate failure. Assumed
+  /// to be a valid pointer to bool.
+  /// @param out output stream where a message should be written to explain
+  /// the failure
+  FailStream(bool* status_ptr, std::ostream* out)
+      : status_ptr_(status_ptr), out_(out) {}
+  /// Copy constructor
+  /// @param other the fail stream to clone
+  FailStream(const FailStream& other) = default;
+
+  /// Converts to a boolean status. A true result indicates success,
+  /// and a false result indicates failure.
+  /// @returns status
+  operator bool() const { return *status_ptr_; }
+  /// Returns the current status value.  This can be more readable
+  /// the conversion operator.
+  /// @returns status
+  bool status() const { return *status_ptr_; }
+
+  /// Records failure.
+  /// @returns a FailStream
+  FailStream& Fail() {
+    *status_ptr_ = false;
+    return *this;
+  }
+
+  /// Appends the given value to the message output stream.
+  /// @param val the value to write to the output stream.
+  /// @returns this object
+  template <typename T>
+  FailStream& operator<<(const T& val) {
+    *out_ << val;
+    return *this;
+  }
+
+ private:
+  bool* status_ptr_;
+  std::ostream* out_;
+};
+
+}  // namespace spv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_READER_SPV_FAIL_STREAM_H_
diff --git a/src/reader/spv/fail_stream_test.cc b/src/reader/spv/fail_stream_test.cc
new file mode 100644
index 0000000..394dc2c
--- /dev/null
+++ b/src/reader/spv/fail_stream_test.cc
@@ -0,0 +1,76 @@
+// 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/spv/fail_stream.h"
+
+#include <memory>
+#include <sstream>
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace reader {
+namespace spv {
+namespace {
+
+using ::testing::Eq;
+
+using FailStreamTest = ::testing::Test;
+
+TEST_F(FailStreamTest, ConversionToBoolIsSameAsStatusMethod) {
+  bool flag = true;
+  FailStream fs(&flag, nullptr);
+
+  EXPECT_TRUE(fs.status());
+  EXPECT_TRUE(bool(fs));
+  flag = false;
+  EXPECT_FALSE(fs.status());
+  EXPECT_FALSE(bool(fs));
+  flag = true;
+  EXPECT_TRUE(fs.status());
+  EXPECT_TRUE(bool(fs));
+}
+
+TEST_F(FailStreamTest, FailMethodChangesStatusToFalse) {
+  bool flag = true;
+  FailStream fs(&flag, nullptr);
+  EXPECT_TRUE(flag);
+  EXPECT_TRUE(bool(fs));
+  fs.Fail();
+  EXPECT_FALSE(flag);
+  EXPECT_FALSE(bool(fs));
+}
+
+TEST_F(FailStreamTest, FailMethodReturnsSelf) {
+  bool flag = true;
+  FailStream fs(&flag, nullptr);
+  FailStream& result = fs.Fail();
+  EXPECT_THAT(&result, Eq(&fs));
+}
+
+TEST_F(FailStreamTest, ShiftOperatorAccumulatesValues) {
+  bool flag = true;
+  std::stringstream ss;
+  FailStream fs(&flag, &ss);
+
+  ss << "prefix ";
+  fs << "cat " << 42;
+
+  EXPECT_THAT(ss.str(), Eq("prefix cat 42"));
+}
+
+}  // namespace
+}  // namespace spv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/reader/spv/parser_test.cc b/src/reader/spv/parser_test.cc
index c32a20c..717034b 100644
--- a/src/reader/spv/parser_test.cc
+++ b/src/reader/spv/parser_test.cc
@@ -24,6 +24,7 @@
 namespace reader {
 namespace spv {
 
+namespace {
 using ParserTest = testing::Test;
 
 TEST_F(ParserTest, Uint32VecEmpty) {
@@ -36,6 +37,8 @@
 // TODO(dneto): uint32 vec, valid SPIR-V
 // TODO(dneto): uint32 vec, invalid SPIR-V
 
+}  // namespace
+
 }  // namespace spv
 }  // namespace reader
 }  // namespace tint