[writer] Allow for out of order text generation

Add Insert() methods to TextBuffer.
Allows generators to insert helper functions at the top of the output without requiring a scan of the program before generation.

Change-Id: I05f67ad05d189f2249e35cfac99536afccb5578d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57140
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/writer/text_generator.cc b/src/writer/text_generator.cc
index 4dc86d4..27b2d28 100644
--- a/src/writer/text_generator.cc
+++ b/src/writer/text_generator.cc
@@ -39,17 +39,16 @@
   return str;
 }
 
-TextGenerator::LineWriter::LineWriter(TextGenerator* generator)
-    : gen(generator) {}
+TextGenerator::LineWriter::LineWriter(TextBuffer* buf) : buffer(buf) {}
 
 TextGenerator::LineWriter::LineWriter(LineWriter&& other) {
-  gen = other.gen;
-  other.gen = nullptr;
+  buffer = other.buffer;
+  other.buffer = nullptr;
 }
 
 TextGenerator::LineWriter::~LineWriter() {
-  if (gen) {
-    gen->current_buffer_->Append(os.str());
+  if (buffer) {
+    buffer->Append(os.str());
   }
 }
 
@@ -68,12 +67,47 @@
   lines.emplace_back(Line{current_indent, line});
 }
 
+void TextGenerator::TextBuffer::Insert(const std::string& line,
+                                       size_t before,
+                                       uint32_t indent) {
+  if (before >= lines.size()) {
+    diag::List d;
+    TINT_ICE(Writer, d)
+        << "TextBuffer::Insert() called with before >= lines.size()\n"
+        << "  before:" << before << "\n"
+        << "  lines.size(): " << lines.size();
+    return;
+  }
+  lines.insert(lines.begin() + before, Line{indent, line});
+}
+
 void TextGenerator::TextBuffer::Append(const TextBuffer& tb) {
   for (auto& line : tb.lines) {
+    // TODO(bclayton): inefficent, consider optimizing
     lines.emplace_back(Line{current_indent + line.indent, line.content});
   }
 }
 
