[clang-tools-extra] [llvm] [llvm] add support for mustache templating language (PR #105893)

via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 11 15:50:28 PDT 2024


https://github.com/PeterChou1 updated https://github.com/llvm/llvm-project/pull/105893

>From ac1c7b5cbf3024bf8cd4021174a47b914d7f7dea Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 30 Jul 2024 23:58:27 -0400
Subject: [PATCH 01/21] [clang-doc] add suport for clang-doc enum generation

---
 clang-tools-extra/clang-doc/BitcodeReader.cpp |   4 +
 clang-tools-extra/clang-doc/BitcodeWriter.cpp |   2 +
 clang-tools-extra/clang-doc/HTMLGenerator.cpp |  88 ++++++++++++--
 .../clang-doc/Representation.cpp              |   2 +
 clang-tools-extra/clang-doc/Representation.h  |   4 +
 clang-tools-extra/clang-doc/Serialize.cpp     |  14 ++-
 clang-tools-extra/test/clang-doc/enum.cpp     | 113 +++++++++++++-----
 7 files changed, 181 insertions(+), 46 deletions(-)

diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index bfb04e7407b380..1f2fb0a8b2b855 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -415,6 +415,10 @@ template <> llvm::Expected<CommentInfo *> getCommentInfo(TypedefInfo *I) {
   return &I->Description.emplace_back();
 }
 
+template <> llvm::Expected<CommentInfo *> getCommentInfo(EnumValueInfo *I) {
+  return &I->Description.emplace_back();
+}
+
 template <> llvm::Expected<CommentInfo *> getCommentInfo(CommentInfo *I) {
   I->Children.emplace_back(std::make_unique<CommentInfo>());
   return I->Children.back().get();
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index 7e5a11783d303a..06f30f76e33d8c 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -536,6 +536,8 @@ void ClangDocBitcodeWriter::emitBlock(const EnumValueInfo &I) {
   emitRecord(I.Name, ENUM_VALUE_NAME);
   emitRecord(I.Value, ENUM_VALUE_VALUE);
   emitRecord(I.ValueExpr, ENUM_VALUE_EXPR);
+  for (const auto &CI : I.Description)
+    emitBlock(CI);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index aef22453035c30..a37192d6ceb9b0 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -48,6 +48,12 @@ class HTMLTag {
     TAG_SPAN,
     TAG_TITLE,
     TAG_UL,
+    TAG_TABLE,
+    TAG_THEAD,
+    TAG_TBODY,
+    TAG_TR,
+    TAG_TD,
+    TAG_TH
   };
 
   HTMLTag() = default;
@@ -133,6 +139,12 @@ bool HTMLTag::isSelfClosing() const {
   case HTMLTag::TAG_SPAN:
   case HTMLTag::TAG_TITLE:
   case HTMLTag::TAG_UL:
+  case HTMLTag::TAG_TABLE:
+  case HTMLTag::TAG_THEAD:
+  case HTMLTag::TAG_TBODY:
+  case HTMLTag::TAG_TR:
+  case HTMLTag::TAG_TD:
+  case HTMLTag::TAG_TH:
     return false;
   }
   llvm_unreachable("Unhandled HTMLTag::TagType");
@@ -174,6 +186,18 @@ StringRef HTMLTag::toString() const {
     return "title";
   case HTMLTag::TAG_UL:
     return "ul";
+  case HTMLTag::TAG_TABLE:
+    return "table";
+  case HTMLTag::TAG_THEAD:
+    return "thead";
+  case HTMLTag::TAG_TBODY:
+    return "tbody";
+  case HTMLTag::TAG_TR:
+    return "tr";
+  case HTMLTag::TAG_TD:
+    return "td";
+  case HTMLTag::TAG_TH:
+    return "th";
   }
   llvm_unreachable("Unhandled HTMLTag::TagType");
 }
@@ -352,6 +376,7 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx);
 static std::vector<std::unique_ptr<TagNode>>
 genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
         StringRef ParentInfoDir);
+static std::unique_ptr<TagNode> genHTML(const std::vector<CommentInfo> &C);
 
 static std::vector<std::unique_ptr<TagNode>>
 genEnumsBlock(const std::vector<EnumInfo> &Enums,
@@ -372,14 +397,33 @@ genEnumsBlock(const std::vector<EnumInfo> &Enums,
 }
 
 static std::unique_ptr<TagNode>
-genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members) {
+genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members,
+                    bool HasComments) {
   if (Members.empty())
     return nullptr;
 
-  auto List = std::make_unique<TagNode>(HTMLTag::TAG_UL);
-  for (const auto &M : Members)
-    List->Children.emplace_back(
-        std::make_unique<TagNode>(HTMLTag::TAG_LI, M.Name));
+  auto List = std::make_unique<TagNode>(HTMLTag::TAG_TBODY);
+
+  for (const auto &M : Members) {
+    auto TRNode = std::make_unique<TagNode>(HTMLTag::TAG_TR);
+    TRNode->Children.emplace_back(
+        std::make_unique<TagNode>(HTMLTag::TAG_TD, M.Name));
+    // Use user supplied value if it exists, otherwise use the value
+    if (!M.ValueExpr.empty()) {
+      TRNode->Children.emplace_back(
+          std::make_unique<TagNode>(HTMLTag::TAG_TD, M.ValueExpr));
+    } else {
+      TRNode->Children.emplace_back(
+          std::make_unique<TagNode>(HTMLTag::TAG_TD, M.Value));
+    }
+
+    if (HasComments) {
+      auto TD = std::make_unique<TagNode>(HTMLTag::TAG_TD);
+      TD->Children.emplace_back(genHTML(M.Description));
+      TRNode->Children.emplace_back(std::move(TD));
+    }
+    List->Children.emplace_back(std::move(TRNode));
+  }
   return List;
 }
 
@@ -653,15 +697,35 @@ static std::vector<std::unique_ptr<TagNode>>
 genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
   std::vector<std::unique_ptr<TagNode>> Out;
   std::string EnumType = I.Scoped ? "enum class " : "enum ";
+  // Determine if enum members have comments attached
+  bool HasComments = false;
+  for (const auto &M : I.Members) {
+    if (!M.Description.empty()) {
+      HasComments = true;
+      break;
+    }
+  }
+  std::unique_ptr<TagNode> Table =
+      std::make_unique<TagNode>(HTMLTag::TAG_TABLE);
+  std::unique_ptr<TagNode> Thead =
+      std::make_unique<TagNode>(HTMLTag::TAG_THEAD);
+  std::unique_ptr<TagNode> TRow = std::make_unique<TagNode>(HTMLTag::TAG_TR);
+  std::unique_ptr<TagNode> TD =
+      std::make_unique<TagNode>(HTMLTag::TAG_TH, EnumType + I.Name);
+  // Span 3 columns if enum has comments
+  TD->Attributes.emplace_back("colspan", HasComments ? "3" : "2");
+
+  Table->Attributes.emplace_back("id", llvm::toHex(llvm::toStringRef(I.USR)));
+  TRow->Children.emplace_back(std::move(TD));
+  Thead->Children.emplace_back(std::move(TRow));
+  Table->Children.emplace_back(std::move(Thead));
+
+  std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members, HasComments);
 
-  Out.emplace_back(
-      std::make_unique<TagNode>(HTMLTag::TAG_H3, EnumType + I.Name));
-  Out.back()->Attributes.emplace_back("id",
-                                      llvm::toHex(llvm::toStringRef(I.USR)));
-
-  std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members);
   if (Node)
