Make Formatter a non-interface

I had originally created `Formatter` as an interface as I was intending to implement this differently for linux and windows (for terminal coloring).

Color printing is instead implemented by the `Printer` interface / PIMPL classes.

Replace the multi-boolean constructor with a `Style` struct, as this will make life easier when we want to add / remove flags.

Bug: tint:282
Change-Id: I630073ed7a76c023348b66e8a8517b00b2b6a0d2
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31569
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/diagnostic/formatter.cc b/src/diagnostic/formatter.cc
index abb7232..6257425 100644
--- a/src/diagnostic/formatter.cc
+++ b/src/diagnostic/formatter.cc
@@ -18,6 +18,7 @@
 #include <sstream>
 
 #include "src/diagnostic/diagnostic.h"
+#include "src/diagnostic/printer.h"
 
 namespace tint {
 namespace diag {
@@ -57,154 +58,152 @@
   return stream;
 }
 
-class BasicFormatter : public Formatter {
- public:
-  struct State {
-    explicit State(Printer* p) : printer(p) {}
-    ~State() { flush(); }
+}  // namespace
 
-    // set_style() sets the current style to new_style, flushing any pending
-    // messages to the printer if the style changed.
-    void set_style(const Style& new_style) {
-      if (style.color != new_style.color || style.bold != new_style.bold) {
-        flush();
-        style = new_style;
-      }
+/// State holds the internal formatter state for a format() call.
+struct Formatter::State {
+  /// Constructs a State associated with the given printer.
+  /// @param p the printer to write formatted messages to.
+  explicit State(Printer* p) : printer(p) {}
+  ~State() { flush(); }
+
+  /// set_style() sets the current style to new_style, flushing any pending
+  /// messages to the printer if the style changed.
+  /// @param new_style the new style to apply for future written messages.
+  void set_style(const diag::Style& new_style) {
+    if (style.color != new_style.color || style.bold != new_style.bold) {
+      flush();
+      style = new_style;
     }
+  }
 
-    // flush() writes any pending messages to the printer, clearing the buffer.
-    void flush() {
-      auto str = stream.str();
-      if (str.length() > 0) {
-        printer->write(str, style);
-        std::stringstream reset;
-        stream.swap(reset);
-      }
+  /// flush writes any pending messages to the printer, clearing the buffer.
+  void flush() {
+    auto str = stream.str();
+    if (str.length() > 0) {
+      printer->write(str, style);
+      std::stringstream reset;
+      stream.swap(reset);
     }
+  }
 
-    // operator<<() queues msg to be written to the printer.
-    template <typename T>
-    State& operator<<(const T& msg) {
-      stream << msg;
-      return *this;
-    }
+  /// operator<< queues msg to be written to the printer.
+  /// @param msg the value or string to write to the printer
+  /// @returns this State so that calls can be chained
+  template <typename T>
+  State& operator<<(const T& msg) {
+    stream << msg;
+    return *this;
+  }
 
-    // newline() queues a newline to be written to the printer.
-    void newline() { stream << std::endl; }
+  /// newline queues a newline to be written to the printer.
+  void newline() { stream << std::endl; }
 
-    // repeat() queues the character c to be writen to the printer n times.
-    void repeat(char c, size_t n) {
-      while (n-- > 0) {
-        stream << c;
-      }
-    }
-
-   private:
-    Printer* printer;
-    Style style;
-    std::stringstream stream;
-  };
-
-  BasicFormatter(bool print_file, bool print_severity, bool print_line)
-      : print_file_(print_file),
-        print_severity_(print_severity),
-        print_line_(print_line) {}
-
-  void format(const List& list, Printer* printer) const override {
-    State state{printer};
-
-    bool first = true;
-    for (auto diag : list) {
-      state.set_style({});
-      if (!first) {
-        state.newline();
-      }
-      format(diag, &state);
-      first = false;
+  /// repeat queues the character c to be writen to the printer n times.
+  /// @param c the character to print |n| times
+  /// @param n the number of times to print character |c|
+  void repeat(char c, size_t n) {
+    while (n-- > 0) {
+      stream << c;
     }
   }
 
  private:
-  void format(const Diagnostic& diag, State* state) const {
-    auto const& src = diag.source;
-    auto const& rng = src.range;
-
-    state->set_style({Color::kDefault, true});
-
-    if (print_file_ && src.file != nullptr && !src.file->path.empty()) {
-      (*state) << src.file->path;
-      if (rng.begin.line > 0) {
-        (*state) << ":" << rng.begin;
-      }
-    } else {
-      (*state) << rng.begin;
-    }
-    if (print_severity_) {
-      switch (diag.severity) {
-        case Severity::Warning:
-          state->set_style({Color::kYellow, true});
-          break;
-        case Severity::Error:
-        case Severity::Fatal:
-          state->set_style({Color::kRed, true});
-          break;
-        default:
-          break;
-      }
-      (*state) << " " << diag.severity;
-    }
-
-    state->set_style({Color::kDefault, true});
-    (*state) << ": " << diag.message;
-
-    if (print_line_ && src.file != nullptr && rng.begin.line > 0) {
-      state->newline();
-      state->set_style({Color::kDefault, false});
-
-      for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
-        if (line < src.file->lines.size() + 1) {
-          auto len = src.file->lines[line - 1].size();
-
-          (*state) << src.file->lines[line - 1];
-          state->newline();
-          state->set_style({Color::kCyan, false});
-
-          if (line == rng.begin.line && line == rng.end.line) {
-            // Single line
-            state->repeat(' ', rng.begin.column - 1);
-            state->repeat(
-                '^', std::max<size_t>(rng.end.column - rng.begin.column, 1));
-          } else if (line == rng.begin.line) {
-            // Start of multi-line
-            state->repeat(' ', rng.begin.column - 1);
-            state->repeat('^', len - (rng.begin.column - 1));
-          } else if (line == rng.end.line) {
-            // End of multi-line
-            state->repeat('^', rng.end.column - 1);
-          } else {
-            // Middle of multi-line
-            state->repeat('^', len);
-          }
-          state->newline();
-        }
-      }
-
-      state->set_style({});
-    }
-  }
-
-  const bool print_file_ = false;
-  const bool print_severity_ = false;
-  const bool print_line_ = false;
+  Printer* printer;
+  diag::Style style;
+  std::stringstream stream;
 };
 
-}  // namespace
+Formatter::Formatter() {}
+Formatter::Formatter(const Style& style) : style_(style) {}
 
-std::unique_ptr<Formatter> Formatter::create(bool print_file,
-                                             bool print_severity,
-                                             bool print_line) {
-  return std::make_unique<BasicFormatter>(print_file, print_severity,
-                                          print_line);
+void Formatter::format(const List& list, Printer* printer) const {
+  State state{printer};
+
+  bool first = true;
+  for (auto diag : list) {
+    state.set_style({});
+    if (!first) {
+      state.newline();
+    }
+    format(diag, state);
+    first = false;
+  }
+}
+
+void Formatter::format(const Diagnostic& diag, State& state) const {
+  auto const& src = diag.source;
+  auto const& rng = src.range;
+
+  state.set_style({Color::kDefault, true});
+
+  if (style_.print_file && src.file != nullptr && !src.file->path.empty()) {
+    state << src.file->path;
+    if (rng.begin.line > 0) {
+      state << ":" << rng.begin;
+    }
+  } else {
+    state << rng.begin;
+  }
+  if (style_.print_severity) {
+    switch (diag.severity) {
+      case Severity::Warning:
+        state.set_style({Color::kYellow, true});
+        break;
+      case Severity::Error:
+      case Severity::Fatal:
+        state.set_style({Color::kRed, true});
+        break;
+      default:
+        break;
+    }
+    state << " " << diag.severity;
+  }
+
+  state.set_style({Color::kDefault, true});
+  state << ": " << diag.message;
+
+  if (style_.print_line && src.file != nullptr && rng.begin.line > 0) {
+    state.newline();
+    state.set_style({Color::kDefault, false});
+
+    for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
+      if (line < src.file->lines.size() + 1) {
+        auto len = src.file->lines[line - 1].size();
+
+        state << src.file->lines[line - 1];
+
+        state.newline();
+        state.set_style({Color::kCyan, false});
+
+        if (line == rng.begin.line && line == rng.end.line) {
+          // Single line
+          state.repeat(' ', rng.begin.column - 1);
+          state.repeat('^',
+                       std::max<size_t>(rng.end.column - rng.begin.column, 1));
+        } else if (line == rng.begin.line) {
+          // Start of multi-line
+          state.repeat(' ', rng.begin.column - 1);
+          state.repeat('^', len - (rng.begin.column - 1));
+        } else if (line == rng.end.line) {
+          // End of multi-line
+          state.repeat('^', rng.end.column - 1);
+        } else {
+          // Middle of multi-line
+          state.repeat('^', len);
+        }
+        state.newline();
+      }
+    }
+
+    state.set_style({});
+  }
+}
+
+std::string Formatter::format(const List& list) const {
+  StringPrinter printer;
+  format(list, &printer);
+  return printer.str();
 }
 
 Formatter::~Formatter() = default;
diff --git a/src/diagnostic/formatter.h b/src/diagnostic/formatter.h
index 43061dd..36d3ef3 100644
--- a/src/diagnostic/formatter.h
+++ b/src/diagnostic/formatter.h
@@ -18,39 +18,49 @@
 #include <memory>
 #include <string>
 
-#include "src/diagnostic/printer.h"
-
 namespace tint {
 namespace diag {
 
+class Diagnostic;
 class List;
+class Printer;
 
-/// Formatters are used to format a list of diagnostics into a human readable
-/// string.
+/// Formatter are used to print a list of diagnostics messages.
 class Formatter {
  public:
-  /// @returns a basic diagnostic formatter
-  /// @param print_file include the file path for each diagnostic
-  /// @param print_severity include the severity for each diagnostic
-  /// @param print_line include the source line(s) for the diagnostic
-  static std::unique_ptr<Formatter> create(bool print_file,
-                                           bool print_severity,
-                                           bool print_line);
+  /// Style controls the formatter's output style.
+  struct Style {
+    /// include the file path for each diagnostic
+    bool print_file = true;
+    /// include the severity for each diagnostic
+    bool print_severity = true;
+    /// include the source line(s) for the diagnostic
+    bool print_line = true;
+  };
 
-  virtual ~Formatter();
+  /// Constructor for the formatter using a default style.
+  Formatter();
 
-  /// format prints the formatted diagnostic list |list| to |printer|.
+  /// Constructor for the formatter using the custom style.
+  /// @param style the style used for the formatter.
+  explicit Formatter(const Style& style);
+
+  ~Formatter();
+
   /// @param list the list of diagnostic messages to format
   /// @param printer the printer used to display the formatted diagnostics
-  virtual void format(const List& list, Printer* printer) const = 0;
+  void format(const List& list, Printer* printer) const;
 
   /// @return the list of diagnostics |list| formatted to a string.
   /// @param list the list of diagnostic messages to format
-  inline std::string format(const List& list) const {
-    StringPrinter printer;
-    format(list, &printer);
-    return printer.str();
-  }
+  std::string format(const List& list) const;
+
+ private:
+  struct State;
+
+  void format(const Diagnostic& diag, State& state) const;
+
+  Style const style_;
 };
 
 }  // namespace diag
diff --git a/src/diagnostic/formatter_test.cc b/src/diagnostic/formatter_test.cc
index ae52b54..69654a2 100644
--- a/src/diagnostic/formatter_test.cc
+++ b/src/diagnostic/formatter_test.cc
@@ -44,8 +44,8 @@
 };
 
 TEST_F(DiagFormatterTest, Simple) {
-  auto fmt = Formatter::create(false, false, false);
-  auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
+  Formatter fmt{{false, false, false}};
+  auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
   auto* expect = R"(1:14: purr
 2:14: grrr
 3:16: hiss
@@ -54,8 +54,8 @@
 }
 
 TEST_F(DiagFormatterTest, WithFile) {
-  auto fmt = Formatter::create(true, false, false);
-  auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
+  Formatter fmt{{true, false, false}};
+  auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
   auto* expect = R"(file.name:1:14: purr
 file.name:2:14: grrr
 file.name:3:16: hiss
@@ -64,8 +64,8 @@
 }
 
 TEST_F(DiagFormatterTest, WithSeverity) {
-  auto fmt = Formatter::create(false, true, false);
-  auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
+  Formatter fmt{{false, true, false}};
+  auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
   auto* expect = R"(1:14 info: purr
 2:14 warning: grrr
 3:16 error: hiss
@@ -74,8 +74,8 @@
 }
 
 TEST_F(DiagFormatterTest, WithLine) {
-  auto fmt = Formatter::create(false, false, true);
-  auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
+  Formatter fmt{{false, false, true}};
+  auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
   auto* expect = R"(1:14: purr
 the cat says meow
              ^
@@ -96,8 +96,8 @@
 }
 
 TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) {
-  auto fmt = Formatter::create(true, true, true);
-  auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
+  Formatter fmt{{true, true, true}};
+  auto got = fmt.format(List{diag_info, diag_warn, diag_err, diag_fatal});
   auto* expect = R"(file.name:1:14 info: purr
 the cat says meow
              ^
@@ -121,9 +121,8 @@
   Diagnostic multiline{Severity::Warning,
                        Source{Source::Range{{2, 9}, {4, 15}}, &file},
                        "multiline"};
-
-  auto fmt = Formatter::create(false, false, true);
-  auto got = fmt->format(List{multiline});
+  Formatter fmt{{false, false, true}};
+  auto got = fmt.format(List{multiline});
   auto* expect = R"(2:9: multiline
 the dog says woof
         ^^^^^^^^^
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 9a902f4..d407048 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -104,7 +104,8 @@
 
   /// @returns the parser error string
   std::string error() const {
-    return diag::Formatter::create(false, false, false)->format(diags_);
+    diag::Formatter formatter{{false, false, false}};
+    return formatter.format(diags_);
   }
 
   /// @returns the diagnostic messages
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index e111fd2..2d2d0e2 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -23,16 +23,14 @@
 
 class ParserImplErrorTest : public ParserImplTest {};
 
-#define EXPECT(SOURCE, EXPECTED)                                              \
-  do {                                                                        \
-    std::string source = SOURCE;                                              \
-    std::string expected = EXPECTED;                                          \
-    auto* p = parser(source);                                                 \
-    EXPECT_EQ(false, p->Parse());                                             \
-    EXPECT_EQ(true, p->diagnostics().contains_errors());                      \
-    EXPECT_EQ(                                                                \
-        expected,                                                             \
-        diag::Formatter::create(true, true, true)->format(p->diagnostics())); \
+#define EXPECT(SOURCE, EXPECTED)                                     \
+  do {                                                               \
+    std::string source = SOURCE;                                     \
+    std::string expected = EXPECTED;                                 \
+    auto* p = parser(source);                                        \
+    EXPECT_EQ(false, p->Parse());                                    \
+    EXPECT_EQ(true, p->diagnostics().contains_errors());             \
+    EXPECT_EQ(expected, diag::Formatter().format(p->diagnostics())); \
   } while (false)
 
 TEST_F(ParserImplErrorTest, AdditiveInvalidExpr) {