+void TextGenerator::TextBuffer::Insert(const TextBuffer& tb,
+                                       size_t before,
+                                       uint32_t indent) {
+  if (before >= lines.size()) {
+    diag::List d;
+    TINT_ICE(Writer, d)
+        << "TextBuffer::Insert() called with before >= lines.size()\n"
+        << "  before:" << before << "\n"
+        << "  lines.size(): " << lines.size();
+    return;
+  }
+  size_t idx = 0;
+  for (auto& line : tb.lines) {
+    // TODO(bclayton): inefficent, consider optimizing
+    lines.insert(lines.begin() + before + idx,
+                 Line{indent + line.indent, line.content});
+    idx++;
+  }
+}
+
 std::string TextGenerator::TextBuffer::String() const {
   std::stringstream ss;
   for (auto& line : lines) {
@@ -96,11 +130,14 @@
 }
 
 TextGenerator::ScopedIndent::ScopedIndent(TextGenerator* generator)
-    : gen(generator) {
-  gen->increment_indent();
+    : ScopedIndent(generator->current_buffer_) {}
+
+TextGenerator::ScopedIndent::ScopedIndent(TextBuffer* buffer)
+    : buffer_(buffer) {
+  buffer_->IncrementIndent();
 }
 TextGenerator::ScopedIndent::~ScopedIndent() {
-  gen->decrement_indent();
+  buffer_->DecrementIndent();
 }
 
 }  // namespace writer
diff --git a/src/writer/text_generator.h b/src/writer/text_generator.h
index 885c319..1e75a52 100644
--- a/src/writer/text_generator.h
+++ b/src/writer/text_generator.h
@@ -60,38 +60,6 @@
   std::string TrimSuffix(std::string str, const std::string& suffix);
 
  protected:
-  /// LineWriter is a helper that acts as a string buffer, who's content is
-  /// emitted to the TextGenerator as a single line on destruction.
-  struct LineWriter {
-   public:
-    /// Constructor
-    /// @param generator the TextGenerator that the LineWriter will append its
-    /// content to on destruction
-    explicit LineWriter(TextGenerator* generator);
-    /// Move constructor
-    /// @param rhs the LineWriter to move
-    LineWriter(LineWriter&& rhs);
-    /// Destructor
-    ~LineWriter();
-
-    /// @returns the ostringstream
-    operator std::ostream &() { return os; }
-
-    /// @param rhs the value to write to the line
-    /// @returns the ostream so calls can be chained
-    template <typename T>
-    std::ostream& operator<<(T&& rhs) {
-      return os << std::forward<T>(rhs);
-    }
-
-   private:
-    LineWriter(const LineWriter&) = delete;
-    LineWriter& operator=(const LineWriter&) = delete;
-
-    std::ostringstream os;
-    TextGenerator* gen;
-  };
-
   /// Line holds a single line of text
   struct Line {
     /// The indentation of the line in whitespaces
@@ -120,10 +88,23 @@
     /// @param line the line to append to the TextBuffer
     void Append(const std::string& line);
 
+    /// Inserts the line to the TextBuffer before the line with index `before`
+    /// @param line the line to append to the TextBuffer
+    /// @param before the zero-based index of the line to insert the text before
+    /// @param indent the indentation to apply to the inserted lines
+    void Insert(const std::string& line, size_t before, uint32_t indent);
+
     /// Appends the lines of `tb` to the end of this TextBuffer
     /// @param tb the TextBuffer to append to the end of this TextBuffer
     void Append(const TextBuffer& tb);
 
+    /// Inserts the lines of `tb` to the TextBuffer before the line with index
+    /// `before`
+    /// @param tb the TextBuffer to insert into this TextBuffer
+    /// @param before the zero-based index of the line to insert the text before
+    /// @param indent the indentation to apply to the inserted lines
+    void Insert(const TextBuffer& tb, size_t before, uint32_t indent);
+
     /// @returns the buffer's content as a single string
     std::string String() const;
 
@@ -135,6 +116,39 @@
     std::vector<Line> lines;
   };
 
+  /// LineWriter is a helper that acts as a string buffer, who's content is
+  /// emitted to the TextBuffer as a single line on destruction.
+  struct LineWriter {
+   public:
+    /// Constructor
+    /// @param buffer the TextBuffer that the LineWriter will append its
+    /// content to on destruction, at the end of the buffer.
+    explicit LineWriter(TextBuffer* buffer);
+
+    /// Move constructor
+    /// @param rhs the LineWriter to move
+    LineWriter(LineWriter&& rhs);
+    /// Destructor
+    ~LineWriter();
+
+    /// @returns the ostringstream
+    operator std::ostream &() { return os; }
+
+    /// @param rhs the value to write to the line
+    /// @returns the ostream so calls can be chained
+    template <typename T>
+    std::ostream& operator<<(T&& rhs) {
+      return os << std::forward<T>(rhs);
+    }
+
+   private:
+    LineWriter(const LineWriter&) = delete;
+    LineWriter& operator=(const LineWriter&) = delete;
+
+    std::ostringstream os;
+    TextBuffer* buffer;
+  };
+
   /// Helper for writing a '(' on construction and a ')' destruction.
   struct ScopedParen {
     /// Constructor
@@ -154,7 +168,11 @@
   /// indentation on destruction.
   struct ScopedIndent {
     /// Constructor
-    /// @param generator the TextGenerator that the ScopedIndent will indent
+    /// @param buffer the TextBuffer that the ScopedIndent will indent
+    explicit ScopedIndent(TextBuffer* buffer);
+    /// Constructor
+    /// @param generator ScopedIndent will indent the generator's
+    /// `current_buffer_`
     explicit ScopedIndent(TextGenerator* generator);
     /// Destructor
     ~ScopedIndent();
@@ -163,7 +181,7 @@
     ScopedIndent(ScopedIndent&& rhs) = delete;
     ScopedIndent(const ScopedIndent&) = delete;
     ScopedIndent& operator=(const ScopedIndent&) = delete;
-    TextGenerator* gen;
+    TextBuffer* buffer_;
   };
 
   /// @returns the resolved type of the ast::Expression `expr`
@@ -184,8 +202,14 @@
     return builder_.TypeOf(type_decl);
   }
 
-  /// @returns a new LineWriter, used for buffering and writing a line to out_
-  LineWriter line() { return LineWriter(this); }
+  /// @returns a new LineWriter, used for buffering and writing a line to
+  /// the end of #current_buffer_.
+  LineWriter line() { return LineWriter(current_buffer_); }
+
+  /// @param buffer the TextBuffer to write the line to
+  /// @returns a new LineWriter, used for buffering and writing a line to
+  /// the end of `buffer`.
+  LineWriter line(TextBuffer* buffer) { return LineWriter(buffer); }
 
   /// The program
   Program const* const program_;