-    Out.emplace_back(std::move(Node));
+    Table->Children.emplace_back(std::move(Node));
+
+  Out.emplace_back(std::move(Table));
 
   if (I.DefLoc) {
     if (!CDCtx.RepositoryUrl)
diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index d08afbb9621890..028dffc21793ae 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -266,6 +266,8 @@ void EnumInfo::merge(EnumInfo &&Other) {
     Scoped = Other.Scoped;
   if (Members.empty())
     Members = std::move(Other.Members);
+  if (Other.HasComments || HasComments)
+    HasComments = true;
   SymbolInfo::merge(std::move(Other));
 }
 
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index d70c279f7a2bdc..db3aff2d60a1b5 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -431,6 +431,8 @@ struct EnumValueInfo {
   // Stores the user-supplied initialization expression for this enumeration
   // constant. This will be empty for implicit enumeration values.
   SmallString<16> ValueExpr;
+
+  std::vector<CommentInfo> Description; // Comment description of this field.
 };
 
 // TODO: Expand to allow for documenting templating.
@@ -443,6 +445,8 @@ struct EnumInfo : public SymbolInfo {
 
   // Indicates whether this enum is scoped (e.g. enum class).
   bool Scoped = false;
+  // Indicates whether or not enum members have comments attached
+  bool HasComments = false;
 
   // Set to nonempty to the type when this is an explicitly typed enum. For
   //   enum Foo : short { ... };
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 3b074d849e8a9c..78b7041368d6df 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -394,10 +394,20 @@ static void parseEnumerators(EnumInfo &I, const EnumDecl *D) {
     std::string ValueExpr;
     if (const Expr *InitExpr = E->getInitExpr())
       ValueExpr = getSourceCode(D, InitExpr->getSourceRange());
-
     SmallString<16> ValueStr;
     E->getInitVal().toString(ValueStr);
-    I.Members.emplace_back(E->getNameAsString(), ValueStr, ValueExpr);
+    I.Members.emplace_back(E->getNameAsString(), ValueStr.str(), ValueExpr);
+    ASTContext &Context = E->getASTContext();
+    RawComment *Comment = E->getASTContext().getRawCommentForDeclNoCache(E);
+    if (Comment) {
+      CommentInfo CInfo;
+      Comment->setAttached();
+      if (comments::FullComment *Fc = Comment->parse(Context, nullptr, E)) {
+        EnumValueInfo &Member = I.Members.back();
+        Member.Description.emplace_back();
+        parseFullComment(Fc, Member.Description.back());
+      }
+    }
   }
 }
 
diff --git a/clang-tools-extra/test/clang-doc/enum.cpp b/clang-tools-extra/test/clang-doc/enum.cpp
index e559940a31de69..fd7bbcb53f2d2b 100644
--- a/clang-tools-extra/test/clang-doc/enum.cpp
+++ b/clang-tools-extra/test/clang-doc/enum.cpp
@@ -21,9 +21,9 @@
 enum Color {
 // MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]*
 // HTML-INDEX-LINE: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
-  Red, ///< Red
-  Green, ///< Green
-  Blue ///< Blue
+  Red, ///< Comment 1
+  Green, ///< Comment 2
+  Blue ///< Comment 3
 };
 
 // MD-INDEX: ## Enums
@@ -34,11 +34,16 @@ enum Color {
 // MD-INDEX: | Blue |
 // MD-INDEX: **brief** For specifying RGB colors
 
-// HTML-INDEX: <h2 id="Enums">Enums</h2>
-// HTML-INDEX: <h3 id="{{([0-9A-F]{40})}}">enum Color</h3>
-// HTML-INDEX: <li>Red</li>
-// HTML-INDEX: <li>Green</li>
-// HTML-INDEX: <li>Blue</li>
+// HTML-INDEX: <th colspan="3">enum Color</th>
+// HTML-INDEX: <td>Red</td>
+// HTML-INDEX: <td>0</td>
+// HTML-INDEX: <p> Comment 1</p>
+// HTML-INDEX: <td>Green</td>
+// HTML-INDEX: <td>1</td>
+// HTML-INDEX: <p> Comment 2</p>
+// HTML-INDEX: <td>Blue</td>
+// HTML-INDEX: <td>2</td>
+// HTML-INDEX: <p> Comment 3</p>
 
 /**
  * @brief Shape Types
@@ -46,11 +51,12 @@ enum Color {
 enum class Shapes {
 // MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]*
 // HTML-INDEX-LINE: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
-  /// Circle
+
+  /// Comment 1
   Circle,
-  /// Rectangle
+  /// Comment 2
   Rectangle,
-  /// Triangle
+  /// Comment 3
   Triangle
 };
 // MD-INDEX: | enum class Shapes |
@@ -60,10 +66,17 @@ enum class Shapes {
 // MD-INDEX: | Triangle |
 // MD-INDEX: **brief** Shape Types
 
-// HTML-INDEX: <h3 id="{{([0-9A-F]{40})}}">enum class Shapes</h3>
-// HTML-INDEX: <li>Circle</li>
-// HTML-INDEX: <li>Rectangle</li>
-// HTML-INDEX: <li>Triangle</li>
+// HTML-INDEX: <th colspan="3">enum class Shapes</th>
+// HTML-INDEX: <td>Circle</td>
+// HTML-INDEX: <td>0</td>
+// HTML-INDEX: <p> Comment 1</p>
+// HTML-INDEX: <td>Rectangle</td>
+// HTML-INDEX: <td>1</td>
+// HTML-INDEX: <p> Comment 2</p>
+// HTML-INDEX: <td>Triangle</td>
+// HTML-INDEX: <td>2</td>
+// HTML-INDEX: <p> Comment 3</p>
+
 
 
 class Animals {
@@ -76,18 +89,25 @@ class Animals {
       enum AnimalType {
 // MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]*
 // HTML-ANIMAL-LINE: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
-          Dog, /// Man's best friend
-          Cat, /// Man's other best friend
-          Iguana /// A lizard
+          Dog, ///< Man's best friend
+          Cat, ///< Man's other best friend
+          Iguana ///< A lizard
       };
 };
 
 // HTML-ANIMAL: <h1>class Animals</h1>
 // HTML-ANIMAL: <h2 id="Enums">Enums</h2>
-// HTML-ANIMAL: <h3 id="{{([0-9A-F]{40})}}">enum AnimalType</h3>
-// HTML-ANIMAL: <li>Dog</li>
-// HTML-ANIMAL: <li>Cat</li>
-// HTML-ANIMAL: <li>Iguana</li>
+// HTML-ANIMAL: <th colspan="3">enum AnimalType</th>
+// HTML-ANIMAL: <td>Dog</td>
+// HTML-ANIMAL: <td>0</td>
+// HTML-ANIMAL: <p> Man's best friend</p>
+// HTML-ANIMAL: <td>Cat</td>
+// HTML-ANIMAL: <td>1</td>
+// HTML-ANIMAL: <p> Man's other best friend</p>
+// HTML-ANIMAL: <td>Iguana</td>
+// HTML-ANIMAL: <td>2</td>
+// HTML-ANIMAL: <p> A lizard</p>
+
 
 // MD-ANIMAL: # class Animals
 // MD-ANIMAL: ## Enums
@@ -106,10 +126,11 @@ namespace Vehicles {
     enum Car {
 // MD-VEHICLES-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]*
 // HTML-VEHICLES-LINE: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
-       Sedan, /// Sedan
-       SUV, /// SUV
-       Pickup, /// Pickup
-       Hatchback /// Hatchback
+
+       Sedan, ///< Comment 1
+       SUV, ///< Comment 2
+       Pickup, ///< Comment 3
+       Hatchback ///< Comment 4
     };
 }
 
@@ -124,9 +145,37 @@ namespace Vehicles {
 // MD-VEHICLES: **brief** specify type of car
 
 // HTML-VEHICLES: <h1>namespace Vehicles</h1>
-// HTML-VEHICLES: <h2 id="Enums">Enums</h2>
-// HTML-VEHICLES: <h3 id="{{([0-9A-F]{40})}}">enum Car</h3>
-// HTML-VEHICLES: <li>Sedan</li>
-// HTML-VEHICLES: <li>SUV</li>
-// HTML-VEHICLES: <li>Pickup</li>
-// HTML-VEHICLES: <li>Hatchback</li>
\ No newline at end of file
+// HTML-VEHICLES: <th colspan="3">enum Car</th>
+// HTML-VEHICLES: <td>Sedan</td>
+// HTML-VEHICLES: <td>0</td>
+// HTML-VEHICLES: <p> Comment 1</p>
+// HTML-VEHICLES: <td>SUV</td>
+// HTML-VEHICLES: <td>1</td>
+// HTML-VEHICLES: <p> Comment 2</p>
+// HTML-VEHICLES: <td>Pickup</td>
+// HTML-VEHICLES: <td>2</td>
+// HTML-VEHICLES: <p> Comment 3</p>
+// HTML-VEHICLES: <td>Hatchback</td>
+// HTML-VEHICLES: <td>3</td>
+// HTML-VEHICLES: <p> Comment 4</p>
+
+
+enum ColorUserSpecified {
+  RedUserSpecified = 'A',
+  GreenUserSpecified = 2,
+  BlueUserSpecified = 'C'
+};
+
+// MD-INDEX: | enum ColorUserSpecified |
+// MD-INDEX: --
+// MD-INDEX: | RedUserSpecified |
+// MD-INDEX: | GreenUserSpecified |
+// MD-INDEX: | BlueUserSpecified |
+
+// HTML-INDEX: <th colspan="2">enum ColorUserSpecified</th>
+// HTML-INDEX: <td>RedUserSpecified</td>
+// HTML-INDEX: <td>'A'</td>
+// HTML-INDEX: <td>GreenUserSpecified</td>
+// HTML-INDEX: <td>2</td>
+// HTML-INDEX: <td>BlueUserSpecified</td>
+// HTML-INDEX: <td>'C'</td>
\ No newline at end of file

>From bcc4b0dc8b3b0216e6249c4de6f2cbcf14006bf6 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Wed, 31 Jul 2024 00:02:13 -0400
Subject: [PATCH 02/21] [clang-doc] remove useless code

---
 clang-tools-extra/clang-doc/Representation.cpp | 2 --
 clang-tools-extra/clang-doc/Representation.h   | 2 --
 2 files changed, 4 deletions(-)

diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 028dffc21793ae..d08afbb9621890 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -266,8 +266,6 @@ void EnumInfo::merge(EnumInfo &&Other) {
     Scoped = Other.Scoped;
   if (Members.empty())
     Members = std::move(Other.Members);
-  if (Other.HasComments || HasComments)
-    HasComments = true;
   SymbolInfo::merge(std::move(Other));
 }
 
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index db3aff2d60a1b5..bd5254b0a84657 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -445,8 +445,6 @@ struct EnumInfo : public SymbolInfo {
 
   // Indicates whether this enum is scoped (e.g. enum class).
   bool Scoped = false;
-  // Indicates whether or not enum members have comments attached
-  bool HasComments = false;
 
   // Set to nonempty to the type when this is an explicitly typed enum. For
   //   enum Foo : short { ... };

>From 5fe47ca87f8dd592fee7a45401eed2620152e5c1 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Wed, 31 Jul 2024 00:33:48 -0400
Subject: [PATCH 03/21] [clang-doc] modify unittest

---
 .../unittests/clang-doc/HTMLGeneratorTest.cpp | 33 +++++++++++++++----
 1 file changed, 27 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
index e4a7340318b934..7ee482e275149d 100644
--- a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
@@ -92,7 +92,13 @@ TEST(HTMLGeneratorTest, emitNamespaceHTML) {
     </div>
     <h2 id="Enums">Enums</h2>
     <div>
-      <h3 id="0000000000000000000000000000000000000000">enum OneEnum</h3>
+      <table id="0000000000000000000000000000000000000000">
+        <thead>
+          <tr>
+            <th colspan="2">enum OneEnum</th>
+          </tr>
+        </thead>
+      </table>
     </div>
   </div>
   <div id="sidebar-right" class="col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right">
@@ -212,7 +218,13 @@ TEST(HTMLGeneratorTest, emitRecordHTML) {
     </div>
     <h2 id="Enums">Enums</h2>
     <div>
-      <h3 id="0000000000000000000000000000000000000000">enum OneEnum</h3>
+      <table id="0000000000000000000000000000000000000000">
+        <thead>
+          <tr>
+            <th colspan="2">enum OneEnum</th>
+          </tr>
+        </thead>
+      </table>
     </div>
   </div>
   <div id="sidebar-right" class="col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right">
@@ -346,10 +358,19 @@ TEST(HTMLGeneratorTest, emitEnumHTML) {
 <main>
   <div id="sidebar-left" path="" class="col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"></div>
   <div id="main-content" class="col-xs-12 col-sm-9 col-md-8 main-content">
-    <h3 id="0000000000000000000000000000000000000000">enum class e</h3>
-    <ul>
-      <li>X</li>
-    </ul>
+    <table id="0000000000000000000000000000000000000000">
+      <thead>
+        <tr>
+          <th colspan="2">enum class e</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td>X</td>
+          <td>0</td>
+        </tr>
+      </tbody>
+    </table>
     <p>
       Defined at line 
       <a href="https://www.repository.com/test.cpp#10">10</a>

>From 28fb40f0cdbe37257d8aea9f05519519d9f2c470 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Mon, 12 Aug 2024 17:12:48 -0400
Subject: [PATCH 04/21] [clang-doc] address pr comments

---
 clang-tools-extra/clang-doc/HTMLGenerator.cpp | 38 ++++++++-----------
 .../clang-doc/Representation.cpp              |  2 +-
 clang-tools-extra/clang-doc/Representation.h  |  4 +-
 clang-tools-extra/clang-doc/Serialize.cpp     |  8 ++--
 4 files changed, 23 insertions(+), 29 deletions(-)

diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index a37192d6ceb9b0..ad7e08667e5cbc 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -397,8 +397,7 @@ genEnumsBlock(const std::vector<EnumInfo> &Enums,
 }
 
 static std::unique_ptr<TagNode>
-genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members,
-                    bool HasComments) {
+genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members) {
   if (Members.empty())
     return nullptr;
 
@@ -416,8 +415,7 @@ genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members,
       TRNode->Children.emplace_back(
           std::make_unique<TagNode>(HTMLTag::TAG_TD, M.Value));
     }
-
-    if (HasComments) {
+    if (M.Description.empty()) {
       auto TD = std::make_unique<TagNode>(HTMLTag::TAG_TD);
       TD->Children.emplace_back(genHTML(M.Description));
       TRNode->Children.emplace_back(std::move(TD));
@@ -663,7 +661,7 @@ static std::unique_ptr<HTMLNode> genHTML(const CommentInfo &I) {
     }
     return std::move(FullComment);
   }
- 
+
   if (I.Kind == "ParagraphComment") {
     auto ParagraphComment = std::make_unique<TagNode>(HTMLTag::TAG_P);
     for (const auto &Child : I.Children) {
@@ -698,16 +696,12 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
   std::vector<std::unique_ptr<TagNode>> Out;
   std::string EnumType = I.Scoped ? "enum class " : "enum ";
   // Determine if enum members have comments attached
-  bool HasComments = false;
-  for (const auto &M : I.Members) {
-    if (!M.Description.empty()) {
-      HasComments = true;
-      break;
-    }
-  }
+  bool HasComments =
+      std::any_of(I.Members.begin(), I.Members.end(),
+                  [](const EnumValueInfo &M) { return M.Description.empty(); });
   std::unique_ptr<TagNode> Table =
       std::make_unique<TagNode>(HTMLTag::TAG_TABLE);
-  std::unique_ptr<TagNode> Thead =
+  std::unique_ptr<TagNode> THead =
       std::make_unique<TagNode>(HTMLTag::TAG_THEAD);
   std::unique_ptr<TagNode> TRow = std::make_unique<TagNode>(HTMLTag::TAG_TR);
   std::unique_ptr<TagNode> TD =
@@ -717,10 +711,10 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
 
   Table->Attributes.emplace_back("id", llvm::toHex(llvm::toStringRef(I.USR)));
   TRow->Children.emplace_back(std::move(TD));
-  Thead->Children.emplace_back(std::move(TRow));
-  Table->Children.emplace_back(std::move(Thead));
+  THead->Children.emplace_back(std::move(TRow));
+  Table->Children.emplace_back(std::move(THead));
 
-  std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members, HasComments);
+  std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members);
 
   if (Node)
     Table->Children.emplace_back(std::move(Node));
@@ -731,8 +725,8 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
     if (!CDCtx.RepositoryUrl)
       Out.emplace_back(writeFileDefinition(*I.DefLoc));
     else
-      Out.emplace_back(writeFileDefinition(
-          *I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
+      Out.emplace_back(
+          writeFileDefinition(*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
   }
 
   std::string Description;
@@ -780,8 +774,8 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
     if (!CDCtx.RepositoryUrl)
       Out.emplace_back(writeFileDefinition(*I.DefLoc));
     else
-      Out.emplace_back(writeFileDefinition(
-          *I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
+      Out.emplace_back(
+          writeFileDefinition(*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
   }
 
   std::string Description;
@@ -847,8 +841,8 @@ genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
     if (!CDCtx.RepositoryUrl)
       Out.emplace_back(writeFileDefinition(*I.DefLoc));
     else
-      Out.emplace_back(writeFileDefinition(
-          *I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
+      Out.emplace_back(
+          writeFileDefinition(*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
   }
 
   std::string Description;
diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index d08afbb9621890..da948ee74c9d63 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -221,7 +221,7 @@ void SymbolInfo::merge(SymbolInfo &&Other) {
 }
 
 NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path)
-      : Info(InfoType::IT_namespace, USR, Name, Path) {}
+    : Info(InfoType::IT_namespace, USR, Name, Path) {}
 
 void NamespaceInfo::merge(NamespaceInfo &&Other) {
   assert(mergeable(Other));
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index bd5254b0a84657..873ac728066261 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -48,7 +48,7 @@ enum class InfoType {
 // A representation of a parsed comment.
 struct CommentInfo {
   CommentInfo() = default;
-  CommentInfo(CommentInfo &Other) = delete;
+  CommentInfo(CommentInfo &Other) = default;
   CommentInfo(CommentInfo &&Other) = default;
   CommentInfo &operator=(CommentInfo &&Other) = default;
 
@@ -432,7 +432,7 @@ struct EnumValueInfo {
   // constant. This will be empty for implicit enumeration values.
   SmallString<16> ValueExpr;
 
-  std::vector<CommentInfo> Description; // Comment description of this field.
+  std::vector<CommentInfo> Description; /// Comment description of this field.
 };
 
 // TODO: Expand to allow for documenting templating.
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 78b7041368d6df..273bc10d3b55d8 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -398,8 +398,8 @@ static void parseEnumerators(EnumInfo &I, const EnumDecl *D) {
     E->getInitVal().toString(ValueStr);
     I.Members.emplace_back(E->getNameAsString(), ValueStr.str(), ValueExpr);
     ASTContext &Context = E->getASTContext();
-    RawComment *Comment = E->getASTContext().getRawCommentForDeclNoCache(E);
-    if (Comment) {
+    if (RawComment *Comment =
+            E->getASTContext().getRawCommentForDeclNoCache(E)) {
       CommentInfo CInfo;
       Comment->setAttached();
       if (comments::FullComment *Fc = Comment->parse(Context, nullptr, E)) {
@@ -568,7 +568,7 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
 static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) {
   assert(D && "Expect non-null FieldDecl in populateMemberTypeInfo");
 
-  ASTContext& Context = D->getASTContext();
+  ASTContext &Context = D->getASTContext();
   // TODO investigate whether we can use ASTContext::getCommentForDecl instead
   // of this logic. See also similar code in Mapper.cpp.
   RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
@@ -576,7 +576,7 @@ static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) {
     return;
 
   Comment->setAttached();
-  if (comments::FullComment* fc = Comment->parse(Context, nullptr, D)) {
+  if (comments::FullComment *fc = Comment->parse(Context, nullptr, D)) {
     I.Description.emplace_back();
     parseFullComment(fc, I.Description.back());
   }

>From 0d150ea08af767017e108096533f0c0d8b31668a Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Mon, 12 Aug 2024 17:24:55 -0400
Subject: [PATCH 05/21] [clang-doc] revert CommentInfo change

---
 clang-tools-extra/clang-doc/Representation.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index 873ac728066261..8f2bba786316fe 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -48,7 +48,7 @@ enum class InfoType {
 // A representation of a parsed comment.
 struct CommentInfo {
   CommentInfo() = default;
-  CommentInfo(CommentInfo &Other) = default;
+  CommentInfo(CommentInfo &Other) = delete;
   CommentInfo(CommentInfo &&Other) = default;
   CommentInfo &operator=(CommentInfo &&Other) = default;
 

>From e5e70b87003e4e7b8e68d23fc89f7edd6961764f Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Mon, 12 Aug 2024 18:29:39 -0400
Subject: [PATCH 06/21] [clang-doc] fix test

---
 clang-tools-extra/clang-doc/HTMLGenerator.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index ad7e08667e5cbc..00f94788ced44c 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -415,7 +415,7 @@ genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members) {
       TRNode->Children.emplace_back(
           std::make_unique<TagNode>(HTMLTag::TAG_TD, M.Value));
     }
-    if (M.Description.empty()) {
+    if (!M.Description.empty()) {
       auto TD = std::make_unique<TagNode>(HTMLTag::TAG_TD);
       TD->Children.emplace_back(genHTML(M.Description));
       TRNode->Children.emplace_back(std::move(TD));

>From 1c4f631df65c1560523ea63c82229102f3d99f54 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Mon, 12 Aug 2024 19:19:39 -0400
Subject: [PATCH 07/21] [clang-doc] fix unittest

---
 clang-tools-extra/clang-doc/HTMLGenerator.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index 00f94788ced44c..6baed082af4a81 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -698,7 +698,7 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
   // Determine if enum members have comments attached
   bool HasComments =
       std::any_of(I.Members.begin(), I.Members.end(),
-                  [](const EnumValueInfo &M) { return M.Description.empty(); });
+                  [](const EnumValueInfo &M) { return !M.Description.empty(); });
   std::unique_ptr<TagNode> Table =
       std::make_unique<TagNode>(HTMLTag::TAG_TABLE);
   std::unique_ptr<TagNode> THead =

>From b8f3f9c97ccd503c07387f1364cb3b357fa92821 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Mon, 12 Aug 2024 19:35:15 -0400
Subject: [PATCH 08/21] [clang-doc] address pr comments

---
 clang-tools-extra/clang-doc/HTMLGenerator.cpp | 20 +++++++++----------
 clang-tools-extra/clang-doc/Serialize.cpp     |  2 +-
 clang-tools-extra/test/clang-doc/enum.cpp     |  2 +-
 3 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index 6baed082af4a81..5b023a409c364f 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -696,9 +696,9 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
   std::vector<std::unique_ptr<TagNode>> Out;
   std::string EnumType = I.Scoped ? "enum class " : "enum ";
   // Determine if enum members have comments attached
-  bool HasComments =
-      std::any_of(I.Members.begin(), I.Members.end(),
-                  [](const EnumValueInfo &M) { return !M.Description.empty(); });
+  bool HasComments = std::any_of(
+      I.Members.begin(), I.Members.end(),
+      [](const EnumValueInfo &M) { return !M.Description.empty(); });
   std::unique_ptr<TagNode> Table =
       std::make_unique<TagNode>(HTMLTag::TAG_TABLE);
   std::unique_ptr<TagNode> THead =
@@ -713,10 +713,8 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
   TRow->Children.emplace_back(std::move(TD));
   THead->Children.emplace_back(std::move(TRow));
   Table->Children.emplace_back(std::move(THead));
-
-  std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members);
-
-  if (Node)
+  
+  if (std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members))
     Table->Children.emplace_back(std::move(Node));
 
   Out.emplace_back(std::move(Table));
@@ -774,8 +772,8 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
     if (!CDCtx.RepositoryUrl)
       Out.emplace_back(writeFileDefinition(*I.DefLoc));
     else
-      Out.emplace_back(
-          writeFileDefinition(*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
+      Out.emplace_back(writeFileDefinition(
+          *I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
   }
 
   std::string Description;
@@ -841,8 +839,8 @@ genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
     if (!CDCtx.RepositoryUrl)
       Out.emplace_back(writeFileDefinition(*I.DefLoc));
     else
-      Out.emplace_back(
-          writeFileDefinition(*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
+      Out.emplace_back(writeFileDefinition(
+          *I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
   }
 
   std::string Description;
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 273bc10d3b55d8..b9db78cf7d688f 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -568,7 +568,7 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
 static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) {
   assert(D && "Expect non-null FieldDecl in populateMemberTypeInfo");
 
-  ASTContext &Context = D->getASTContext();
+  ASTContext& Context = D->getASTContext();
   // TODO investigate whether we can use ASTContext::getCommentForDecl instead
   // of this logic. See also similar code in Mapper.cpp.
   RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
diff --git a/clang-tools-extra/test/clang-doc/enum.cpp b/clang-tools-extra/test/clang-doc/enum.cpp
index fd7bbcb53f2d2b..ef768e33b45668 100644
--- a/clang-tools-extra/test/clang-doc/enum.cpp
+++ b/clang-tools-extra/test/clang-doc/enum.cpp
@@ -178,4 +178,4 @@ enum ColorUserSpecified {
 // HTML-INDEX: <td>GreenUserSpecified</td>
 // HTML-INDEX: <td>2</td>
 // HTML-INDEX: <td>BlueUserSpecified</td>
-// HTML-INDEX: <td>'C'</td>
\ No newline at end of file
+// HTML-INDEX: <td>'C'</td>

>From bb98aad362353e44267f2df6ffb985005e67f147 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Mon, 12 Aug 2024 19:45:02 -0400
Subject: [PATCH 09/21] [clang-doc] clang-format

---
 clang-tools-extra/clang-doc/HTMLGenerator.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index 5b023a409c364f..0ced83f91724ff 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -713,7 +713,7 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
   TRow->Children.emplace_back(std::move(TD));
   THead->Children.emplace_back(std::move(TRow));
   Table->Children.emplace_back(std::move(THead));
-  
+
   if (std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members))
     Table->Children.emplace_back(std::move(Node));
 

>From c60e2b505e27cebffac27b0085fee12d68bbedf7 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 23 Aug 2024 17:39:16 -0400
Subject: [PATCH 10/21] [llvm] implement support for mustache template language

---
 llvm/include/llvm/Support/Mustache.h    | 109 ++++++++++
 llvm/lib/Support/CMakeLists.txt         |   1 +
 llvm/lib/Support/Mustache.cpp           | 276 ++++++++++++++++++++++++
 llvm/unittests/Support/CMakeLists.txt   |   1 +
 llvm/unittests/Support/MustacheTest.cpp | 135 ++++++++++++
 5 files changed, 522 insertions(+)
 create mode 100644 llvm/include/llvm/Support/Mustache.h
 create mode 100644 llvm/lib/Support/Mustache.cpp
 create mode 100644 llvm/unittests/Support/MustacheTest.cpp

diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
new file mode 100644
index 00000000000000..a1ce9d945a37c5
--- /dev/null
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -0,0 +1,109 @@
+//===--- Mustache.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implementation of the Mustache templating language supports version 1.4.2
+// (https://mustache.github.io/mustache.5.html).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_MUSTACHE
+#define LLVM_SUPPORT_MUSTACHE
+
+#include "Error.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/JSON.h"
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace llvm {
+namespace mustache {
+
+using Accessor = std::vector<std::string>;
+
+class Token {
+public:
+  enum class Type {
+    Text,
+    Variable,
+    Partial,
+    SectionOpen,
+    SectionClose,
+    InvertSectionOpen,
+    UnescapeVariable,
+    Comment,
+  };
+
+  Token(std::string Str);
+
+  Token(std::string Str, char Identifier);
+
+  std::string getTokenBody() const { return TokenBody; };
+
+  Accessor getAccessor() const { return Accessor; };
+
+  Type getType() const { return TokenType; };
+
+private:
+  Type TokenType;
+  Accessor Accessor;
+  std::string TokenBody;
+};
+
+class ASTNode {
+public:
+  enum Type {
+    Root,
+    Text,
+    Partial,
+    Variable,
+    UnescapeVariable,
+    Section,
+    InvertSection,
+  };
+
+  ASTNode() : T(Type::Root), LocalContext(nullptr){};
+
+  ASTNode(std::string Body, std::shared_ptr<ASTNode> Parent)
+      : T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr){};
+
+  // Constructor for Section/InvertSection/Variable/UnescapeVariable
+  ASTNode(Type T, Accessor Accessor, std::shared_ptr<ASTNode> Parent)
+      : T(T), Accessor(Accessor), Parent(Parent), LocalContext(nullptr),
+        Children({}){};
+
+  void addChild(std::shared_ptr<ASTNode> Child) {
+    Children.emplace_back(Child);
+  };
+
+  std::string render(llvm::json::Value Data);
+
+  llvm::json::Value findContext();
+
+  Type T;
+  std::string Body;
+  std::weak_ptr<ASTNode> Parent;
+  std::vector<std::shared_ptr<ASTNode>> Children;
+  Accessor Accessor;
+  llvm::json::Value LocalContext;
+};
+
+class Template {
+public:
+  static Expected<Template> createTemplate(std::string TemplateStr);
+
+  std::string render(llvm::json::Value Data);
+
+private:
+  Template(std::shared_ptr<ASTNode> Tree) : Tree(Tree){};
+  std::shared_ptr<ASTNode> Tree;
+};
+
+} // namespace mustache
+} // end namespace llvm
+#endif // LLVM_SUPPORT_MUSTACHE
\ No newline at end of file
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index f653379e303349..91c8c3fd7de0e6 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -207,6 +207,7 @@ add_llvm_component_library(LLVMSupport
   MD5.cpp
   MSP430Attributes.cpp
   MSP430AttributeParser.cpp
+  Mustache.cpp      
   NativeFormatting.cpp
   OptimizedStructLayout.cpp
   Optional.cpp
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
new file mode 100644
index 00000000000000..19a0ccffe87acc
--- /dev/null
+++ b/llvm/lib/Support/Mustache.cpp
@@ -0,0 +1,276 @@
+//===-- Mustache.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/Mustache.h"
+#include "llvm/Support/Error.h"
+#include <iostream>
+#include <regex>
+#include <sstream>
+
+using namespace llvm;
+using namespace llvm::json;
+using namespace llvm::mustache;
+
+std::string escapeHtml(const std::string &Input) {
+  DenseMap<char, std::string> HtmlEntities = {{'&', "&"},
+                                              {'<', "<"},
+                                              {'>', ">"},
+                                              {'"', """},
+                                              {'"', "'"}};
+  std::string EscapedString;
+  EscapedString.reserve(Input.size());
+
+  for (char C : Input) {
+    if (HtmlEntities.find(C) != HtmlEntities.end()) {
+      EscapedString += HtmlEntities[C];
+    } else {
+      EscapedString += C;
+    }
+  }
+
+  return EscapedString;
+}
+
+std::vector<std::string> split(const std::string &Str, char Delimiter) {
+  std::vector<std::string> Tokens;
+  std::string Token;
+  std::stringstream SS(Str);
+  if (Str == ".") {
+    Tokens.push_back(Str);
+    return Tokens;
+  }
+  while (std::getline(SS, Token, Delimiter)) {
+    Tokens.push_back(Token);
+  }
+  return Tokens;
+}
+
+Token::Token(std::string Str, char Identifier) {
+  switch (Identifier) {
+  case '#':
+    TokenType = Type::SectionOpen;
+    break;
+  case '/':
+    TokenType = Type::SectionClose;
+    break;
+  case '^':
+    TokenType = Type::InvertSectionOpen;
+    break;
+  case '!':
+    TokenType = Type::Comment;
+    break;
+  case '>':
+    TokenType = Type::Partial;
+    break;
+  case '&':
+    TokenType = Type::UnescapeVariable;
+    break;
+  default:
+    TokenType = Type::Variable;
+  }
+  if (TokenType == Type::Comment)
+    return;
+
+  TokenBody = Str;
+  std::string AccessorStr = Str;
+  if (TokenType != Type::Variable) {
+    AccessorStr = Str.substr(1);
+  }
+  Accessor = split(StringRef(AccessorStr).trim().str(), '.');
+}
+
+Token::Token(std::string Str)
+    : TokenType(Type::Text), TokenBody(Str), Accessor({}) {}
+
+std::vector<Token> tokenize(std::string Template) {
+  std::vector<Token> Tokens;
+  std::regex Re(R"(\{\{(.*?)\}\})");
+  std::sregex_token_iterator Iter(Template.begin(), Template.end(), Re,
+                                  {-1, 0});
+  std::sregex_token_iterator End;
+
+  for (; Iter != End; ++Iter) {
+    if (!Iter->str().empty()) {
+      std::string Token = *Iter;
+      std::smatch Match;
+      if (std::regex_match(Token, Match, Re)) {
+        std::string Group = Match[1];
+        Tokens.emplace_back(Group, Group[0]);
+      } else {
+        Tokens.emplace_back(Token);
+      }
+    }
+  }
+
+  return Tokens;
+}
+
+class Parser {
+public:
+  Parser(std::string TemplateStr) : TemplateStr(TemplateStr) {}
+
+  std::shared_ptr<ASTNode> parse();
+
+private:
+  void parseMustache(std::shared_ptr<ASTNode> Parent);
+
+  std::vector<Token> Tokens;
+  std::size_t CurrentPtr;
+  std::string TemplateStr;
+};
+
+std::shared_ptr<ASTNode> Parser::parse() {
+  Tokens = tokenize(TemplateStr);
+  CurrentPtr = 0;
+  std::shared_ptr<ASTNode> Root = std::make_shared<ASTNode>();
+  parseMustache(Root);
+  return Root;
+}
+
+void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
+
+  while (CurrentPtr < Tokens.size()) {
+    Token CurrentToken = Tokens[CurrentPtr];
+    CurrentPtr++;
+    Accessor A = CurrentToken.getAccessor();
+    std::shared_ptr<ASTNode> CurrentNode;
+
+    switch (CurrentToken.getType()) {
+    case Token::Type::Text: {
+      CurrentNode =
+          std::make_shared<ASTNode>(CurrentToken.getTokenBody(), Parent);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::Variable: {
+      CurrentNode = std::make_shared<ASTNode>(ASTNode::Variable, A, Parent);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::UnescapeVariable: {
+      CurrentNode =
+          std::make_shared<ASTNode>(ASTNode::UnescapeVariable, A, Parent);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::Partial: {
+      CurrentNode = std::make_shared<ASTNode>(ASTNode::Partial, A, Parent);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::SectionOpen: {
+      CurrentNode = std::make_shared<ASTNode>(ASTNode::Section, A, Parent);
+      parseMustache(CurrentNode);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::InvertSectionOpen: {
+      CurrentNode =
+          std::make_shared<ASTNode>(ASTNode::InvertSection, A, Parent);
+      parseMustache(CurrentNode);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::SectionClose: {
+      return;
+    }
+    default:
+      break;
+    }
+  }
+}
+
+Expected<Template> Template::createTemplate(std::string TemplateStr) {
+  Parser P = Parser(TemplateStr);
+  Expected<std::shared_ptr<ASTNode>> MustacheTree = P.parse();
+  if (!MustacheTree)
+    return MustacheTree.takeError();
+  return Template(MustacheTree.get());
+}
+std::string Template::render(Value Data) { return Tree->render(Data); }
+
+std::string printJson(Value &Data) {
+  if (Data.getAsNull().has_value()) {
+    return "";
+  }
+  if (auto *Arr = Data.getAsArray()) {
+    if (Arr->empty()) {
+      return "";
+    }
+  }
+  if (Data.getAsString().has_value()) {
+    return Data.getAsString()->str();
+  }
+  return llvm::formatv("{0:2}", Data);
+}
+
+std::string ASTNode::render(Value Data) {
+  LocalContext = Data;
+  Value Context = T == Root ? Data : findContext();
+  switch (T) {
+  case Root: {
+    std::string Result = "";
+    for (std::shared_ptr<ASTNode> Child : Children) {
+      Result += Child->render(Context);
+    }
+    return Result;
+  }
+  case Text:
+    return escapeHtml(Body);
+  case Partial:
+    break;
+  case Variable:
+    return escapeHtml(printJson(Context));
+  case UnescapeVariable:
+    return printJson(Context);
+  case Section:
+    break;
+  case InvertSection:
+    break;
+  }
+
+  return std::string();
+}
+
+Value ASTNode::findContext() {
+  if (Accessor.empty()) {
+    return nullptr;
+  }
+  if (Accessor[0] == ".") {
+    return LocalContext;
+  }
+  json::Object *CurrentContext = LocalContext.getAsObject();
+  std::string &CurrentAccessor = Accessor[0];
+  std::weak_ptr<ASTNode> CurrentParent = Parent;
+
+  while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
+    if (auto Ptr = CurrentParent.lock()) {
+      CurrentContext = Ptr->LocalContext.getAsObject();
+      CurrentParent = Ptr->Parent;
+      continue;
+    }
+    return nullptr;
+  }
+  Value Context = nullptr;
+  for (std::size_t i = 0; i < Accessor.size(); i++) {
+    CurrentAccessor = Accessor[i];
+    Value *CurrentValue = CurrentContext->get(CurrentAccessor);
+    if (!CurrentValue) {
+      return nullptr;
+    }
+    if (i < Accessor.size() - 1) {
+      CurrentContext = CurrentValue->getAsObject();
+      if (!CurrentContext) {
+        return nullptr;
+      }
+    } else {
+      Context = *CurrentValue;
+    }
+  }
+  return Context;
+}
diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index 631f2e6bf00df0..63a6a13019bdcf 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -60,6 +60,7 @@ add_llvm_unittest(SupportTests
   MemoryBufferRefTest.cpp
   MemoryBufferTest.cpp
   MemoryTest.cpp
+  MustacheTest.cpp      
   NativeFormatTests.cpp
   OptimizedStructLayoutTest.cpp
   ParallelTest.cpp
diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
new file mode 100644
index 00000000000000..7479e29816e944
--- /dev/null
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -0,0 +1,135 @@
+//===- llvm/unittest/Support/MustacheTest.cpp ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Test conforming to Mustache 1.4.2 spec found here:
+// https://github.com/mustache/spec
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/Mustache.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::mustache;
+using namespace llvm::json;
+
+TEST(MustacheInterpolation, NoInterpolation) {
+  // Mustache-free templates should render as-is.
+  Value D = {};
+  auto T = Template::createTemplate("Hello from {Mustache}!\n");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("Hello from {Mustache}!\n", Out);
+}
+
+TEST(MustacheInterpolation, BasicInterpolation) {
+  // Unadorned tags should interpolate content into the template.
+  Value D = Object{{"subject", "World"}};
+  auto T = Template::createTemplate("Hello, {{subject}}!");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("Hello, World!", Out);
+}
+
+TEST(MustacheInterpolation, NoReinterpolation) {
+  // Interpolated tag output should not be re-interpolated.
+  Value D = Object{{"template", "{{planet}}"}, {"planet", "Earth"}};
+  auto T = Template::createTemplate("{{template}}: {{planet}}");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("{{planet}}: Earth", Out);
+}
+
+TEST(MustacheInterpolation, HTMLEscaping) {
+  // Interpolated tag output should not be re-interpolated.
+  Value D = Object{
+      {"forbidden", "& \" < >"},
+  };
+  auto T = Template::createTemplate(
+      "These characters should be HTML escaped: {{forbidden}}\n");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("These characters should be HTML escaped: & " < >\n",
+            Out);
+}
+
+TEST(MustacheInterpolation, Ampersand) {
+  // Interpolated tag output should not be re-interpolated.
+  Value D = Object{
+      {"forbidden", "& \" < >"},
+  };
+  auto T = Template::createTemplate(
+      "These characters should not be HTML escaped: {{&forbidden}}\n");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out);
+}
+
+TEST(MustacheInterpolation, BasicIntegerInterpolation) {
+  Value D = Object{{"mph", 85}};
+  auto T = Template::createTemplate("{{mph}} miles an hour!");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("85 miles an hour!", Out);
+}
+
+TEST(MustacheInterpolation, BasicDecimalInterpolation) {
+  Value D = Object{{"power", 1.21}};
+  auto T = Template::createTemplate("{{power}} jiggawatts!");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("1.21 jiggawatts!", Out);
+}
+
+TEST(MustacheInterpolation, BasicNullInterpolation) {
+  Value D = Object{{"cannot", nullptr}};
+  auto T = Template::createTemplate("I ({{cannot}}) be seen!");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("I () be seen!", Out);
+}
+
+TEST(MustacheInterpolation, BasicContextMissInterpolation) {
+  Value D = Object{};
+  auto T = Template::createTemplate("I ({{cannot}}) be seen!");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("I () be seen!", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesBasicInterpolation) {
+  Value D = Object{{"person", Object{{"name", "Joe"}}}};
+  auto T = Template::createTemplate(
+      "{{person.name}} == {{#person}}{{name}}{{/person}}");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("Joe == Joe", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
+  Value D = Object{
+      {"a",
+       Object{{"b",
+               Object{{"c",
+                       Object{{"d",
+                               Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}};
+  auto T = Template::createTemplate("{{a.b.c.d.e.name}} == Phil");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("Phil == Phil", Out);
+}
+
+TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) {
+  Value D = "world";
+  auto T = Template::createTemplate("Hello, {{.}}!\n");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("Hello, world!\n", Out);
+}
+
+TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) {
+  Value D = Object{{"string", "---"}};
+  auto T = Template::createTemplate("| {{string}} |");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("| --- |", Out);
+}
+
+TEST(MustacheInterpolation, InterpolationWithPadding) {
+  Value D = Object{{"string", "---"}};
+  auto T = Template::createTemplate("|{{ string }}|");
+  auto Out = T.get().render(D);
+  EXPECT_EQ("|---|", Out);
+}

>From 8290c381a099864db4c827e6102c015f09cfed5c Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 02:35:33 -0400
Subject: [PATCH 11/21] [llvm][Support] Finish implementation of Mustache

---
 llvm/include/llvm/Support/Mustache.h    |  63 +-
 llvm/lib/Support/Mustache.cpp           | 383 ++++++++---
 llvm/unittests/Support/MustacheTest.cpp | 877 +++++++++++++++++++++++-
 3 files changed, 1204 insertions(+), 119 deletions(-)

diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index a1ce9d945a37c5..cb7d80e47809e8 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -24,7 +24,9 @@
 namespace llvm {
 namespace mustache {
 
-using Accessor = std::vector<std::string>;
+using Accessor = std::vector<SmallString<128>>;
+using Lambda = std::function<llvm::json::Value()>;
+using SectionLambda = std::function<llvm::json::Value(StringRef)>;
 
 class Token {
 public:
@@ -39,20 +41,27 @@ class Token {
     Comment,
   };
 
-  Token(std::string Str);
+  Token(StringRef Str);
 
-  Token(std::string Str, char Identifier);
+  Token(StringRef RawBody, StringRef Str, char Identifier);
 
-  std::string getTokenBody() const { return TokenBody; };
+  StringRef getTokenBody() const { return TokenBody; };
+
+  StringRef getRawBody() const { return RawBody; };
+
+  void setTokenBody(SmallString<128> NewBody) { TokenBody = NewBody; };
 
   Accessor getAccessor() const { return Accessor; };
 
   Type getType() const { return TokenType; };
 
+  static Type getTokenType(char Identifier);
+
 private:
   Type TokenType;
+  SmallString<128> RawBody;
   Accessor Accessor;
-  std::string TokenBody;
+  SmallString<128> TokenBody;
 };
 
 class ASTNode {
@@ -69,7 +78,7 @@ class ASTNode {
 
   ASTNode() : T(Type::Root), LocalContext(nullptr){};
 
-  ASTNode(std::string Body, std::shared_ptr<ASTNode> Parent)
+  ASTNode(StringRef Body, std::shared_ptr<ASTNode> Parent)
       : T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr){};
 
   // Constructor for Section/InvertSection/Variable/UnescapeVariable
@@ -81,26 +90,56 @@ class ASTNode {
     Children.emplace_back(Child);
   };
 
-  std::string render(llvm::json::Value Data);
+  SmallString<128> getBody() const { return Body; };
 
-  llvm::json::Value findContext();
+  void setBody(StringRef NewBody) { Body = NewBody; };
+
+  void setRawBody(StringRef NewBody) { RawBody = NewBody; };
+
+  SmallString<128> getRawBody() const { return RawBody; };
+
+  std::shared_ptr<ASTNode> getLastChild() const {
+    return Children.empty() ? nullptr : Children.back();
+  };
 
+  SmallString<128>
+  render(llvm::json::Value Data,
+         DenseMap<StringRef, std::shared_ptr<ASTNode>> &Partials,
+         DenseMap<StringRef, Lambda> &Lambdas,
+         DenseMap<StringRef, SectionLambda> &SectionLambdas,
+         DenseMap<char, StringRef> &Escapes);
+
+private:
+  llvm::json::Value findContext();
   Type T;
-  std::string Body;
+  SmallString<128> RawBody;
+  SmallString<128> Body;
   std::weak_ptr<ASTNode> Parent;
   std::vector<std::shared_ptr<ASTNode>> Children;
-  Accessor Accessor;
+  const Accessor Accessor;
   llvm::json::Value LocalContext;
 };
 
 class Template {
 public:
-  static Expected<Template> createTemplate(std::string TemplateStr);
+  static Template createTemplate(StringRef TemplateStr);
+
+  SmallString<128> render(llvm::json::Value Data);
+
+  void registerPartial(StringRef Name, StringRef Partial);
+
+  void registerLambda(StringRef Name, Lambda Lambda);
+
+  void registerLambda(StringRef Name, SectionLambda Lambda);
 
-  std::string render(llvm::json::Value Data);
+  void registerEscape(DenseMap<char, StringRef> Escapes);
 
 private:
   Template(std::shared_ptr<ASTNode> Tree) : Tree(Tree){};
+  DenseMap<StringRef, std::shared_ptr<ASTNode>> Partials;
+  DenseMap<StringRef, Lambda> Lambdas;
+  DenseMap<StringRef, SectionLambda> SectionLambdas;
+  DenseMap<char, StringRef> Escapes;
   std::shared_ptr<ASTNode> Tree;
 };
 
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 19a0ccffe87acc..80b3d8bc53651b 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -16,103 +16,172 @@ using namespace llvm;
 using namespace llvm::json;
 using namespace llvm::mustache;
 
-std::string escapeHtml(const std::string &Input) {
-  DenseMap<char, std::string> HtmlEntities = {{'&', "&"},
-                                              {'<', "<"},
-                                              {'>', ">"},
-                                              {'"', """},
-                                              {'"', "'"}};
-  std::string EscapedString;
-  EscapedString.reserve(Input.size());
-
+SmallString<128> escapeString(StringRef Input,
+                              DenseMap<char, StringRef> &Escape) {
+  SmallString<128> EscapedString("");
   for (char C : Input) {
-    if (HtmlEntities.find(C) != HtmlEntities.end()) {
-      EscapedString += HtmlEntities[C];
+    if (Escape.find(C) != Escape.end()) {
+      EscapedString += Escape[C];
     } else {
       EscapedString += C;
     }
   }
-
   return EscapedString;
 }
 
-std::vector<std::string> split(const std::string &Str, char Delimiter) {
-  std::vector<std::string> Tokens;
-  std::string Token;
-  std::stringstream SS(Str);
+std::vector<SmallString<128>> split(StringRef Str, char Delimiter) {
+  std::vector<SmallString<128>> Tokens;
   if (Str == ".") {
     Tokens.push_back(Str);
     return Tokens;
   }
-  while (std::getline(SS, Token, Delimiter)) {
-    Tokens.push_back(Token);
+  StringRef Ref(Str);
+  while (!Ref.empty()) {
+    llvm::StringRef Part;
+    std::tie(Part, Ref) = Ref.split(Delimiter);
+    Tokens.push_back(Part.trim());
   }
   return Tokens;
 }
 
-Token::Token(std::string Str, char Identifier) {
+Token::Token(StringRef RawBody, StringRef InnerBody, char Identifier)
+    : RawBody(RawBody), TokenBody(InnerBody) {
+
+  TokenType = getTokenType(Identifier);
+  if (TokenType == Type::Comment)
+    return;
+
+  StringRef AccessorStr = InnerBody;
+  if (TokenType != Type::Variable) {
+    AccessorStr = InnerBody.substr(1);
+  }
+  Accessor = split(AccessorStr.trim(), '.');
+}
+
+Token::Token(StringRef Str)
+    : RawBody(Str), TokenType(Type::Text), TokenBody(Str), Accessor({}) {}
+
+Token::Type Token::getTokenType(char Identifier) {
   switch (Identifier) {
   case '#':
-    TokenType = Type::SectionOpen;
-    break;
+    return Type::SectionOpen;
   case '/':
-    TokenType = Type::SectionClose;
-    break;
+    return Type::SectionClose;
   case '^':
-    TokenType = Type::InvertSectionOpen;
-    break;
+    return Type::InvertSectionOpen;
   case '!':
-    TokenType = Type::Comment;
-    break;
+    return Type::Comment;
   case '>':
-    TokenType = Type::Partial;
-    break;
+    return Type::Partial;
   case '&':
-    TokenType = Type::UnescapeVariable;
-    break;
+    return Type::UnescapeVariable;
   default:
-    TokenType = Type::Variable;
+    return Type::Variable;
   }
-  if (TokenType == Type::Comment)
-    return;
+}
 
-  TokenBody = Str;
-  std::string AccessorStr = Str;
-  if (TokenType != Type::Variable) {
-    AccessorStr = Str.substr(1);
+std::vector<Token> tokenize(StringRef Template) {
+  // Simple tokenizer that splits the template into tokens
+  // the mustache spec allows {{{ }}} to unescape variables
+  // but we don't support that here unescape variable
+  // is represented only by {{& variable}}
+  std::vector<Token> Tokens;
+  SmallString<128> Open("{{");
+  SmallString<128> Close("}}");
+  std::size_t Start = 0;
+  std::size_t DelimiterStart = Template.find(Open);
+  if (DelimiterStart == StringRef::npos) {
+    Tokens.push_back(Token(Template));
+    return Tokens;
   }
-  Accessor = split(StringRef(AccessorStr).trim().str(), '.');
-}
+  while (DelimiterStart != StringRef::npos) {
+    if (DelimiterStart != Start) {
+      Token TextToken = Token(Template.substr(Start, DelimiterStart - Start));
+      Tokens.push_back(TextToken);
+    }
+
+    std::size_t DelimiterEnd = Template.find(Close, DelimiterStart);
+    if (DelimiterEnd == StringRef::npos) {
+      break;
+    }
 
-Token::Token(std::string Str)
-    : TokenType(Type::Text), TokenBody(Str), Accessor({}) {}
+    SmallString<128> Interpolated =
+        Template.substr(DelimiterStart + Open.size(),
+                        DelimiterEnd - DelimiterStart - Close.size());
+    SmallString<128> RawBody;
+    RawBody += Open;
+    RawBody += Interpolated;
+    RawBody += Close;
 
-std::vector<Token> tokenize(std::string Template) {
-  std::vector<Token> Tokens;
-  std::regex Re(R"(\{\{(.*?)\}\})");
-  std::sregex_token_iterator Iter(Template.begin(), Template.end(), Re,
-                                  {-1, 0});
-  std::sregex_token_iterator End;
-
-  for (; Iter != End; ++Iter) {
-    if (!Iter->str().empty()) {
-      std::string Token = *Iter;
-      std::smatch Match;
-      if (std::regex_match(Token, Match, Re)) {
-        std::string Group = Match[1];
-        Tokens.emplace_back(Group, Group[0]);
-      } else {
-        Tokens.emplace_back(Token);
+    Tokens.push_back(Token(RawBody, Interpolated, Interpolated[0]));
+    Start = DelimiterEnd + Close.size();
+    DelimiterStart = Template.find(Open, Start);
+  }
+
+  if (Start < Template.size()) {
+    Tokens.push_back(Token(Template.substr(Start)));
+  }
+
+  // fix up white spaces for
+  // open sections/inverted sections/close section/comment
+  for (std::size_t I = 0; I < Tokens.size(); I++) {
+    Token::Type CurrentType = Tokens[I].getType();
+    bool RequiresCleanUp = CurrentType == Token::Type::SectionOpen ||
+                           CurrentType == Token::Type::InvertSectionOpen ||
+                           CurrentType == Token::Type::SectionClose ||
+                           CurrentType == Token::Type::Comment ||
+                           CurrentType == Token::Type::Partial;
+
+    bool NoTextBehind = false;
+    bool NoTextAhead = false;
+    if (I > 0 && Tokens[I - 1].getType() == Token::Type::Text &&
+        RequiresCleanUp) {
+      Token &PrevToken = Tokens[I - 1];
+      StringRef TokenBody = PrevToken.getTokenBody().rtrim(" \t\v\t");
+      if (TokenBody.ends_with("\n") || TokenBody.ends_with("\r\n") ||
+          TokenBody.empty()) {
+        NoTextBehind = true;
+      }
+    }
+    if (I < Tokens.size() - 1 && Tokens[I + 1].getType() == Token::Type::Text &&
+        RequiresCleanUp) {
+      Token &NextToken = Tokens[I + 1];
+      StringRef TokenBody = NextToken.getTokenBody().ltrim(" ");
+      if (TokenBody.starts_with("\r\n") || TokenBody.starts_with("\n")) {
+        NoTextAhead = true;
       }
     }
-  }
 
+    if (NoTextBehind && NoTextAhead) {
+      Token &PrevToken = Tokens[I - 1];
+      Token &NextToken = Tokens[I + 1];
+      StringRef NextTokenBody = NextToken.getTokenBody();
+      PrevToken.setTokenBody(PrevToken.getTokenBody().rtrim(" \t\v\t"));
+      if (NextTokenBody.starts_with("\r\n")) {
+        NextToken.setTokenBody(NextTokenBody.substr(2));
+      } else if (NextToken.getTokenBody().starts_with("\n")) {
+        NextToken.setTokenBody(NextTokenBody.substr(1));
+      }
+    } else if (NoTextAhead && I == 0) {
+      Token &NextToken = Tokens[I + 1];
+      StringRef NextTokenBody = NextToken.getTokenBody();
+      if (NextTokenBody.starts_with("\r\n")) {
+        NextToken.setTokenBody(NextTokenBody.substr(2));
+      } else if (NextToken.getTokenBody().starts_with("\n")) {
+        NextToken.setTokenBody(NextTokenBody.substr(1));
+      }
+    } else if (NoTextBehind && I == Tokens.size() - 1) {
+      Token &PrevToken = Tokens[I - 1];
+      StringRef PrevTokenBody = PrevToken.getTokenBody();
+      PrevToken.setTokenBody(PrevTokenBody.rtrim(" \t\v\t"));
+    }
+  }
   return Tokens;
 }
 
 class Parser {
 public:
-  Parser(std::string TemplateStr) : TemplateStr(TemplateStr) {}
+  Parser(StringRef TemplateStr) : TemplateStr(TemplateStr) {}
 
   std::shared_ptr<ASTNode> parse();
 
@@ -121,7 +190,7 @@ class Parser {
 
   std::vector<Token> Tokens;
   std::size_t CurrentPtr;
-  std::string TemplateStr;
+  StringRef TemplateStr;
 };
 
 std::shared_ptr<ASTNode> Parser::parse() {
@@ -165,14 +234,34 @@ void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
     }
     case Token::Type::SectionOpen: {
       CurrentNode = std::make_shared<ASTNode>(ASTNode::Section, A, Parent);
+      std::size_t Start = CurrentPtr;
       parseMustache(CurrentNode);
+      std::size_t End = CurrentPtr;
+      SmallString<128> RawBody;
+      if (Start + 1 < End - 1)
+        for (std::size_t I = Start + 1; I < End - 1; I++) {
+          RawBody += Tokens[I].getRawBody();
+        }
+      else if (Start + 1 == End - 1)
+        RawBody = Tokens[Start].getRawBody();
+      CurrentNode->setRawBody(RawBody);
       Parent->addChild(CurrentNode);
       break;
     }
     case Token::Type::InvertSectionOpen: {
       CurrentNode =
           std::make_shared<ASTNode>(ASTNode::InvertSection, A, Parent);
+      std::size_t Start = CurrentPtr;
       parseMustache(CurrentNode);
+      std::size_t End = CurrentPtr;
+      SmallString<128> RawBody;
+      if (Start + 1 < End - 1)
+        for (std::size_t I = Start + 1; I < End - 1; I++) {
+          RawBody += Tokens[I].getRawBody();
+        }
+      else if (Start + 1 == End - 1)
+        RawBody = Tokens[Start].getRawBody();
+      CurrentNode->setRawBody(RawBody);
       Parent->addChild(CurrentNode);
       break;
     }
@@ -185,59 +274,167 @@ void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
   }
 }
 
-Expected<Template> Template::createTemplate(std::string TemplateStr) {
+Template Template::createTemplate(StringRef TemplateStr) {
   Parser P = Parser(TemplateStr);
-  Expected<std::shared_ptr<ASTNode>> MustacheTree = P.parse();
-  if (!MustacheTree)
-    return MustacheTree.takeError();
-  return Template(MustacheTree.get());
+  std::shared_ptr<ASTNode> MustacheTree = P.parse();
+  Template T = Template(MustacheTree);
+  // the default behaviour is to escape html entities
+  DenseMap<char, StringRef> HtmlEntities = {{'&', "&"},
+                                            {'<', "<"},
+                                            {'>', ">"},
+                                            {'"', """},
+                                            {'\'', "'"}};
+  T.registerEscape(HtmlEntities);
+  return T;
+}
+
+SmallString<128> Template::render(Value Data) {
+  return Tree->render(Data, Partials, Lambdas, SectionLambdas, Escapes);
+}
+
+void Template::registerPartial(StringRef Name, StringRef Partial) {
+  Parser P = Parser(Partial);
+  std::shared_ptr<ASTNode> PartialTree = P.parse();
+  Partials[Name] = PartialTree;
+}
+
+void Template::registerLambda(StringRef Name, Lambda L) { Lambdas[Name] = L; }
+
+void Template::registerLambda(StringRef Name, SectionLambda L) {
+  SectionLambdas[Name] = L;
 }
-std::string Template::render(Value Data) { return Tree->render(Data); }
 
-std::string printJson(Value &Data) {
-  if (Data.getAsNull().has_value()) {
-    return "";
+void Template::registerEscape(DenseMap<char, StringRef> E) { Escapes = E; }
+
+SmallString<128> printJson(Value &Data) {
+
+  SmallString<128> Result;
+  if (Data.getAsNull()) {
+    return Result;
   }
   if (auto *Arr = Data.getAsArray()) {
     if (Arr->empty()) {
-      return "";
+      return Result;
     }
   }
-  if (Data.getAsString().has_value()) {
-    return Data.getAsString()->str();
+  if (Data.getAsString()) {
+    Result += Data.getAsString()->str();
+    return Result;
   }
   return llvm::formatv("{0:2}", Data);
 }
 
-std::string ASTNode::render(Value Data) {
+bool isFalsey(Value &V) {
+  return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||
+         (V.getAsArray() && V.getAsArray()->empty()) ||
+         (V.getAsObject() && V.getAsObject()->empty());
+}
+
+SmallString<128>
+ASTNode::render(Value Data,
+                DenseMap<StringRef, std::shared_ptr<ASTNode>> &Partials,
+                DenseMap<StringRef, Lambda> &Lambdas,
+                DenseMap<StringRef, SectionLambda> &SectionLambdas,
+                DenseMap<char, StringRef> &Escapes) {
   LocalContext = Data;
   Value Context = T == Root ? Data : findContext();
+  SmallString<128> Result;
   switch (T) {
   case Root: {
-    std::string Result = "";
-    for (std::shared_ptr<ASTNode> Child : Children) {
-      Result += Child->render(Context);
-    }
+    for (std::shared_ptr<ASTNode> Child : Children)
+      Result +=
+          Child->render(Context, Partials, Lambdas, SectionLambdas, Escapes);
     return Result;
   }
   case Text:
-    return escapeHtml(Body);
-  case Partial:
-    break;
-  case Variable:
-    return escapeHtml(printJson(Context));
-  case UnescapeVariable:
+    return Body;
+  case Partial: {
+    if (Partials.find(Accessor[0]) != Partials.end()) {
+      std::shared_ptr<ASTNode> Partial = Partials[Accessor[0]];
+      Result +=
+          Partial->render(Data, Partials, Lambdas, SectionLambdas, Escapes);
+      return Result;
+    }
+  }
+  case Variable: {
+    if (Lambdas.find(Accessor[0]) != Lambdas.end()) {
+      Lambda &L = Lambdas[Accessor[0]];
+      Value LambdaResult = L();
+      StringRef LambdaStr = printJson(LambdaResult);
+      Parser P = Parser(LambdaStr);
+      std::shared_ptr<ASTNode> LambdaNode = P.parse();
+      return LambdaNode->render(Data, Partials, Lambdas, SectionLambdas,
+                                Escapes);
+    }
+    return escapeString(printJson(Context), Escapes);
+  }
+  case UnescapeVariable: {
+    if (Lambdas.find(Accessor[0]) != Lambdas.end()) {
+      Lambda &L = Lambdas[Accessor[0]];
+      Value LambdaResult = L();
+      StringRef LambdaStr = printJson(LambdaResult);
+      Parser P = Parser(LambdaStr);
+      std::shared_ptr<ASTNode> LambdaNode = P.parse();
+      DenseMap<char, StringRef> EmptyEscapes;
+      return LambdaNode->render(Data, Partials, Lambdas, SectionLambdas,
+                                EmptyEscapes);
+    }
     return printJson(Context);
-  case Section:
-    break;
-  case InvertSection:
-    break;
   }
+  case Section: {
+    // Sections are not rendered if the context is falsey
+    bool IsLambda = SectionLambdas.find(Accessor[0]) != SectionLambdas.end();
+
+    if (isFalsey(Context) && !IsLambda)
+      return Result;
+
+    if (IsLambda) {
+      SectionLambda &Lambda = SectionLambdas[Accessor[0]];
+      Value Return = Lambda(RawBody);
+      if (isFalsey(Return))
+        return Result;
+      StringRef LambdaStr = printJson(Return);
+      Parser P = Parser(LambdaStr);
+      std::shared_ptr<ASTNode> LambdaNode = P.parse();
+      return LambdaNode->render(Data, Partials, Lambdas, SectionLambdas,
+                                Escapes);
+    }
+
+    if (Context.getAsArray()) {
+      json::Array *Arr = Context.getAsArray();
+      for (Value &V : *Arr) {
+        for (std::shared_ptr<ASTNode> Child : Children)
+          Result +=
+              Child->render(V, Partials, Lambdas, SectionLambdas, Escapes);
+      }
+      return Result;
+    }
 
-  return std::string();
+    for (std::shared_ptr<ASTNode> Child : Children)
+      Result +=
+          Child->render(Context, Partials, Lambdas, SectionLambdas, Escapes);
+
+    return Result;
+  }
+  case InvertSection: {
+    bool IsLambda = SectionLambdas.find(Accessor[0]) != SectionLambdas.end();
+    if (!isFalsey(Context) || IsLambda)
+      return Result;
+    for (std::shared_ptr<ASTNode> Child : Children)
+      Result +=
+          Child->render(Context, Partials, Lambdas, SectionLambdas, Escapes);
+    return Result;
+  }
+  }
+  llvm_unreachable("Invalid ASTNode type");
 }
 
 Value ASTNode::findContext() {
+  // The mustache spec allows for dot notation to access nested values
+  // a single dot refers to the current context
+  // We attempt to find the JSON context in the current node if it is not found
+  // we traverse the parent nodes to find the context until we reach the root
+  // node or the context is found
   if (Accessor.empty()) {
     return nullptr;
   }
@@ -245,7 +442,7 @@ Value ASTNode::findContext() {
     return LocalContext;
   }
   json::Object *CurrentContext = LocalContext.getAsObject();
-  std::string &CurrentAccessor = Accessor[0];
+  SmallString<128> CurrentAccessor = Accessor[0];
   std::weak_ptr<ASTNode> CurrentParent = Parent;
 
   while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
@@ -257,13 +454,13 @@ Value ASTNode::findContext() {
     return nullptr;
   }
   Value Context = nullptr;
-  for (std::size_t i = 0; i < Accessor.size(); i++) {
-    CurrentAccessor = Accessor[i];
+  for (std::size_t I = 0; I < Accessor.size(); I++) {
+    CurrentAccessor = Accessor[I];
     Value *CurrentValue = CurrentContext->get(CurrentAccessor);
     if (!CurrentValue) {
       return nullptr;
     }
-    if (i < Accessor.size() - 1) {
+    if (I < Accessor.size() - 1) {
       CurrentContext = CurrentValue->getAsObject();
       if (!CurrentContext) {
         return nullptr;
diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
index 7479e29816e944..ceee8942f88f4e 100644
--- a/llvm/unittests/Support/MustacheTest.cpp
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -22,7 +22,7 @@ TEST(MustacheInterpolation, NoInterpolation) {
   // Mustache-free templates should render as-is.
   Value D = {};
   auto T = Template::createTemplate("Hello from {Mustache}!\n");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("Hello from {Mustache}!\n", Out);
 }
 
@@ -30,7 +30,7 @@ TEST(MustacheInterpolation, BasicInterpolation) {
   // Unadorned tags should interpolate content into the template.
   Value D = Object{{"subject", "World"}};
   auto T = Template::createTemplate("Hello, {{subject}}!");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("Hello, World!", Out);
 }
 
@@ -38,7 +38,7 @@ TEST(MustacheInterpolation, NoReinterpolation) {
   // Interpolated tag output should not be re-interpolated.
   Value D = Object{{"template", "{{planet}}"}, {"planet", "Earth"}};
   auto T = Template::createTemplate("{{template}}: {{planet}}");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("{{planet}}: Earth", Out);
 }
 
@@ -49,7 +49,7 @@ TEST(MustacheInterpolation, HTMLEscaping) {
   };
   auto T = Template::createTemplate(
       "These characters should be HTML escaped: {{forbidden}}\n");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("These characters should be HTML escaped: & " < >\n",
             Out);
 }
@@ -61,47 +61,78 @@ TEST(MustacheInterpolation, Ampersand) {
   };
   auto T = Template::createTemplate(
       "These characters should not be HTML escaped: {{&forbidden}}\n");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out);
 }
 
 TEST(MustacheInterpolation, BasicIntegerInterpolation) {
+  // Integers should interpolate seamlessly.
   Value D = Object{{"mph", 85}};
   auto T = Template::createTemplate("{{mph}} miles an hour!");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
+  EXPECT_EQ("85 miles an hour!", Out);
+}
+
+TEST(MustacheInterpolation, AmpersandIntegerInterpolation) {
+  // Integers should interpolate seamlessly.
+  Value D = Object{{"mph", 85}};
+  auto T = Template::createTemplate("{{&mph}} miles an hour!");
+  auto Out = T.render(D);
   EXPECT_EQ("85 miles an hour!", Out);
 }
 
 TEST(MustacheInterpolation, BasicDecimalInterpolation) {
+  // Decimals should interpolate seamlessly with proper significance.
   Value D = Object{{"power", 1.21}};
   auto T = Template::createTemplate("{{power}} jiggawatts!");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("1.21 jiggawatts!", Out);
 }
 
 TEST(MustacheInterpolation, BasicNullInterpolation) {
+  // Nulls should interpolate as the empty string.
   Value D = Object{{"cannot", nullptr}};
   auto T = Template::createTemplate("I ({{cannot}}) be seen!");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
+  EXPECT_EQ("I () be seen!", Out);
+}
+
+TEST(MustacheInterpolation, AmpersandNullInterpolation) {
+  // Nulls should interpolate as the empty string.
+  Value D = Object{{"cannot", nullptr}};
+  auto T = Template::createTemplate("I ({{&cannot}}) be seen!");
+  auto Out = T.render(D);
   EXPECT_EQ("I () be seen!", Out);
 }
 
 TEST(MustacheInterpolation, BasicContextMissInterpolation) {
+  // Failed context lookups should default to empty strings.
   Value D = Object{};
   auto T = Template::createTemplate("I ({{cannot}}) be seen!");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("I () be seen!", Out);
 }
 
 TEST(MustacheInterpolation, DottedNamesBasicInterpolation) {
+  // Dotted names should be considered a form of shorthand for sections.
   Value D = Object{{"person", Object{{"name", "Joe"}}}};
   auto T = Template::createTemplate(
       "{{person.name}} == {{#person}}{{name}}{{/person}}");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
+  EXPECT_EQ("Joe == Joe", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesAmpersandInterpolation) {
+  // Dotted names should be considered a form of shorthand for sections.
+  Value D = Object{{"person", Object{{"name", "Joe"}}}};
+  auto T = Template::createTemplate(
+      "{{&person.name}} == {{#person}}{{&name}}{{/person}}");
+  auto Out = T.render(D);
   EXPECT_EQ("Joe == Joe", Out);
 }
 
 TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
+  // Dotted names should be functional to any level of nesting.
   Value D = Object{
       {"a",
        Object{{"b",
@@ -109,27 +140,845 @@ TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
                        Object{{"d",
                                Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}};
   auto T = Template::createTemplate("{{a.b.c.d.e.name}} == Phil");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("Phil == Phil", Out);
 }
 
+TEST(MustacheInterpolation, DottedNamesBrokenChains) {
+  // Any falsey value prior to the last part of the name should yield ''.
+  Value D = Object{{"a", Object{}}};
+  auto T = Template::createTemplate("{{a.b.c}} == ");
+  auto Out = T.render(D);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesBrokenChainResolution) {
+  // Each part of a dotted name should resolve only against its parent.
+  Value D =
+      Object{{"a", Object{{"b", Object{}}}}, {"c", Object{{"name", "Jim"}}}};
+  auto T = Template::createTemplate("{{a.b.c.name}} == ");
+  auto Out = T.render(D);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesInitialResolution) {
+  // The first part of a dotted name should resolve as any other name.
+  Value D = Object{
+      {"a",
+       Object{
+           {"b",
+            Object{{"c",
+                    Object{{"d", Object{{"e", Object{{"name", "Phil"}}}}}}}}}}},
+      {"b",
+       Object{{"c", Object{{"d", Object{{"e", Object{{"name", "Wrong"}}}}}}}}}};
+  auto T = Template::createTemplate("{{#a}}{{b.c.d.e.name}}{{/a}} == Phil");
+  auto Out = T.render(D);
+  EXPECT_EQ("Phil == Phil", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesContextPrecedence) {
+  // Dotted names should be resolved against former resolutions.
+  Value D =
+      Object{{"a", Object{{"b", Object{}}}}, {"b", Object{{"c", "ERROR"}}}};
+  auto T = Template::createTemplate("{{#a}}{{b.c}}{{/a}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesAreNotSingleKeys) {
+  // Dotted names shall not be parsed as single, atomic keys
+  Value D = Object{{"a.b", "c"}};
+  auto T = Template::createTemplate("{{a.b}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesNoMasking) {
+  // Dotted Names in a given context are unavailable due to dot splitting
+  Value D = Object{{"a.b", "c"}, {"a", Object{{"b", "d"}}}};
+  auto T = Template::createTemplate("{{a.b}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("d", Out);
+}
+
 TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) {
+  // Unadorned tags should interpolate content into the template.
   Value D = "world";
   auto T = Template::createTemplate("Hello, {{.}}!\n");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("Hello, world!\n", Out);
 }
 
+TEST(MustacheInterpolation, ImplicitIteratorsAmersand) {
+  // Basic interpolation should be HTML escaped.
+  Value D = "& \" < >";
+  auto T = Template::createTemplate(
+      "These characters should not be HTML escaped: {{&.}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out);
+}
+
+TEST(MustacheInterpolation, ImplicitIteratorsInteger) {
+  // Integers should interpolate seamlessly.
+  Value D = 85;
+  auto T = Template::createTemplate("{{.}} miles an hour!\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("85 miles an hour!\n", Out);
+}
+
 TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) {
+  // Interpolation should not alter surrounding whitespace.
   Value D = Object{{"string", "---"}};
   auto T = Template::createTemplate("| {{string}} |");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
   EXPECT_EQ("| --- |", Out);
 }
 
+TEST(MustacheInterpolation, AmersandSurroundingWhitespace) {
+  // Interpolation should not alter surrounding whitespace.
+  Value D = Object{{"string", "---"}};
+  auto T = Template::createTemplate("| {{&string}} |");
+  auto Out = T.render(D);
+  EXPECT_EQ("| --- |", Out);
+}
+
+TEST(MustacheInterpolation, StandaloneInterpolationWithWhitespace) {
+  // Standalone interpolation should not alter surrounding whitespace.
+  Value D = Object{{"string", "---"}};
+  auto T = Template::createTemplate("  {{string}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("  ---\n", Out);
+}
+
+TEST(MustacheInterpolation, StandaloneAmpersandWithWhitespace) {
+  // Standalone interpolation should not alter surrounding whitespace.
+  Value D = Object{{"string", "---"}};
+  auto T = Template::createTemplate("  {{&string}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("  ---\n", Out);
+}
+
 TEST(MustacheInterpolation, InterpolationWithPadding) {
+  // Superfluous in-tag whitespace should be ignored.
   Value D = Object{{"string", "---"}};
   auto T = Template::createTemplate("|{{ string }}|");
-  auto Out = T.get().render(D);
+  auto Out = T.render(D);
+  EXPECT_EQ("|---|", Out);
+}
+
+TEST(MustacheInterpolation, AmpersandWithPadding) {
+  // Superfluous in-tag whitespace should be ignored.
+  Value D = Object{{"string", "---"}};
+  auto T = Template::createTemplate("|{{& string }}|");
+  auto Out = T.render(D);
+  EXPECT_EQ("|---|", Out);
+}
+
+TEST(MustacheInterpolation, InterpolationWithPaddingAndNewlines) {
+  // Superfluous in-tag whitespace should be ignored.
+  Value D = Object{{"string", "---"}};
+  auto T = Template::createTemplate("|{{ string \n\n\n }}|");
+  auto Out = T.render(D);
   EXPECT_EQ("|---|", Out);
 }
+
+TEST(MustacheSections, Truthy) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate(
+      "{{#boolean}}This should be rendered.{{/boolean}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("This should be rendered.", Out);
+}
+
+TEST(MustacheSections, Falsey) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate(
+      "{{#boolean}}This should not be rendered.{{/boolean}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheSections, NullIsFalsey) {
+  Value D = Object{{"null", nullptr}};
+  auto T = Template::createTemplate(
+      "{{#null}}This should not be rendered.{{/null}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheSections, Context) {
+  Value D = Object{{"context", Object{{"name", "Joe"}}}};
+  auto T = Template::createTemplate("{{#context}}Hi {{name}}.{{/context}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("Hi Joe.", Out);
+}
+
+TEST(MustacheSections, ParentContexts) {
+  Value D = Object{{"a", "foo"},
+                   {"b", "wrong"},
+                   {"sec", Object{{"b", "bar"}}},
+                   {"c", Object{{"d", "baz"}}}};
+  auto T = Template::createTemplate("{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("foo, bar, baz", Out);
+}
+
+TEST(MustacheSections, VariableTest) {
+  Value D = Object{{"foo", "bar"}};
+  auto T = Template::createTemplate("{{#foo}}{{.}} is {{foo}}{{/foo}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("bar is bar", Out);
+}
+
+TEST(MustacheSections, ListContexts) {
+  Value D = Object{
+      {"tops",
+       Array{Object{
+           {"tname", Object{{"upper", "A"}, {"lower", "a"}}},
+           {"middles",
+            Array{Object{{"mname", "1"},
+                         {"bottoms", Array{Object{{"bname", "x"}},
+                                           Object{{"bname", "y"}}}}}}}}}}};
+  auto T = Template::createTemplate("{{#tops}}"
+                                    "{{#middles}}"
+                                    "{{tname.lower}}{{mname}}."
+                                    "{{#bottoms}}"
+                                    "{{tname.upper}}{{mname}}{{bname}}."
+                                    "{{/bottoms}}"
+                                    "{{/middles}}"
+                                    "{{/tops}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("a1.A1x.A1y.", Out);
+}
+
+TEST(MustacheSections, DeeplyNestedContexts) {
+  Value D = Object{
+      {"a", Object{{"one", 1}}},
+      {"b", Object{{"two", 2}}},
+      {"c", Object{{"three", 3}, {"d", Object{{"four", 4}, {"five", 5}}}}}};
+  auto T = Template::createTemplate(
+      "{{#a}}\n{{one}}\n{{#b}}\n{{one}}{{two}}{{one}}\n{{#c}}\n{{one}}{{two}}{{"
+      "three}}{{two}}{{one}}\n{{#d}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{"
+      "{two}}{{one}}\n{{#five}}\n{{one}}{{two}}{{three}}{{four}}{{five}}{{four}"
+      "}{{three}}{{two}}{{one}}\n{{one}}{{two}}{{three}}{{four}}{{.}}6{{.}}{{"
+      "four}}{{three}}{{two}}{{one}}\n{{one}}{{two}}{{three}}{{four}}{{five}}{{"
+      "four}}{{three}}{{two}}{{one}}\n{{/"
+      "five}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}\n{{/"
+      "d}}\n{{one}}{{two}}{{three}}{{two}}{{one}}\n{{/"
+      "c}}\n{{one}}{{two}}{{one}}\n{{/b}}\n{{one}}\n{{/a}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("1\n121\n12321\n1234321\n123454321\n12345654321\n123454321\n1234321"
+            "\n12321\n121\n1\n",
+            Out);
+}
+
+TEST(MustacheSections, List) {
+  Value D = Object{{"list", Array{Object{{"item", 1}}, Object{{"item", 2}},
+                                  Object{{"item", 3}}}}};
+  auto T = Template::createTemplate("{{#list}}{{item}}{{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("123", Out);
+}
+
+TEST(MustacheSections, EmptyList) {
+  Value D = Object{{"list", Array{}}};
+  auto T = Template::createTemplate("{{#list}}Yay lists!{{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheSections, Doubled) {
+  Value D = Object{{"bool", true}, {"two", "second"}};
+  auto T = Template::createTemplate("{{#bool}}\n* first\n{{/bool}}\n* "
+                                    "{{two}}\n{{#bool}}\n* third\n{{/bool}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("* first\n* second\n* third\n", Out);
+}
+
+TEST(MustacheSections, NestedTruthy) {
+  Value D = Object{{"bool", true}};
+  auto T = Template::createTemplate(
+      "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |");
+  auto Out = T.render(D);
+  EXPECT_EQ("| A B C D E |", Out);
+}
+
+TEST(MustacheSections, NestedFalsey) {
+  Value D = Object{{"bool", false}};
+  auto T = Template::createTemplate(
+      "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |");
+  auto Out = T.render(D);
+  EXPECT_EQ("| A  E |", Out);
+}
+
+TEST(MustacheSections, ContextMisses) {
+  Value D = Object{};
+  auto T = Template::createTemplate(
+      "[{{#missing}}Found key 'missing'!{{/missing}}]");
+  auto Out = T.render(D);
+  EXPECT_EQ("[]", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorString) {
+  Value D = Object{{"list", Array{"a", "b", "c", "d", "e"}}};
+  auto T = Template::createTemplate("{{#list}}({{.}}){{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("(a)(b)(c)(d)(e)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorInteger) {
+  Value D = Object{{"list", Array{1, 2, 3, 4, 5}}};
+  auto T = Template::createTemplate("{{#list}}({{.}}){{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("(1)(2)(3)(4)(5)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorArray) {
+  Value D = Object{{"list", Array{Array{1, 2, 3}, Array{"a", "b", "c"}}}};
+  auto T = Template::createTemplate("{{#list}}({{#.}}{{.}}{{/.}}){{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("(123)(abc)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorHTMLEscaping) {
+  Value D = Object{{"list", Array{"&", "\"", "<", ">"}}};
+  auto T = Template::createTemplate("{{#list}}({{.}}){{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("(&)(")(<)(>)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorAmpersand) {
+  Value D = Object{{"list", Array{"&", "\"", "<", ">"}}};
+  auto T = Template::createTemplate("{{#list}}({{&.}}){{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("(&)(\")(<)(>)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorRootLevel) {
+  Value D = Array{Object{{"value", "a"}}, Object{{"value", "b"}}};
+  auto T = Template::createTemplate("{{#.}}({{value}}){{/.}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("(a)(b)", Out);
+}
+
+TEST(MustacheSections, DottedNamesTruthy) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}};
+  auto T = Template::createTemplate("{{#a.b.c}}Here{{/a.b.c}} == Here");
+  auto Out = T.render(D);
+  EXPECT_EQ("Here == Here", Out);
+}
+
+TEST(MustacheSections, DottedNamesFalsey) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}};
+  auto T = Template::createTemplate("{{#a.b.c}}Here{{/a.b.c}} == ");
+  auto Out = T.render(D);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheSections, DottedNamesBrokenChains) {
+  Value D = Object{{"a", Object{}}};
+  auto T = Template::createTemplate("{{#a.b.c}}Here{{/a.b.c}} == ");
+  auto Out = T.render(D);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheSections, SurroundingWhitespace) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate(" | {{#boolean}}\t|\t{{/boolean}} | \n");
+  auto Out = T.render(D);
+  EXPECT_EQ(" | \t|\t | \n", Out);
+}
+
+TEST(MustacheSections, InternalWhitespace) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate(
+      " | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
+  auto Out = T.render(D);
+  EXPECT_EQ(" |  \n  | \n", Out);
+}
+
+TEST(MustacheSections, IndentedInlineSections) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate(
+      " {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ(" YES\n GOOD\n", Out);
+}
+
+TEST(MustacheSections, StandaloneLines) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate(
+      "| This Is\n{{#boolean}}\n|\n{{/boolean}}\n| A Line\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheSections, IndentedStandaloneLines) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate(
+      "| This Is\n  {{#boolean}}\n|\n  {{/boolean}}\n| A Line\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheSections, StandaloneLineEndings) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate("|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|");
+  auto Out = T.render(D);
+  EXPECT_EQ("|\r\n|", Out);
+}
+
+TEST(MustacheSections, StandaloneWithoutPreviousLine) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate("  {{#boolean}}\n#{{/boolean}}\n/");
+  auto Out = T.render(D);
+  EXPECT_EQ("#\n/", Out);
+}
+
+TEST(MustacheSections, StandaloneWithoutNewline) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate("#{{#boolean}}\n/\n  {{/boolean}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("#\n/\n", Out);
+}
+
+TEST(MustacheSections, Padding) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate("|{{# boolean }}={{/ boolean }}|");
+  auto Out = T.render(D);
+  EXPECT_EQ("|=|", Out);
+}
+
+TEST(MustacheInvertedSections, Falsey) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate(
+      "{{^boolean}}This should be rendered.{{/boolean}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("This should be rendered.", Out);
+}
+
+TEST(MustacheInvertedSections, Truthy) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate(
+      "{{^boolean}}This should not be rendered.{{/boolean}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInvertedSections, NullIsFalsey) {
+  Value D = Object{{"null", nullptr}};
+  auto T =
+      Template::createTemplate("{{^null}}This should be rendered.{{/null}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("This should be rendered.", Out);
+}
+
+TEST(MustacheInvertedSections, Context) {
+  Value D = Object{{"context", Object{{"name", "Joe"}}}};
+  auto T = Template::createTemplate("{{^context}}Hi {{name}}.{{/context}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInvertedSections, List) {
+  Value D = Object{
+      {"list", Array{Object{{"n", 1}}, Object{{"n", 2}}, Object{{"n", 3}}}}};
+  auto T = Template::createTemplate("{{^list}}{{n}}{{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInvertedSections, EmptyList) {
+  Value D = Object{{"list", Array{}}};
+  auto T = Template::createTemplate("{{^list}}Yay lists!{{/list}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("Yay lists!", Out);
+}
+
+TEST(MustacheInvertedSections, Doubled) {
+  Value D = Object{{"bool", false}, {"two", "second"}};
+  auto T = Template::createTemplate("{{^bool}}\n* first\n{{/bool}}\n* "
+                                    "{{two}}\n{{^bool}}\n* third\n{{/bool}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("* first\n* second\n* third\n", Out);
+}
+
+TEST(MustacheInvertedSections, NestedFalsey) {
+  Value D = Object{{"bool", false}};
+  auto T = Template::createTemplate(
+      "| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |");
+  auto Out = T.render(D);
+  EXPECT_EQ("| A B C D E |", Out);
+}
+
+TEST(MustacheInvertedSections, NestedTruthy) {
+  Value D = Object{{"bool", true}};
+  auto T = Template::createTemplate(
+      "| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |");
+  auto Out = T.render(D);
+  EXPECT_EQ("| A  E |", Out);
+}
+
+TEST(MustacheInvertedSections, ContextMisses) {
+  Value D = Object{};
+  auto T = Template::createTemplate(
+      "[{{^missing}}Cannot find key 'missing'!{{/missing}}]");
+  auto Out = T.render(D);
+  EXPECT_EQ("[Cannot find key 'missing'!]", Out);
+}
+
+TEST(MustacheInvertedSections, DottedNamesTruthy) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}};
+  auto T = Template::createTemplate("{{^a.b.c}}Not Here{{/a.b.c}} == ");
+  auto Out = T.render(D);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheInvertedSections, DottedNamesFalsey) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}};
+  auto T = Template::createTemplate("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here");
+  auto Out = T.render(D);
+  EXPECT_EQ("Not Here == Not Here", Out);
+}
+
+TEST(MustacheInvertedSections, DottedNamesBrokenChains) {
+  Value D = Object{{"a", Object{}}};
+  auto T = Template::createTemplate("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here");
+  auto Out = T.render(D);
+  EXPECT_EQ("Not Here == Not Here", Out);
+}
+
+TEST(MustacheInvertedSections, SurroundingWhitespace) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate(" | {{^boolean}}\t|\t{{/boolean}} | \n");
+  auto Out = T.render(D);
+  EXPECT_EQ(" | \t|\t | \n", Out);
+}
+
+TEST(MustacheInvertedSections, InternalWhitespace) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate(
+      " | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
+  auto Out = T.render(D);
+  EXPECT_EQ(" |  \n  | \n", Out);
+}
+
+TEST(MustacheInvertedSections, IndentedInlineSections) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate(
+      " {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ(" NO\n WAY\n", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneLines) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate(
+      "| This Is\n{{^boolean}}\n|\n{{/boolean}}\n| A Line\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneIndentedLines) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate(
+      "| This Is\n  {{^boolean}}\n|\n  {{/boolean}}\n| A Line\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneLineEndings) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate("|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|");
+  auto Out = T.render(D);
+  EXPECT_EQ("|\r\n|", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneWithoutPreviousLine) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate("  {{^boolean}}\n^{{/boolean}}\n/");
+  auto Out = T.render(D);
+  EXPECT_EQ("^\n/", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneWithoutNewline) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate("^{{^boolean}}\n/\n  {{/boolean}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("^\n/\n", Out);
+}
+
+TEST(MustacheInvertedSections, Padding) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template::createTemplate("|{{^ boolean }}={{/ boolean }}|");
+  auto Out = T.render(D);
+  EXPECT_EQ("|=|", Out);
+}
+
+TEST(MustachePartials, BasicBehavior) {
+  Value D = Object{};
+  auto T = Template::createTemplate("{{>text}}");
+  T.registerPartial("text", "from partial");
+  auto Out = T.render(D);
+  EXPECT_EQ("from partial", Out);
+}
+
+TEST(MustachePartials, FailedLookup) {
+  Value D = Object{};
+  auto T = Template::createTemplate("{{>text}}");
+  auto Out = T.render(D);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustachePartials, Context) {
+  Value D = Object{{"text", "content"}};
+  auto T = Template::createTemplate("{{>partial}}");
+  T.registerPartial("partial", "*{{text}}*");
+  auto Out = T.render(D);
+  EXPECT_EQ("*content*", Out);
+}
+
+TEST(MustachePartials, Recursion) {
+  Value D =
+      Object{{"content", "X"},
+             {"nodes", Array{Object{{"content", "Y"}, {"nodes", Array{}}}}}};
+  auto T = Template::createTemplate("{{>node}}");
+  T.registerPartial("node", "{{content}}({{#nodes}}{{>node}}{{/nodes}})");
+  auto Out = T.render(D);
+  EXPECT_EQ("X(Y())", Out);
+}
+
+TEST(MustachePartials, Nested) {
+  Value D = Object{{"a", "hello"}, {"b", "world"}};
+  auto T = Template::createTemplate("{{>outer}}");
+  T.registerPartial("outer", "*{{a}} {{>inner}}*");
+  T.registerPartial("inner", "{{b}}!");
+  auto Out = T.render(D);
+  EXPECT_EQ("*hello world!*", Out);
+}
+
+TEST(MustachePartials, SurroundingWhitespace) {
+  Value D = Object{};
+  auto T = Template::createTemplate("| {{>partial}} |");
+  T.registerPartial("partial", "\t|\t");
+  auto Out = T.render(D);
+  EXPECT_EQ("| \t|\t |", Out);
+}
+
+TEST(MustachePartials, InlineIndentation) {
+  Value D = Object{{"data", "|"}};
+  auto T = Template::createTemplate("  {{data}}  {{> partial}}\n");
+  T.registerPartial("partial", "(\n(");
+  auto Out = T.render(D);
+  EXPECT_EQ("  |  (\n(\n", Out);
+}
+
+TEST(MustachePartials, PaddingWhitespace) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template::createTemplate("|{{> partial }}|");
+  T.registerPartial("partial", "[]");
+  auto Out = T.render(D);
+  EXPECT_EQ("|[]|", Out);
+}
+
+TEST(MustacheLambdas, BasicInterpolation) {
+  Value D = Object{};
+  auto T = Template::createTemplate("Hello, {{lambda}}!");
+  Lambda L = []() -> llvm::SmallString<128> {
+    llvm::SmallString<128> Result("World");
+    return Result;
+  };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("Hello, World!", Out);
+}
+
+TEST(MustacheLambdas, InterpolationExpansion) {
+  Value D = Object{{"planet", "World"}};
+  auto T = Template::createTemplate("Hello, {{lambda}}!");
+  Lambda L = []() -> llvm::SmallString<128> {
+    return llvm::SmallString<128>("{{planet}}");
+  };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("Hello, World!", Out);
+}
+
+TEST(MustacheLambdas, BasicMultipleCalls) {
+  Value D = Object{};
+  auto T = Template::createTemplate("{{lambda}} == {{lambda}} == {{lambda}}");
+  int I = 0;
+  Lambda L = [&I]() -> llvm::json::Value {
+    I += 1;
+    return I;
+  };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("1 == 2 == 3", Out);
+}
+
+TEST(MustacheLambdas, Escaping) {
+  Value D = Object{};
+  auto T = Template::createTemplate("<{{lambda}}{{&lambda}}");
+  Lambda L = []() -> llvm::json::Value { return ">"; };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("<>>", Out);
+}
+
+TEST(MustacheLambdas, Sections) {
+  Value D = Object{};
+  auto T = Template::createTemplate("<{{#lambda}}{{x}}{{/lambda}}>");
+  SectionLambda L = [](StringRef Text) -> llvm::json::Value {
+    if (Text == "{{x}}") {
+      return "yes";
+    }
+    return "no";
+  };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("<yes>", Out);
+}
+
+TEST(MustacheLambdas, SectionExpansion) {
+  Value D = Object{
+      {"planet", "Earth"},
+  };
+  auto T = Template::createTemplate("<{{#lambda}}-{{/lambda}}>");
+  SectionLambda L = [](StringRef Text) -> llvm::json::Value {
+    SmallString<128> Result;
+    Result += Text;
+    Result += "{{planet}}";
+    Result += Text;
+    return Result;
+  };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("<-Earth->", Out);
+}
+
+TEST(MustacheLambdas, SectionsMultipleCalls) {
+  Value D = Object{};
+  auto T = Template::createTemplate(
+      "{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}");
+  SectionLambda L = [](StringRef Text) -> llvm::json::Value {
+    SmallString<128> Result;
+    Result += "__";
+    Result += Text;
+    Result += "__";
+    return Result;
+  };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("__FILE__ != __LINE__", Out);
+}
+
+TEST(MustacheLambdas, InvertedSections) {
+  Value D = Object{{"static", "static"}};
+  auto T = Template::createTemplate("<{{^lambda}}{{static}}{{/lambda}}>");
+  SectionLambda L = [](StringRef Text) -> llvm::json::Value { return false; };
+  T.registerLambda("lambda", L);
+  auto Out = T.render(D);
+  EXPECT_EQ("<>", Out);
+}
+
+TEST(MustacheComments, Inline) {
+  // Comment blocks should be removed from the template.
+  Value D = {};
+  auto T = Template::createTemplate("12345{{! Comment Block! }}67890");
+  auto Out = T.render(D);
+  EXPECT_EQ("1234567890", Out);
+}
+
+TEST(MustacheComments, Multiline) {
+  // Multiline comments should be permitted.
+  Value D = {};
+  auto T = Template::createTemplate(
+      "12345{{!\n  This is a\n  multi-line comment...\n}}67890\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("1234567890\n", Out);
+}
+
+TEST(MustacheComments, Standalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T = Template::createTemplate("Begin.\n{{! Comment Block! }}\nEnd.\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, IndentedStandalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T = Template::createTemplate(
+      "Begin.\n  {{! Indented Comment Block! }}\nEnd.\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, StandaloneLineEndings) {
+  // "\r\n" should be considered a newline for standalone tags.
+  Value D = {};
+  auto T = Template::createTemplate("|\r\n{{! Standalone Comment }}\r\n|");
+  auto Out = T.render(D);
+  EXPECT_EQ("|\r\n|", Out);
+}
+
+TEST(MustacheComments, StandaloneWithoutPreviousLine) {
+  // Standalone tags should not require a newline to precede them.
+  Value D = {};
+  auto T = Template::createTemplate("  {{! I'm Still Standalone }}\n!");
+  auto Out = T.render(D);
+  EXPECT_EQ("!", Out);
+}
+
+TEST(MustacheComments, StandaloneWithoutNewline) {
+  // Standalone tags should not require a newline to follow them.
+  Value D = {};
+  auto T = Template::createTemplate("!\n  {{! I'm Still Standalone }}");
+  auto Out = T.render(D);
+  EXPECT_EQ("!\n", Out);
+}
+
+TEST(MustacheComments, MultilineStandalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T = Template::createTemplate(
+      "Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, IndentedMultilineStandalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T = Template::createTemplate(
+      "Begin.\n  {{!\n    Something's going on here...\n  }}\nEnd.\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, IndentedInline) {
+  // Inline comments should not strip whitespace.
+  Value D = {};
+  auto T = Template::createTemplate("  12 {{! 34 }}\n");
+  auto Out = T.render(D);
+  EXPECT_EQ("  12 \n", Out);
+}
+
+TEST(MustacheComments, SurroundingWhitespace) {
+  // Comment removal should preserve surrounding whitespace.
+  Value D = {};
+  auto T = Template::createTemplate("12345 {{! Comment Block! }} 67890");
+  auto Out = T.render(D);
+  EXPECT_EQ("12345  67890", Out);
+}
+
+TEST(MustacheComments, VariableNameCollision) {
+  // Comments must never render, even if a variable with the same name exists.
+  Value D = Object{
+      {"! comment", 1}, {"! comment ", 2}, {"!comment", 3}, {"comment", 4}};
+  auto T = Template::createTemplate("comments never show: >{{! comment }}<");
+  auto Out = T.render(D);
+  EXPECT_EQ("comments never show: ><", Out);
+}
\ No newline at end of file

>From 6e893c07ca49773825ddcb6d99107b96b37d5540 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 15:51:33 -0400
Subject: [PATCH 12/21] [llvm][support] fix mustache test

---
 llvm/lib/Support/Mustache.cpp           | 11 ++++++-----
 llvm/unittests/Support/MustacheTest.cpp |  4 ++--
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 80b3d8bc53651b..09adba4d6239ee 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -137,16 +137,16 @@ std::vector<Token> tokenize(StringRef Template) {
     if (I > 0 && Tokens[I - 1].getType() == Token::Type::Text &&
         RequiresCleanUp) {
       Token &PrevToken = Tokens[I - 1];
-      StringRef TokenBody = PrevToken.getTokenBody().rtrim(" \t\v\t");
+      StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
       if (TokenBody.ends_with("\n") || TokenBody.ends_with("\r\n") ||
-          TokenBody.empty()) {
+          (TokenBody.empty() && I == 1)) {
         NoTextBehind = true;
       }
     }
     if (I < Tokens.size() - 1 && Tokens[I + 1].getType() == Token::Type::Text &&
         RequiresCleanUp) {
       Token &NextToken = Tokens[I + 1];
-      StringRef TokenBody = NextToken.getTokenBody().ltrim(" ");
+      StringRef TokenBody = NextToken.getRawBody().ltrim(" ");
       if (TokenBody.starts_with("\r\n") || TokenBody.starts_with("\n")) {
         NoTextAhead = true;
       }
@@ -363,8 +363,9 @@ ASTNode::render(Value Data,
       StringRef LambdaStr = printJson(LambdaResult);
       Parser P = Parser(LambdaStr);
       std::shared_ptr<ASTNode> LambdaNode = P.parse();
-      return LambdaNode->render(Data, Partials, Lambdas, SectionLambdas,
-                                Escapes);
+      SmallString<128> RenderStr =
+          LambdaNode->render(Data, Partials, Lambdas, SectionLambdas, Escapes);
+      return escapeString(RenderStr, Escapes);
     }
     return escapeString(printJson(Context), Escapes);
   }
diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
index ceee8942f88f4e..dfd28b0b5b2377 100644
--- a/llvm/unittests/Support/MustacheTest.cpp
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -768,9 +768,9 @@ TEST(MustachePartials, SurroundingWhitespace) {
 TEST(MustachePartials, InlineIndentation) {
   Value D = Object{{"data", "|"}};
   auto T = Template::createTemplate("  {{data}}  {{> partial}}\n");
-  T.registerPartial("partial", "(\n(");
+  T.registerPartial("partial", "<\n<");
   auto Out = T.render(D);
-  EXPECT_EQ("  |  (\n(\n", Out);
+  EXPECT_EQ("  |  <\n<\n", Out);
 }
 
 TEST(MustachePartials, PaddingWhitespace) {

>From 976593eff073b8741b6e3d1e4d7bcf648f6983d8 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 16:39:30 -0400
Subject: [PATCH 13/21] [llvm][support] add comments

---
 llvm/include/llvm/Support/Mustache.h | 51 +++++++++++++++++--
 llvm/lib/Support/Mustache.cpp        | 75 +++++++++-------------------
 2 files changed, 71 insertions(+), 55 deletions(-)

diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index cb7d80e47809e8..f5714c19089348 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -7,8 +7,50 @@
 //===----------------------------------------------------------------------===//
 //
 // Implementation of the Mustache templating language supports version 1.4.2
+// currently relies on llvm::json::Value for data input
+// see the Mustache spec for more information
 // (https://mustache.github.io/mustache.5.html).
 //
+// Current Features Supported:
+// - Variables
+// - Sections
+// - Inverted Sections
+// - Partials
+// - Comments
+// - Lambdas
+// - Unescaped Variables
+//
+// Features Not Supported:
+// - Set Delimiter
+// - Blocks
+// - Parents
+// - Dynamic Names
+//
+// Usage:
+// - Creating a simple template and rendering it:
+// \code
+//   auto Template = Template::createTemplate("Hello, {{name}}!");
+//   Value Data = {{"name", "World"}};
+//   StringRef Rendered = Template.render(Data);
+//   // Rendered == "Hello, World!"
+// \endcode
+// - Creating a template with a partial and rendering it:
+// \code
+//   auto Template = Template::createTemplate("{{>partial}}");
+//   Template.registerPartial("partial", "Hello, {{name}}!");
+//   Value Data = {{"name", "World"}};
+//   StringRef Rendered = Template.render(Data);
+//   // Rendered == "Hello, World!"
+// \endcode
+// - Creating a template with a lambda and rendering it:
+// \code
+//   auto Template = Template::createTemplate("{{#lambda}}Hello,
+//                                             {{name}}!{{/lambda}}");
+//   Template.registerLambda("lambda", []() { return true; });
+//   Value Data = {{"name", "World"}};
+//   StringRef Rendered = Template.render(Data);
+//   // Rendered == "Hello, World!"
+// \endcode
 //===----------------------------------------------------------------------===//
 
 #ifndef LLVM_SUPPORT_MUSTACHE
@@ -17,8 +59,6 @@
 #include "Error.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/Support/JSON.h"
-#include <string>
-#include <variant>
 #include <vector>
 
 namespace llvm {
@@ -96,8 +136,6 @@ class ASTNode {
 
   void setRawBody(StringRef NewBody) { RawBody = NewBody; };
 
-  SmallString<128> getRawBody() const { return RawBody; };
-
   std::shared_ptr<ASTNode> getLastChild() const {
     return Children.empty() ? nullptr : Children.back();
   };
@@ -120,6 +158,8 @@ class ASTNode {
   llvm::json::Value LocalContext;
 };
 
+// A Template represents the container for the AST and the partials
+// and Lambdas that are registered with it.
 class Template {
 public:
   static Template createTemplate(StringRef TemplateStr);
@@ -132,6 +172,9 @@ class Template {
 
   void registerLambda(StringRef Name, SectionLambda Lambda);
 
+  // By default the Mustache Spec Specifies that HTML special characters
+  // should be escaped. This function allows the user to specify which
+  // characters should be escaped.
   void registerEscape(DenseMap<char, StringRef> Escapes);
 
 private:
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 09adba4d6239ee..18a94f9f241c4d 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -8,9 +8,6 @@
 
 #include "llvm/Support/Mustache.h"
 #include "llvm/Support/Error.h"
-#include <iostream>
-#include <regex>
-#include <sstream>
 
 using namespace llvm;
 using namespace llvm::json;
@@ -20,11 +17,10 @@ SmallString<128> escapeString(StringRef Input,
                               DenseMap<char, StringRef> &Escape) {
   SmallString<128> EscapedString("");
   for (char C : Input) {
-    if (Escape.find(C) != Escape.end()) {
+    if (Escape.find(C) != Escape.end())
       EscapedString += Escape[C];
-    } else {
+    else
       EscapedString += C;
-    }
   }
   return EscapedString;
 }
@@ -52,9 +48,9 @@ Token::Token(StringRef RawBody, StringRef InnerBody, char Identifier)
     return;
 
   StringRef AccessorStr = InnerBody;
-  if (TokenType != Type::Variable) {
+  if (TokenType != Type::Variable)
     AccessorStr = InnerBody.substr(1);
-  }
+
   Accessor = split(AccessorStr.trim(), '.');
 }
 
@@ -118,9 +114,8 @@ std::vector<Token> tokenize(StringRef Template) {
     DelimiterStart = Template.find(Open, Start);
   }
 
-  if (Start < Template.size()) {
+  if (Start < Template.size())
     Tokens.push_back(Token(Template.substr(Start)));
-  }
 
   // fix up white spaces for
   // open sections/inverted sections/close section/comment
@@ -139,38 +134,27 @@ std::vector<Token> tokenize(StringRef Template) {
       Token &PrevToken = Tokens[I - 1];
       StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
       if (TokenBody.ends_with("\n") || TokenBody.ends_with("\r\n") ||
-          (TokenBody.empty() && I == 1)) {
+          (TokenBody.empty() && I == 1))
         NoTextBehind = true;
-      }
     }
     if (I < Tokens.size() - 1 && Tokens[I + 1].getType() == Token::Type::Text &&
         RequiresCleanUp) {
       Token &NextToken = Tokens[I + 1];
       StringRef TokenBody = NextToken.getRawBody().ltrim(" ");
-      if (TokenBody.starts_with("\r\n") || TokenBody.starts_with("\n")) {
+      if (TokenBody.starts_with("\r\n") || TokenBody.starts_with("\n"))
         NoTextAhead = true;
-      }
     }
 
-    if (NoTextBehind && NoTextAhead) {
-      Token &PrevToken = Tokens[I - 1];
-      Token &NextToken = Tokens[I + 1];
-      StringRef NextTokenBody = NextToken.getTokenBody();
-      PrevToken.setTokenBody(PrevToken.getTokenBody().rtrim(" \t\v\t"));
-      if (NextTokenBody.starts_with("\r\n")) {
-        NextToken.setTokenBody(NextTokenBody.substr(2));
-      } else if (NextToken.getTokenBody().starts_with("\n")) {
-        NextToken.setTokenBody(NextTokenBody.substr(1));
-      }
-    } else if (NoTextAhead && I == 0) {
+    if ((NoTextBehind && NoTextAhead) || (NoTextAhead && I == 0)) {
       Token &NextToken = Tokens[I + 1];
       StringRef NextTokenBody = NextToken.getTokenBody();
-      if (NextTokenBody.starts_with("\r\n")) {
+      if (NextTokenBody.starts_with("\r\n"))
         NextToken.setTokenBody(NextTokenBody.substr(2));
-      } else if (NextToken.getTokenBody().starts_with("\n")) {
+      else if (NextToken.getTokenBody().starts_with("\n"))
         NextToken.setTokenBody(NextTokenBody.substr(1));
-      }
-    } else if (NoTextBehind && I == Tokens.size() - 1) {
+    }
+    if ((NoTextBehind && NoTextAhead) ||
+        (NoTextBehind && I == Tokens.size() - 1)) {
       Token &PrevToken = Tokens[I - 1];
       StringRef PrevTokenBody = PrevToken.getTokenBody();
       PrevToken.setTokenBody(PrevTokenBody.rtrim(" \t\v\t"));
@@ -239,9 +223,8 @@ void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
       std::size_t End = CurrentPtr;
       SmallString<128> RawBody;
       if (Start + 1 < End - 1)
-        for (std::size_t I = Start + 1; I < End - 1; I++) {
+        for (std::size_t I = Start + 1; I < End - 1; I++)
           RawBody += Tokens[I].getRawBody();
-        }
       else if (Start + 1 == End - 1)
         RawBody = Tokens[Start].getRawBody();
       CurrentNode->setRawBody(RawBody);
@@ -256,18 +239,16 @@ void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
       std::size_t End = CurrentPtr;
       SmallString<128> RawBody;
       if (Start + 1 < End - 1)
-        for (std::size_t I = Start + 1; I < End - 1; I++) {
+        for (std::size_t I = Start + 1; I < End - 1; I++)
           RawBody += Tokens[I].getRawBody();
-        }
       else if (Start + 1 == End - 1)
         RawBody = Tokens[Start].getRawBody();
       CurrentNode->setRawBody(RawBody);
       Parent->addChild(CurrentNode);
       break;
     }
-    case Token::Type::SectionClose: {
+    case Token::Type::SectionClose:
       return;
-    }
     default:
       break;
     }
@@ -309,14 +290,11 @@ void Template::registerEscape(DenseMap<char, StringRef> E) { Escapes = E; }
 SmallString<128> printJson(Value &Data) {
 
   SmallString<128> Result;
-  if (Data.getAsNull()) {
+  if (Data.getAsNull())
     return Result;
-  }
-  if (auto *Arr = Data.getAsArray()) {
-    if (Arr->empty()) {
+  if (auto *Arr = Data.getAsArray())
+    if (Arr->empty())
       return Result;
-    }
-  }
   if (Data.getAsString()) {
     Result += Data.getAsString()->str();
     return Result;
@@ -436,12 +414,10 @@ Value ASTNode::findContext() {
   // We attempt to find the JSON context in the current node if it is not found
   // we traverse the parent nodes to find the context until we reach the root
   // node or the context is found
-  if (Accessor.empty()) {
+  if (Accessor.empty())
     return nullptr;
-  }
-  if (Accessor[0] == ".") {
+  if (Accessor[0] == ".")
     return LocalContext;
-  }
   json::Object *CurrentContext = LocalContext.getAsObject();
   SmallString<128> CurrentAccessor = Accessor[0];
   std::weak_ptr<ASTNode> CurrentParent = Parent;
@@ -458,17 +434,14 @@ Value ASTNode::findContext() {
   for (std::size_t I = 0; I < Accessor.size(); I++) {
     CurrentAccessor = Accessor[I];
     Value *CurrentValue = CurrentContext->get(CurrentAccessor);
-    if (!CurrentValue) {
+    if (!CurrentValue)
       return nullptr;
-    }
     if (I < Accessor.size() - 1) {
       CurrentContext = CurrentValue->getAsObject();
-      if (!CurrentContext) {
+      if (!CurrentContext)
         return nullptr;
-      }
-    } else {
+    } else
       Context = *CurrentValue;
-    }
   }
   return Context;
 }

>From 6a60eabf0e28e1a43d0943b5952f2eb5865cb5b7 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 17:21:22 -0400
Subject: [PATCH 14/21] [llvm][support] add comments

---
 llvm/include/llvm/Support/Mustache.h | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index f5714c19089348..03892ea5679ca0 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -27,23 +27,21 @@
 // - Dynamic Names
 //
 // Usage:
-// - Creating a simple template and rendering it:
 // \code
+//   // Creating a simple template and rendering it
 //   auto Template = Template::createTemplate("Hello, {{name}}!");
 //   Value Data = {{"name", "World"}};
 //   StringRef Rendered = Template.render(Data);
 //   // Rendered == "Hello, World!"
-// \endcode
-// - Creating a template with a partial and rendering it:
-// \code
+//
+//   // Creating a template with a partial and rendering it
 //   auto Template = Template::createTemplate("{{>partial}}");
 //   Template.registerPartial("partial", "Hello, {{name}}!");
 //   Value Data = {{"name", "World"}};
 //   StringRef Rendered = Template.render(Data);
 //   // Rendered == "Hello, World!"
-// \endcode
-// - Creating a template with a lambda and rendering it:
-// \code
+//
+//   // Creating a template with a lambda and rendering it
 //   auto Template = Template::createTemplate("{{#lambda}}Hello,
 //                                             {{name}}!{{/lambda}}");
 //   Template.registerLambda("lambda", []() { return true; });

>From 6a1fcc8f0f4c40a35d4afff7ebd66c2c99b356c1 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 17:25:06 -0400
Subject: [PATCH 15/21] [llvm][support] use enumerate for loop

---
 llvm/lib/Support/Mustache.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 18a94f9f241c4d..ad5d30d335f626 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -431,8 +431,7 @@ Value ASTNode::findContext() {
     return nullptr;
   }
   Value Context = nullptr;
-  for (std::size_t I = 0; I < Accessor.size(); I++) {
-    CurrentAccessor = Accessor[I];
+  for (auto CurrentAccessor : Accessor) {
     Value *CurrentValue = CurrentContext->get(CurrentAccessor);
     if (!CurrentValue)
       return nullptr;

>From eb1e1a66382551ed2d65c37a2a6fc307051904a2 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 17:34:43 -0400
Subject: [PATCH 16/21] [llvm][support] clang-format

---
 llvm/include/llvm/Support/Mustache.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 03892ea5679ca0..db09089707167d 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -114,15 +114,15 @@ class ASTNode {
     InvertSection,
   };
 
-  ASTNode() : T(Type::Root), LocalContext(nullptr){};
+  ASTNode() : T(Type::Root), LocalContext(nullptr) {};
 
   ASTNode(StringRef Body, std::shared_ptr<ASTNode> Parent)
-      : T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr){};
+      : T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr) {};
 
   // Constructor for Section/InvertSection/Variable/UnescapeVariable
   ASTNode(Type T, Accessor Accessor, std::shared_ptr<ASTNode> Parent)
       : T(T), Accessor(Accessor), Parent(Parent), LocalContext(nullptr),
-        Children({}){};
+        Children({}) {};
 
   void addChild(std::shared_ptr<ASTNode> Child) {
     Children.emplace_back(Child);

>From f4b052084bf41dd109213af38622fc5df581691f Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 17:43:01 -0400
Subject: [PATCH 17/21] [llvm][support] fix mustache test

---
 llvm/unittests/Support/MustacheTest.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
index dfd28b0b5b2377..c38a2ed2823379 100644
--- a/llvm/unittests/Support/MustacheTest.cpp
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -139,9 +139,9 @@ TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
                Object{{"c",
                        Object{{"d",
                                Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}};
-  auto T = Template::createTemplate("{{a.b.c.d.e.name}} == Phil");
+  auto T = Template::createTemplate("{{a.b.c.d.e.name}}");
   auto Out = T.render(D);
-  EXPECT_EQ("Phil == Phil", Out);
+  EXPECT_EQ("Phil", Out);
 }
 
 TEST(MustacheInterpolation, DottedNamesBrokenChains) {
@@ -171,9 +171,9 @@ TEST(MustacheInterpolation, DottedNamesInitialResolution) {
                     Object{{"d", Object{{"e", Object{{"name", "Phil"}}}}}}}}}}},
       {"b",
        Object{{"c", Object{{"d", Object{{"e", Object{{"name", "Wrong"}}}}}}}}}};
-  auto T = Template::createTemplate("{{#a}}{{b.c.d.e.name}}{{/a}} == Phil");
+  auto T = Template::createTemplate("{{#a}}{{b.c.d.e.name}}{{/a}}");
   auto Out = T.render(D);
-  EXPECT_EQ("Phil == Phil", Out);
+  EXPECT_EQ("Phil", Out);
 }
 
 TEST(MustacheInterpolation, DottedNamesContextPrecedence) {

>From 7ffaeec649728e21687e81fc3c479def997b1d83 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 17:50:37 -0400
Subject: [PATCH 18/21] [llvm][support] clang-format

---
 llvm/include/llvm/Support/Mustache.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index db09089707167d..afde8d995cc147 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -176,7 +176,7 @@ class Template {
   void registerEscape(DenseMap<char, StringRef> Escapes);
 
 private:
-  Template(std::shared_ptr<ASTNode> Tree) : Tree(Tree){};
+  Template(std::shared_ptr<ASTNode> Tree) : Tree(Tree) {};
   DenseMap<StringRef, std::shared_ptr<ASTNode>> Partials;
   DenseMap<StringRef, Lambda> Lambdas;
   DenseMap<StringRef, SectionLambda> SectionLambdas;

>From 746fb97f94d3533b84ff674f9f7140fdc1ef5522 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 6 Sep 2024 18:22:52 -0400
Subject: [PATCH 19/21] [llvm][support] use llvm enumerate

---
 llvm/lib/Support/Mustache.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index ad5d30d335f626..f0fb6767f6fcd5 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -431,11 +431,11 @@ Value ASTNode::findContext() {
     return nullptr;
   }
   Value Context = nullptr;
-  for (auto CurrentAccessor : Accessor) {
-    Value *CurrentValue = CurrentContext->get(CurrentAccessor);
+  for (auto E : enumerate(Accessor)) {
+    Value *CurrentValue = CurrentContext->get(E.value());
     if (!CurrentValue)
       return nullptr;
-    if (I < Accessor.size() - 1) {
+    if (E.index() < Accessor.size() - 1) {
       CurrentContext = CurrentValue->getAsObject();
       if (!CurrentContext)
         return nullptr;

>From bcc86fe0ad6f55a27c3fe50231da8d152517ac17 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Wed, 11 Sep 2024 18:41:38 -0400
Subject: [PATCH 20/21] [llvm][support] fix unittest

---
 llvm/include/llvm/Support/Mustache.h    |  4 ++--
 llvm/lib/Support/Mustache.cpp           |  7 ++++---
 llvm/unittests/Support/MustacheTest.cpp | 10 +++++-----
 3 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index afde8d995cc147..6243e29f52282d 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -121,8 +121,8 @@ class ASTNode {
 
   // Constructor for Section/InvertSection/Variable/UnescapeVariable
   ASTNode(Type T, Accessor Accessor, std::shared_ptr<ASTNode> Parent)
-      : T(T), Accessor(Accessor), Parent(Parent), LocalContext(nullptr),
-        Children({}) {};
+      : T(T), Parent(Parent), Children({}), Accessor(Accessor),
+        LocalContext(nullptr) {};
 
   void addChild(std::shared_ptr<ASTNode> Child) {
     Children.emplace_back(Child);
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index f0fb6767f6fcd5..7372a248d209bc 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -55,7 +55,7 @@ Token::Token(StringRef RawBody, StringRef InnerBody, char Identifier)
 }
 
 Token::Token(StringRef Str)
-    : RawBody(Str), TokenType(Type::Text), TokenBody(Str), Accessor({}) {}
+    : TokenType(Type::Text), RawBody(Str), Accessor({}), TokenBody(Str) {}
 
 Token::Type Token::getTokenType(char Identifier) {
   switch (Identifier) {
@@ -256,7 +256,7 @@ void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
 }
 
 Template Template::createTemplate(StringRef TemplateStr) {
-  Parser P = Parser(TemplateStr);
+  Parser P = Parser(TemplateStr.str());
   std::shared_ptr<ASTNode> MustacheTree = P.parse();
   Template T = Template(MustacheTree);
   // the default behaviour is to escape html entities
@@ -333,6 +333,7 @@ ASTNode::render(Value Data,
           Partial->render(Data, Partials, Lambdas, SectionLambdas, Escapes);
       return Result;
     }
+    return Result;
   }
   case Variable: {
     if (Lambdas.find(Accessor[0]) != Lambdas.end()) {
@@ -373,7 +374,7 @@ ASTNode::render(Value Data,
       if (isFalsey(Return))
         return Result;
       StringRef LambdaStr = printJson(Return);
-      Parser P = Parser(LambdaStr);
+      Parser P = Parser(LambdaStr.str());
       std::shared_ptr<ASTNode> LambdaNode = P.parse();
       return LambdaNode->render(Data, Partials, Lambdas, SectionLambdas,
                                 Escapes);
diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
index c38a2ed2823379..33cbad56240faf 100644
--- a/llvm/unittests/Support/MustacheTest.cpp
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -12,6 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Support/Mustache.h"
+#include "llvm/Support/raw_ostream.h"
 #include "gtest/gtest.h"
 
 using namespace llvm;
@@ -784,9 +785,8 @@ TEST(MustachePartials, PaddingWhitespace) {
 TEST(MustacheLambdas, BasicInterpolation) {
   Value D = Object{};
   auto T = Template::createTemplate("Hello, {{lambda}}!");
-  Lambda L = []() -> llvm::SmallString<128> {
-    llvm::SmallString<128> Result("World");
-    return Result;
+  Lambda L = []() -> llvm::json::Value {
+    return "World";
   };
   T.registerLambda("lambda", L);
   auto Out = T.render(D);
@@ -796,8 +796,8 @@ TEST(MustacheLambdas, BasicInterpolation) {
 TEST(MustacheLambdas, InterpolationExpansion) {
   Value D = Object{{"planet", "World"}};
   auto T = Template::createTemplate("Hello, {{lambda}}!");
-  Lambda L = []() -> llvm::SmallString<128> {
-    return llvm::SmallString<128>("{{planet}}");
+  Lambda L = []() -> llvm::json::Value {
+    return "{{planet}}";
   };
   T.registerLambda("lambda", L);
   auto Out = T.render(D);

>From 95ad7a6a0b128128b608606bd055f386d8162a5b Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Wed, 11 Sep 2024 18:50:01 -0400
Subject: [PATCH 21/21] [llvm][support] clang-format

---
 llvm/unittests/Support/MustacheTest.cpp | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
index 33cbad56240faf..3f3f04a9b55b13 100644
--- a/llvm/unittests/Support/MustacheTest.cpp
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -785,9 +785,7 @@ TEST(MustachePartials, PaddingWhitespace) {
 TEST(MustacheLambdas, BasicInterpolation) {
   Value D = Object{};
   auto T = Template::createTemplate("Hello, {{lambda}}!");
-  Lambda L = []() -> llvm::json::Value {
-    return "World";
-  };
+  Lambda L = []() -> llvm::json::Value { return "World"; };
   T.registerLambda("lambda", L);
   auto Out = T.render(D);
   EXPECT_EQ("Hello, World!", Out);
@@ -796,9 +794,7 @@ TEST(MustacheLambdas, BasicInterpolation) {
 TEST(MustacheLambdas, InterpolationExpansion) {
   Value D = Object{{"planet", "World"}};
   auto T = Template::createTemplate("Hello, {{lambda}}!");
-  Lambda L = []() -> llvm::json::Value {
-    return "{{planet}}";
-  };
+  Lambda L = []() -> llvm::json::Value { return "{{planet}}"; };
   T.registerLambda("lambda", L);
   auto Out = T.render(D);
   EXPECT_EQ("Hello, World!", Out);



More information about the cfe-commits mailing list