[llvm] 8f05f25 - reapply [llvm] add support for mustache templating language (#130732)

via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 11 17:46:16 PDT 2025


Author: PeterChou1
Date: 2025-03-11T20:46:11-04:00
New Revision: 8f05f253601618a2a9c7203cf9e7b9de4f9e22de

URL: https://github.com/llvm/llvm-project/commit/8f05f253601618a2a9c7203cf9e7b9de4f9e22de
DIFF: https://github.com/llvm/llvm-project/commit/8f05f253601618a2a9c7203cf9e7b9de4f9e22de.diff

LOG: reapply [llvm] add support for mustache templating language (#130732)

Reapply https://github.com/llvm/llvm-project/pull/105893

Fixes errors which broke build bot that uses GCC as a compiler
https://lab.llvm.org/buildbot/#/builders/136/builds/3100

The issue here was that using Accessor defined in the anonymous
namespace introduces Accessor as a type alias. Which is, later redeclare
as members in classes Token and ASTNode with the same name which causes
error in GCC. The patch fixes it by renaming the Accesor to
AccessorValue. It also fixes warnings caused by the compile due to
initialization


Adds Support for the Mustache Templating Language. See specs here:
https://mustache.github.io/mustache.5.html
This patch implements support+tests for majority of the features of the
language including:

   - Variables
   - Comments
   - Lambdas
   - Sections

This meant as a library to support places where we have to generate
HTML, such as in clang-doc.

Added: 
    llvm/include/llvm/Support/Mustache.h
    llvm/lib/Support/Mustache.cpp
    llvm/unittests/Support/MustacheTest.cpp

Modified: 
    llvm/lib/Support/CMakeLists.txt
    llvm/unittests/Support/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
new file mode 100644
index 0000000000000..41173b96d1a9a
--- /dev/null
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -0,0 +1,127 @@
+//===--- 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
+// 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
+//
+// The Template class is a container class that outputs the Mustache template
+// string and is the main class for users. It stores all the lambdas and the
+// ASTNode Tree. When the Template is instantiated it tokenizes the Template
+// String and creates a vector of Tokens. Then it calls a basic recursive
+// descent parser to construct the ASTNode Tree. The ASTNodes are all stored
+// in an arena allocator which is freed once the template class goes out of
+// scope.
+//
+// Usage:
+// \code
+//   // Creating a simple template and rendering it
+//   auto Template = Template("Hello, {{name}}!");
+//   Value Data = {{"name", "World"}};
+//   std::string Out;
+//   raw_string_ostream OS(Out);
+//   T.render(Data, OS);
+//   // Out == "Hello, World!"
+//
+//   // Creating a template with a partial and rendering it
+//   auto Template = Template("{{>partial}}");
+//   Template.registerPartial("partial", "Hello, {{name}}!");
+//   Value Data = {{"name", "World"}};
+//   std::string Out;
+//   raw_string_ostream OS(Out);
+//   T.render(Data, OS);
+//   // Out == "Hello, World!"
+//
+//   // Creating a template with a lambda and rendering it
+//   Value D = Object{};
+//   auto T = Template("Hello, {{lambda}}!");
+//   Lambda L = []() -> llvm::json::Value { return "World"; };
+//   T.registerLambda("lambda", L);
+//   std::string Out;
+//   raw_string_ostream OS(Out);
+//   T.render(D, OS);
+//   // Out == "Hello, World!"
+// \endcode
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_MUSTACHE
+#define LLVM_SUPPORT_MUSTACHE
+
+#include "Error.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/StringSaver.h"
+#include <functional>
+#include <vector>
+
+namespace llvm::mustache {
+
+using Lambda = std::function<llvm::json::Value()>;
+using SectionLambda = std::function<llvm::json::Value(std::string)>;
+
+class ASTNode;
+
+// A Template represents the container for the AST and the partials
+// and Lambdas that are registered with it.
+class Template {
+public:
+  Template(StringRef TemplateStr);
+
+  Template(const Template &) = delete;
+
+  Template &operator=(const Template &) = delete;
+
+  Template(Template &&Other) noexcept;
+
+  Template &operator=(Template &&Other) noexcept;
+
+  void render(const llvm::json::Value &Data, llvm::raw_ostream &OS);
+
+  void registerPartial(std::string Name, std::string Partial);
+
+  void registerLambda(std::string Name, Lambda Lambda);
+
+  void registerLambda(std::string 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 overrideEscapeCharacters(DenseMap<char, std::string> Escapes);
+
+private:
+  StringMap<ASTNode *> Partials;
+  StringMap<Lambda> Lambdas;
+  StringMap<SectionLambda> SectionLambdas;
+  DenseMap<char, std::string> Escapes;
+  // The allocator for the ASTNode Tree
+  llvm::BumpPtrAllocator AstAllocator;
+  // Allocator for each render call resets after each render
+  llvm::BumpPtrAllocator RenderAllocator;
+  ASTNode *Tree;
+};
+} // namespace llvm::mustache
+
+#endif // LLVM_SUPPORT_MUSTACHE

diff  --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 49a26a618de83..2754c97fce6c1 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -220,6 +220,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 0000000000000..40204fa34b422
--- /dev/null
+++ b/llvm/lib/Support/Mustache.cpp
@@ -0,0 +1,793 @@
+//===-- 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/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
+#include <sstream>
+
+using namespace llvm;
+using namespace llvm::mustache;
+
+namespace {
+
+using Accessor = SmallVector<std::string>;
+
+static bool isFalsey(const json::Value &V) {
+  return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||
+         (V.getAsArray() && V.getAsArray()->empty());
+}
+
+static Accessor splitMustacheString(StringRef Str) {
+  // We split the mustache string into an accessor.
+  // For example:
+  //    "a.b.c" would be split into {"a", "b", "c"}
+  // We make an exception for a single dot which
+  // refers to the current context.
+  Accessor Tokens;
+  if (Str == ".") {
+    Tokens.emplace_back(Str);
+    return Tokens;
+  }
+  while (!Str.empty()) {
+    StringRef Part;
+    std::tie(Part, Str) = Str.split(".");
+    Tokens.emplace_back(Part.trim());
+  }
+  return Tokens;
+}
+} // namespace
+
+namespace llvm::mustache {
+
+class Token {
+public:
+  enum class Type {
+    Text,
+    Variable,
+    Partial,
+    SectionOpen,
+    SectionClose,
+    InvertSectionOpen,
+    UnescapeVariable,
+    Comment,
+  };
+
+  Token(std::string Str)
+      : TokenType(Type::Text), RawBody(std::move(Str)), TokenBody(RawBody),
+        AccessorValue({}), Indentation(0) {};
+
+  Token(std::string RawBody, std::string TokenBody, char Identifier)
+      : RawBody(std::move(RawBody)), TokenBody(std::move(TokenBody)),
+        Indentation(0) {
+    TokenType = getTokenType(Identifier);
+    if (TokenType == Type::Comment)
+      return;
+    StringRef AccessorStr(this->TokenBody);
+    if (TokenType != Type::Variable)
+      AccessorStr = AccessorStr.substr(1);
+    AccessorValue = splitMustacheString(StringRef(AccessorStr).trim());
+  }
+
+  Accessor getAccessor() const { return AccessorValue; }
+
+  Type getType() const { return TokenType; }
+
+  void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }
+
+  size_t getIndentation() const { return Indentation; }
+
+  static Type getTokenType(char Identifier) {
+    switch (Identifier) {
+    case '#':
+      return Type::SectionOpen;
+    case '/':
+      return Type::SectionClose;
+    case '^':
+      return Type::InvertSectionOpen;
+    case '!':
+      return Type::Comment;
+    case '>':
+      return Type::Partial;
+    case '&':
+      return Type::UnescapeVariable;
+    default:
+      return Type::Variable;
+    }
+  }
+
+  Type TokenType;
+  // RawBody is the original string that was tokenized.
+  std::string RawBody;
+  // TokenBody is the original string with the identifier removed.
+  std::string TokenBody;
+  Accessor AccessorValue;
+  size_t Indentation;
+};
+
+class ASTNode {
+public:
+  enum Type {
+    Root,
+    Text,
+    Partial,
+    Variable,
+    UnescapeVariable,
+    Section,
+    InvertSection,
+  };
+
+  ASTNode(llvm::BumpPtrAllocator &Alloc, llvm::StringMap<ASTNode *> &Partials,
+          llvm::StringMap<Lambda> &Lambdas,
+          llvm::StringMap<SectionLambda> &SectionLambdas,
+          llvm::DenseMap<char, std::string> &Escapes)
+      : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas),
+        SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Type::Root),
+        Parent(nullptr), ParentContext(nullptr) {}
+
+  ASTNode(std::string Body, ASTNode *Parent, llvm::BumpPtrAllocator &Alloc,
+          llvm::StringMap<ASTNode *> &Partials,
+          llvm::StringMap<Lambda> &Lambdas,
+          llvm::StringMap<SectionLambda> &SectionLambdas,
+          llvm::DenseMap<char, std::string> &Escapes)
+      : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas),
+        SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Type::Text),
+        Body(std::move(Body)), Parent(Parent), ParentContext(nullptr) {}
+
+  // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes
+  ASTNode(Type Ty, Accessor Accessor, ASTNode *Parent,
+          llvm::BumpPtrAllocator &Alloc, llvm::StringMap<ASTNode *> &Partials,
+          llvm::StringMap<Lambda> &Lambdas,
+          llvm::StringMap<SectionLambda> &SectionLambdas,
+          llvm::DenseMap<char, std::string> &Escapes)
+      : Allocator(Alloc), Partials(Partials), Lambdas(Lambdas),
+        SectionLambdas(SectionLambdas), Escapes(Escapes), Ty(Ty),
+        Parent(Parent), AccessorValue(std::move(Accessor)),
+        ParentContext(nullptr) {}
+
+  void addChild(ASTNode *Child) { Children.emplace_back(Child); };
+
+  void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); };
+
+  void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
+
+  void render(const llvm::json::Value &Data, llvm::raw_ostream &OS);
+
+private:
+  void renderLambdas(const llvm::json::Value &Contexts, llvm::raw_ostream &OS,
+                     Lambda &L);
+
+  void renderSectionLambdas(const llvm::json::Value &Contexts,
+                            llvm::raw_ostream &OS, SectionLambda &L);
+
+  void renderPartial(const llvm::json::Value &Contexts, llvm::raw_ostream &OS,
+                     ASTNode *Partial);
+
+  void renderChild(const llvm::json::Value &Context, llvm::raw_ostream &OS);
+
+  const llvm::json::Value *findContext();
+
+  llvm::BumpPtrAllocator &Allocator;
+  StringMap<ASTNode *> &Partials;
+  StringMap<Lambda> &Lambdas;
+  StringMap<SectionLambda> &SectionLambdas;
+  DenseMap<char, std::string> &Escapes;
+  Type Ty;
+  size_t Indentation = 0;
+  std::string RawBody;
+  std::string Body;
+  ASTNode *Parent;
+  // TODO: switch implementation to SmallVector<T>
+  std::vector<ASTNode *> Children;
+  const Accessor AccessorValue;
+  const llvm::json::Value *ParentContext;
+};
+
+// A wrapper for arena allocator for ASTNodes
+ASTNode *createRootNode(void *Node, llvm::BumpPtrAllocator &Alloc,
+                        llvm::StringMap<ASTNode *> &Partials,
+                        llvm::StringMap<Lambda> &Lambdas,
+                        llvm::StringMap<SectionLambda> &SectionLambdas,
+                        llvm::DenseMap<char, std::string> &Escapes) {
+  return new (Node) ASTNode(Alloc, Partials, Lambdas, SectionLambdas, Escapes);
+}
+
+ASTNode *createNode(void *Node, ASTNode::Type T, Accessor A, ASTNode *Parent,
+                    llvm::BumpPtrAllocator &Alloc,
+                    llvm::StringMap<ASTNode *> &Partials,
+                    llvm::StringMap<Lambda> &Lambdas,
+                    llvm::StringMap<SectionLambda> &SectionLambdas,
+                    llvm::DenseMap<char, std::string> &Escapes) {
+  return new (Node) ASTNode(T, std::move(A), Parent, Alloc, Partials, Lambdas,
+                            SectionLambdas, Escapes);
+}
+
+ASTNode *createTextNode(void *Node, std::string Body, ASTNode *Parent,
+                        llvm::BumpPtrAllocator &Alloc,
+                        llvm::StringMap<ASTNode *> &Partials,
+                        llvm::StringMap<Lambda> &Lambdas,
+                        llvm::StringMap<SectionLambda> &SectionLambdas,
+                        llvm::DenseMap<char, std::string> &Escapes) {
+  return new (Node) ASTNode(std::move(Body), Parent, Alloc, Partials, Lambdas,
+                            SectionLambdas, Escapes);
+}
+
+// Function to check if there is meaningful text behind.
+// We determine if a token has meaningful text behind
+// if the right of previous token contains anything that is
+// not a newline.
+// For example:
+//  "Stuff {{#Section}}" (returns true)
+//   vs
+//  "{{#Section}} \n" (returns false)
+// We make an exception for when previous token is empty
+// and the current token is the second token.
+// For example:
+//  "{{#Section}}"
+bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) {
+  if (Idx == 0)
+    return true;
+
+  size_t PrevIdx = Idx - 1;
+  if (Tokens[PrevIdx].getType() != Token::Type::Text)
+    return true;
+
+  const Token &PrevToken = Tokens[PrevIdx];
+  StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v");
+  return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1);
+}
+
+// Function to check if there's no meaningful text ahead.
+// We determine if a token has text ahead if the left of previous
+// token does not start with a newline.
+bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) {
+  if (Idx >= Tokens.size() - 1)
+    return true;
+
+  size_t NextIdx = Idx + 1;
+  if (Tokens[NextIdx].getType() != Token::Type::Text)
+    return true;
+
+  const Token &NextToken = Tokens[NextIdx];
+  StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" ");
+  return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n");
+}
+
+bool requiresCleanUp(Token::Type T) {
+  // We must clean up all the tokens that could contain child nodes.
+  return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen ||
+         T == Token::Type::SectionClose || T == Token::Type::Comment ||
+         T == Token::Type::Partial;
+}
+
+// Adjust next token body if there is no text ahead.
+// For example:
+// The template string
+//  "{{! Comment }} \nLine 2"
+// would be considered as no text ahead and should be rendered as
+//  " Line 2"
+void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {
+  Token &NextToken = Tokens[Idx + 1];
+  StringRef NextTokenBody = NextToken.TokenBody;
+  // Cut off the leading newline which could be \n or \r\n.
+  if (NextTokenBody.starts_with("\r\n"))
+    NextToken.TokenBody = NextTokenBody.substr(2).str();
+  else if (NextTokenBody.starts_with("\n"))
+    NextToken.TokenBody = NextTokenBody.substr(1).str();
+}
+
+// Adjust previous token body if there no text behind.
+// For example:
+//  The template string
+//  " \t{{#section}}A{{/section}}"
+// would be considered as having no text ahead and would be render as
+//  "A"
+// The exception for this is partial tag which requires us to
+// keep track of the indentation once it's rendered.
+void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx,
+                      Token &CurrentToken, Token::Type CurrentType) {
+  Token &PrevToken = Tokens[Idx - 1];
+  StringRef PrevTokenBody = PrevToken.TokenBody;
+  StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v");
+  size_t Indentation = PrevTokenBody.size() - Unindented.size();
+  if (CurrentType != Token::Type::Partial)
+    PrevToken.TokenBody = std::move(Unindented.str());
+  CurrentToken.setIndentation(Indentation);
+}
+
+// Simple tokenizer that splits the template into tokens.
+// The mustache spec allows {{{ }}} to unescape variables,
+// but we don't support that here. An unescape variable
+// is represented only by {{& variable}}.
+SmallVector<Token> tokenize(StringRef Template) {
+  SmallVector<Token> Tokens;
+  StringLiteral Open("{{");
+  StringLiteral Close("}}");
+  size_t Start = 0;
+  size_t DelimiterStart = Template.find(Open);
+  if (DelimiterStart == StringRef::npos) {
+    Tokens.emplace_back(Template.str());
+    return Tokens;
+  }
+  while (DelimiterStart != StringRef::npos) {
+    if (DelimiterStart != Start)
+      Tokens.emplace_back(Template.substr(Start, DelimiterStart - Start).str());
+    size_t DelimiterEnd = Template.find(Close, DelimiterStart);
+    if (DelimiterEnd == StringRef::npos)
+      break;
+
+    // Extract the Interpolated variable without delimiters.
+    size_t InterpolatedStart = DelimiterStart + Open.size();
+    size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size();
+    std::string Interpolated =
+        Template.substr(InterpolatedStart, InterpolatedEnd).str();
+    std::string RawBody = Open.str() + Interpolated + Close.str();
+    Tokens.emplace_back(RawBody, Interpolated, Interpolated[0]);
+    Start = DelimiterEnd + Close.size();
+    DelimiterStart = Template.find(Open, Start);
+  }
+
+  if (Start < Template.size())
+    Tokens.emplace_back(Template.substr(Start).str());
+
+  // Fix up white spaces for:
+  //   - open sections
+  //   - inverted sections
+  //   - close sections
+  //   - comments
+  //
+  // This loop attempts to find standalone tokens and tries to trim out
+  // the surrounding whitespace.
+  // For example:
+  // if you have the template string
+  //  {{#section}} \n Example \n{{/section}}
+  // The output should would be
+  // For example:
+  //  \n Example \n
+  size_t LastIdx = Tokens.size() - 1;
+  for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {
+    Token &CurrentToken = Tokens[Idx];
+    Token::Type CurrentType = CurrentToken.getType();
+    // Check if token type requires cleanup.
+    bool RequiresCleanUp = requiresCleanUp(CurrentType);
+
+    if (!RequiresCleanUp)
+      continue;
+
+    // We adjust the token body if there's no text behind or ahead.
+    // A token is considered to have no text ahead if the right of the previous
+    // token is a newline followed by spaces.
+    // A token is considered to have no text behind if the left of the next
+    // token is spaces followed by a newline.
+    // eg.
+    //  "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3"
+    bool HasTextBehind = hasTextBehind(Idx, Tokens);
+    bool HasTextAhead = hasTextAhead(Idx, Tokens);
+
+    if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0))
+      stripTokenAhead(Tokens, Idx);
+
+    if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx))
+      stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);
+  }
+  return Tokens;
+}
+
+// Custom stream to escape strings.
+class EscapeStringStream : public raw_ostream {
+public:
+  explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
+                              DenseMap<char, std::string> &Escape)
+      : Escape(Escape), WrappedStream(WrappedStream) {
+    SetUnbuffered();
+  }
+
+protected:
+  void write_impl(const char *Ptr, size_t Size) override {
+    llvm::StringRef Data(Ptr, Size);
+    for (char C : Data) {
+      auto It = Escape.find(C);
+      if (It != Escape.end())
+        WrappedStream << It->getSecond();
+      else
+        WrappedStream << C;
+    }
+  }
+
+  uint64_t current_pos() const override { return WrappedStream.tell(); }
+
+private:
+  DenseMap<char, std::string> &Escape;
+  llvm::raw_ostream &WrappedStream;
+};
+
+// Custom stream to add indentation used to for rendering partials.
+class AddIndentationStringStream : public raw_ostream {
+public:
+  explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream,
+                                      size_t Indentation)
+      : Indentation(Indentation), WrappedStream(WrappedStream) {
+    SetUnbuffered();
+  }
+
+protected:
+  void write_impl(const char *Ptr, size_t Size) override {
+    llvm::StringRef Data(Ptr, Size);
+    SmallString<0> Indent;
+    Indent.resize(Indentation, ' ');
+    for (char C : Data) {
+      WrappedStream << C;
+      if (C == '\n')
+        WrappedStream << Indent;
+    }
+  }
+
+  uint64_t current_pos() const override { return WrappedStream.tell(); }
+
+private:
+  size_t Indentation;
+  llvm::raw_ostream &WrappedStream;
+};
+
+class Parser {
+public:
+  Parser(StringRef TemplateStr, BumpPtrAllocator &Allocator)
+      : ASTAllocator(Allocator), TemplateStr(TemplateStr) {}
+
+  ASTNode *parse(llvm::BumpPtrAllocator &RenderAlloc,
+                 llvm::StringMap<ASTNode *> &Partials,
+                 llvm::StringMap<Lambda> &Lambdas,
+                 llvm::StringMap<SectionLambda> &SectionLambdas,
+                 llvm::DenseMap<char, std::string> &Escapes);
+
+private:
+  void parseMustache(ASTNode *Parent, llvm::BumpPtrAllocator &Alloc,
+                     llvm::StringMap<ASTNode *> &Partials,
+                     llvm::StringMap<Lambda> &Lambdas,
+                     llvm::StringMap<SectionLambda> &SectionLambdas,
+                     llvm::DenseMap<char, std::string> &Escapes);
+
+  BumpPtrAllocator &ASTAllocator;
+  SmallVector<Token> Tokens;
+  size_t CurrentPtr;
+  StringRef TemplateStr;
+};
+
+ASTNode *Parser::parse(llvm::BumpPtrAllocator &RenderAlloc,
+                       llvm::StringMap<ASTNode *> &Partials,
+                       llvm::StringMap<Lambda> &Lambdas,
+                       llvm::StringMap<SectionLambda> &SectionLambdas,
+                       llvm::DenseMap<char, std::string> &Escapes) {
+  Tokens = tokenize(TemplateStr);
+  CurrentPtr = 0;
+  void *Root = ASTAllocator.Allocate(sizeof(ASTNode), alignof(ASTNode));
+  ASTNode *RootNode = createRootNode(Root, RenderAlloc, Partials, Lambdas,
+                                     SectionLambdas, Escapes);
+  parseMustache(RootNode, RenderAlloc, Partials, Lambdas, SectionLambdas,
+                Escapes);
+  return RootNode;
+}
+
+void Parser::parseMustache(ASTNode *Parent, llvm::BumpPtrAllocator &Alloc,
+                           llvm::StringMap<ASTNode *> &Partials,
+                           llvm::StringMap<Lambda> &Lambdas,
+                           llvm::StringMap<SectionLambda> &SectionLambdas,
+                           llvm::DenseMap<char, std::string> &Escapes) {
+
+  while (CurrentPtr < Tokens.size()) {
+    Token CurrentToken = Tokens[CurrentPtr];
+    CurrentPtr++;
+    Accessor A = CurrentToken.getAccessor();
+    ASTNode *CurrentNode;
+    void *Node = ASTAllocator.Allocate(sizeof(ASTNode), alignof(ASTNode));
+
+    switch (CurrentToken.getType()) {
+    case Token::Type::Text: {
+      CurrentNode =
+          createTextNode(Node, std::move(CurrentToken.TokenBody), Parent, Alloc,
+                         Partials, Lambdas, SectionLambdas, Escapes);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::Variable: {
+      CurrentNode =
+          createNode(Node, ASTNode::Variable, std::move(A), Parent, Alloc,
+                     Partials, Lambdas, SectionLambdas, Escapes);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::UnescapeVariable: {
+      CurrentNode =
+          createNode(Node, ASTNode::UnescapeVariable, std::move(A), Parent,
+                     Alloc, Partials, Lambdas, SectionLambdas, Escapes);
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::Partial: {
+      CurrentNode =
+          createNode(Node, ASTNode::Partial, std::move(A), Parent, Alloc,
+                     Partials, Lambdas, SectionLambdas, Escapes);
+      CurrentNode->setIndentation(CurrentToken.getIndentation());
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::SectionOpen: {
+      CurrentNode = createNode(Node, ASTNode::Section, A, Parent, Alloc,
+                               Partials, Lambdas, SectionLambdas, Escapes);
+      size_t Start = CurrentPtr;
+      parseMustache(CurrentNode, Alloc, Partials, Lambdas, SectionLambdas,
+                    Escapes);
+      const size_t End = CurrentPtr - 1;
+      std::string RawBody;
+      for (std::size_t I = Start; I < End; I++)
+        RawBody += Tokens[I].RawBody;
+      CurrentNode->setRawBody(std::move(RawBody));
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::InvertSectionOpen: {
+      CurrentNode = createNode(Node, ASTNode::InvertSection, A, Parent, Alloc,
+                               Partials, Lambdas, SectionLambdas, Escapes);
+      size_t Start = CurrentPtr;
+      parseMustache(CurrentNode, Alloc, Partials, Lambdas, SectionLambdas,
+                    Escapes);
+      const size_t End = CurrentPtr - 1;
+      std::string RawBody;
+      for (size_t Idx = Start; Idx < End; Idx++)
+        RawBody += Tokens[Idx].RawBody;
+      CurrentNode->setRawBody(std::move(RawBody));
+      Parent->addChild(CurrentNode);
+      break;
+    }
+    case Token::Type::Comment:
+      break;
+    case Token::Type::SectionClose:
+      return;
+    }
+  }
+}
+void toMustacheString(const json::Value &Data, raw_ostream &OS) {
+  switch (Data.kind()) {
+  case json::Value::Null:
+    return;
+  case json::Value::Number: {
+    auto Num = *Data.getAsNumber();
+    std::ostringstream SS;
+    SS << Num;
+    OS << SS.str();
+    return;
+  }
+  case json::Value::String: {
+    auto Str = *Data.getAsString();
+    OS << Str.str();
+    return;
+  }
+
+  case json::Value::Array: {
+    auto Arr = *Data.getAsArray();
+    if (Arr.empty())
+      return;
+    [[fallthrough]];
+  }
+  case json::Value::Object:
+  case json::Value::Boolean: {
+    llvm::json::OStream JOS(OS, 2);
+    JOS.value(Data);
+    break;
+  }
+  }
+}
+
+void ASTNode::render(const json::Value &Data, raw_ostream &OS) {
+  ParentContext = &Data;
+  const json::Value *ContextPtr = Ty == Root ? ParentContext : findContext();
+  const json::Value &Context = ContextPtr ? *ContextPtr : nullptr;
+
+  switch (Ty) {
+  case Root:
+    renderChild(Data, OS);
+    return;
+  case Text:
+    OS << Body;
+    return;
+  case Partial: {
+    auto Partial = Partials.find(AccessorValue[0]);
+    if (Partial != Partials.end())
+      renderPartial(Data, OS, Partial->getValue());
+    return;
+  }
+  case Variable: {
+    auto Lambda = Lambdas.find(AccessorValue[0]);
+    if (Lambda != Lambdas.end())
+      renderLambdas(Data, OS, Lambda->getValue());
+    else {
+      EscapeStringStream ES(OS, Escapes);
+      toMustacheString(Context, ES);
+    }
+    return;
+  }
+  case UnescapeVariable: {
+    auto Lambda = Lambdas.find(AccessorValue[0]);
+    if (Lambda != Lambdas.end())
+      renderLambdas(Data, OS, Lambda->getValue());
+    else
+      toMustacheString(Context, OS);
+    return;
+  }
+  case Section: {
+    // Sections are not rendered if the context is falsey.
+    auto SectionLambda = SectionLambdas.find(AccessorValue[0]);
+    bool IsLambda = SectionLambda != SectionLambdas.end();
+    if (isFalsey(Context) && !IsLambda)
+      return;
+
+    if (IsLambda) {
+      renderSectionLambdas(Data, OS, SectionLambda->getValue());
+      return;
+    }
+
+    if (Context.getAsArray()) {
+      const json::Array *Arr = Context.getAsArray();
+      for (const json::Value &V : *Arr)
+        renderChild(V, OS);
+      return;
+    }
+    renderChild(Context, OS);
+    return;
+  }
+  case InvertSection: {
+    bool IsLambda =
+        SectionLambdas.find(AccessorValue[0]) != SectionLambdas.end();
+    if (!isFalsey(Context) || IsLambda)
+      return;
+    renderChild(Context, OS);
+    return;
+  }
+  }
+  llvm_unreachable("Invalid ASTNode type");
+}
+
+const json::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, then we traverse the parent nodes to find the context until we
+  // reach the root node or the context is found.
+  if (AccessorValue.empty())
+    return nullptr;
+  if (AccessorValue[0] == ".")
+    return ParentContext;
+
+  const json::Object *CurrentContext = ParentContext->getAsObject();
+  StringRef CurrentAccessor = AccessorValue[0];
+  ASTNode *CurrentParent = Parent;
+
+  while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
+    if (CurrentParent->Ty != Root) {
+      CurrentContext = CurrentParent->ParentContext->getAsObject();
+      CurrentParent = CurrentParent->Parent;
+      continue;
+    }
+    return nullptr;
+  }
+  const json::Value *Context = nullptr;
+  for (auto [Idx, Acc] : enumerate(AccessorValue)) {
+    const json::Value *CurrentValue = CurrentContext->get(Acc);
+    if (!CurrentValue)
+      return nullptr;
+    if (Idx < AccessorValue.size() - 1) {
+      CurrentContext = CurrentValue->getAsObject();
+      if (!CurrentContext)
+        return nullptr;
+    } else
+      Context = CurrentValue;
+  }
+  return Context;
+}
+
+void ASTNode::renderChild(const json::Value &Contexts, llvm::raw_ostream &OS) {
+  for (ASTNode *Child : Children)
+    Child->render(Contexts, OS);
+}
+
+void ASTNode::renderPartial(const json::Value &Contexts, llvm::raw_ostream &OS,
+                            ASTNode *Partial) {
+  AddIndentationStringStream IS(OS, Indentation);
+  Partial->render(Contexts, IS);
+}
+
+void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS,
+                            Lambda &L) {
+  json::Value LambdaResult = L();
+  std::string LambdaStr;
+  raw_string_ostream Output(LambdaStr);
+  toMustacheString(LambdaResult, Output);
+  Parser P = Parser(LambdaStr, Allocator);
+  ASTNode *LambdaNode =
+      P.parse(Allocator, Partials, Lambdas, SectionLambdas, Escapes);
+
+  EscapeStringStream ES(OS, Escapes);
+  if (Ty == Variable) {
+    LambdaNode->render(Contexts, ES);
+    return;
+  }
+  LambdaNode->render(Contexts, OS);
+}
+
+void ASTNode::renderSectionLambdas(const json::Value &Contexts,
+                                   llvm::raw_ostream &OS, SectionLambda &L) {
+  json::Value Return = L(RawBody);
+  if (isFalsey(Return))
+    return;
+  std::string LambdaStr;
+  raw_string_ostream Output(LambdaStr);
+  toMustacheString(Return, Output);
+  Parser P = Parser(LambdaStr, Allocator);
+  ASTNode *LambdaNode =
+      P.parse(Allocator, Partials, Lambdas, SectionLambdas, Escapes);
+  LambdaNode->render(Contexts, OS);
+  return;
+}
+
+void Template::render(const json::Value &Data, llvm::raw_ostream &OS) {
+  Tree->render(Data, OS);
+  RenderAllocator.Reset();
+}
+
+void Template::registerPartial(std::string Name, std::string Partial) {
+  Parser P = Parser(Partial, AstAllocator);
+  ASTNode *PartialTree =
+      P.parse(RenderAllocator, Partials, Lambdas, SectionLambdas, Escapes);
+  Partials.insert(std::make_pair(Name, PartialTree));
+}
+
+void Template::registerLambda(std::string Name, Lambda L) { Lambdas[Name] = L; }
+
+void Template::registerLambda(std::string Name, SectionLambda L) {
+  SectionLambdas[Name] = L;
+}
+
+void Template::overrideEscapeCharacters(DenseMap<char, std::string> E) {
+  Escapes = std::move(E);
+}
+
+Template::Template(StringRef TemplateStr) {
+  Parser P = Parser(TemplateStr, AstAllocator);
+  Tree = P.parse(RenderAllocator, Partials, Lambdas, SectionLambdas, Escapes);
+  // The default behavior is to escape html entities.
+  DenseMap<char, std::string> HtmlEntities = {{'&', "&"},
+                                              {'<', "<"},
+                                              {'>', ">"},
+                                              {'"', """},
+                                              {'\'', "'"}};
+  overrideEscapeCharacters(HtmlEntities);
+}
+
+Template::Template(Template &&Other) noexcept
+    : Partials(std::move(Other.Partials)), Lambdas(std::move(Other.Lambdas)),
+      SectionLambdas(std::move(Other.SectionLambdas)),
+      Escapes(std::move(Other.Escapes)),
+      AstAllocator(std::move(Other.AstAllocator)),
+      RenderAllocator(std::move(Other.RenderAllocator)), Tree(Other.Tree) {
+  Other.Tree = nullptr;
+}
+
+Template &Template::operator=(Template &&Other) noexcept {
+  if (this != &Other) {
+    Partials = std::move(Other.Partials);
+    Lambdas = std::move(Other.Lambdas);
+    SectionLambdas = std::move(Other.SectionLambdas);
+    Escapes = std::move(Other.Escapes);
+    AstAllocator = std::move(Other.AstAllocator);
+    RenderAllocator = std::move(Other.RenderAllocator);
+    Tree = Other.Tree;
+    Other.Tree = nullptr;
+  }
+  return *this;
+}
+} // namespace llvm::mustache

diff  --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index 6de8165826442..6c4e7cb689b20 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -61,6 +61,7 @@ add_llvm_unittest(SupportTests
   MemoryBufferRefTest.cpp
   MemoryBufferTest.cpp
   MemoryTest.cpp
+  MustacheTest.cpp      
   ModRefTest.cpp
   NativeFormatTests.cpp
   OptimizedStructLayoutTest.cpp

diff  --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
new file mode 100644
index 0000000000000..6ab3d4b01bc1b
--- /dev/null
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -0,0 +1,1226 @@
+//===- 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 "llvm/Support/raw_ostream.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("Hello from {Mustache}!\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("Hello, {{subject}}!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{template}}: {{planet}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("{{planet}}: Earth", Out);
+}
+
+TEST(MustacheInterpolation, HTMLEscaping) {
+  // Interpolated tag output should not be re-interpolated.
+  Value D = Object{
+      {"forbidden", "& \" < >"},
+  };
+  auto T = Template("These characters should be HTML escaped: {{forbidden}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("These characters should not be HTML escaped: {{&forbidden}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{mph}} miles an hour!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("85 miles an hour!", Out);
+}
+
+TEST(MustacheInterpolation, AmpersandIntegerInterpolation) {
+  // Integers should interpolate seamlessly.
+  Value D = Object{{"mph", 85}};
+  auto T = Template("{{&mph}} miles an hour!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{power}} jiggawatts!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("1.21 jiggawatts!", Out);
+}
+
+TEST(MustacheInterpolation, BasicNullInterpolation) {
+  // Nulls should interpolate as the empty string.
+  Value D = Object{{"cannot", nullptr}};
+  auto T = Template("I ({{cannot}}) be seen!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("I () be seen!", Out);
+}
+
+TEST(MustacheInterpolation, AmpersandNullInterpolation) {
+  // Nulls should interpolate as the empty string.
+  Value D = Object{{"cannot", nullptr}};
+  auto T = Template("I ({{&cannot}}) be seen!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("I () be seen!", Out);
+}
+
+TEST(MustacheInterpolation, BasicContextMissInterpolation) {
+  // Failed context lookups should default to empty strings.
+  Value D = Object{};
+  auto T = Template("I ({{cannot}}) be seen!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{person.name}} == {{#person}}{{name}}{{/person}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{&person.name}} == {{#person}}{{&name}}{{/person}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Joe == Joe", Out);
+}
+
+TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
+  // Dotted names should be functional to any level of nesting.
+  Value D = Object{
+      {"a",
+       Object{{"b",
+               Object{{"c",
+                       Object{{"d",
+                               Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}};
+  auto T = Template("{{a.b.c.d.e.name}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("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("{{a.b.c}} == ");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{a.b.c.name}} == ");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{#a}}{{b.c.d.e.name}}{{/a}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("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("{{#a}}{{b.c}}{{/a}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{a.b}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{a.b}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("d", Out);
+}
+
+TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) {
+  // Unadorned tags should interpolate content into the template.
+  Value D = "world";
+  auto T = Template("Hello, {{.}}!\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Hello, world!\n", Out);
+}
+
+TEST(MustacheInterpolation, ImplicitIteratorsAmersand) {
+  // Basic interpolation should be HTML escaped.
+  Value D = "& \" < >";
+  auto T = Template("These characters should not be HTML escaped: {{&.}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out);
+}
+
+TEST(MustacheInterpolation, ImplicitIteratorsInteger) {
+  // Integers should interpolate seamlessly.
+  Value D = 85;
+  auto T = Template("{{.}} miles an hour!\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("85 miles an hour!\n", Out);
+}
+
+TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) {
+  // Interpolation should not alter surrounding whitespace.
+  Value D = Object{{"string", "---"}};
+  auto T = Template("| {{string}} |");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| --- |", Out);
+}
+
+TEST(MustacheInterpolation, AmersandSurroundingWhitespace) {
+  // Interpolation should not alter surrounding whitespace.
+  Value D = Object{{"string", "---"}};
+  auto T = Template("| {{&string}} |");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| --- |", Out);
+}
+
+TEST(MustacheInterpolation, StandaloneInterpolationWithWhitespace) {
+  // Standalone interpolation should not alter surrounding whitespace.
+  Value D = Object{{"string", "---"}};
+  auto T = Template("  {{string}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("  ---\n", Out);
+}
+
+TEST(MustacheInterpolation, StandaloneAmpersandWithWhitespace) {
+  // Standalone interpolation should not alter surrounding whitespace.
+  Value D = Object{{"string", "---"}};
+  auto T = Template("  {{&string}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("  ---\n", Out);
+}
+
+TEST(MustacheInterpolation, InterpolationWithPadding) {
+  // Superfluous in-tag whitespace should be ignored.
+  Value D = Object{{"string", "---"}};
+  auto T = Template("|{{ string }}|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|---|", Out);
+}
+
+TEST(MustacheInterpolation, AmpersandWithPadding) {
+  // Superfluous in-tag whitespace should be ignored.
+  Value D = Object{{"string", "---"}};
+  auto T = Template("|{{& string }}|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|---|", Out);
+}
+
+TEST(MustacheInterpolation, InterpolationWithPaddingAndNewlines) {
+  // Superfluous in-tag whitespace should be ignored.
+  Value D = Object{{"string", "---"}};
+  auto T = Template("|{{ string \n\n\n }}|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|---|", Out);
+}
+
+TEST(MustacheSections, Truthy) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("{{#boolean}}This should be rendered.{{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("This should be rendered.", Out);
+}
+
+TEST(MustacheSections, Falsey) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("{{#boolean}}This should not be rendered.{{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInterpolation, IsFalseyNull) {
+  // Mustache-free templates should render as-is.
+  Value D = Object{{"boolean", nullptr}};
+  auto T = Template("Hello, {{#boolean}}World{{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Hello, ", Out);
+}
+
+TEST(MustacheInterpolation, IsFalseyArray) {
+  // Mustache-free templates should render as-is.
+  Value D = Object{{"boolean", Array()}};
+  auto T = Template("Hello, {{#boolean}}World{{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Hello, ", Out);
+}
+
+TEST(MustacheInterpolation, IsFalseyObject) {
+  // Mustache-free templates should render as-is.
+  Value D = Object{{"boolean", Object{}}};
+  auto T = Template("Hello, {{#boolean}}World{{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Hello, World", Out);
+}
+
+TEST(MustacheInterpolation, DoubleRendering) {
+  // Mustache-free templates should render as-is.
+  Value D1 = Object{{"subject", "World"}};
+  auto T = Template("Hello, {{subject}}!");
+  std::string Out1;
+  raw_string_ostream OS1(Out1);
+  T.render(D1, OS1);
+  EXPECT_EQ("Hello, World!", Out1);
+  std::string Out2;
+  raw_string_ostream OS2(Out2);
+  Value D2 = Object{{"subject", "New World"}};
+  T.render(D2, OS2);
+  EXPECT_EQ("Hello, New World!", Out2);
+}
+
+TEST(MustacheSections, NullIsFalsey) {
+  Value D = Object{{"null", nullptr}};
+  auto T = Template("{{#null}}This should not be rendered.{{/null}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheSections, Context) {
+  Value D = Object{{"context", Object{{"name", "Joe"}}}};
+  auto T = Template("{{#context}}Hi {{name}}.{{/context}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("foo, bar, baz", Out);
+}
+
+TEST(MustacheSections, VariableTest) {
+  Value D = Object{{"foo", "bar"}};
+  auto T = Template("{{#foo}}{{.}} is {{foo}}{{/foo}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{#tops}}"
+                    "{{#middles}}"
+                    "{{tname.lower}}{{mname}}."
+                    "{{#bottoms}}"
+                    "{{tname.upper}}{{mname}}{{bname}}."
+                    "{{/bottoms}}"
+                    "{{/middles}}"
+                    "{{/tops}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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(
+      "{{#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");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{#list}}{{item}}{{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("123", Out);
+}
+
+TEST(MustacheSections, EmptyList) {
+  Value D = Object{{"list", Array{}}};
+  auto T = Template("{{#list}}Yay lists!{{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheSections, Doubled) {
+  Value D = Object{{"bool", true}, {"two", "second"}};
+  auto T = Template("{{#bool}}\n* first\n{{/bool}}\n* "
+                    "{{two}}\n{{#bool}}\n* third\n{{/bool}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("* first\n* second\n* third\n", Out);
+}
+
+TEST(MustacheSections, NestedTruthy) {
+  Value D = Object{{"bool", true}};
+  auto T = Template("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| A B C D E |", Out);
+}
+
+TEST(MustacheSections, NestedFalsey) {
+  Value D = Object{{"bool", false}};
+  auto T = Template("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| A  E |", Out);
+}
+
+TEST(MustacheSections, ContextMisses) {
+  Value D = Object{};
+  auto T = Template("[{{#missing}}Found key 'missing'!{{/missing}}]");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("[]", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorString) {
+  Value D = Object{{"list", Array{"a", "b", "c", "d", "e"}}};
+  auto T = Template("{{#list}}({{.}}){{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("(a)(b)(c)(d)(e)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorInteger) {
+  Value D = Object{{"list", Array{1, 2, 3, 4, 5}}};
+  auto T = Template("{{#list}}({{.}}){{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("{{#list}}({{#.}}{{.}}{{/.}}){{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("(123)(abc)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorHTMLEscaping) {
+  Value D = Object{{"list", Array{"&", "\"", "<", ">"}}};
+  auto T = Template("{{#list}}({{.}}){{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("(&)(")(<)(>)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorAmpersand) {
+  Value D = Object{{"list", Array{"&", "\"", "<", ">"}}};
+  auto T = Template("{{#list}}({{&.}}){{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("(&)(\")(<)(>)", Out);
+}
+
+TEST(MustacheSections, ImplicitIteratorRootLevel) {
+  Value D = Array{Object{{"value", "a"}}, Object{{"value", "b"}}};
+  auto T = Template("{{#.}}({{value}}){{/.}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("(a)(b)", Out);
+}
+
+TEST(MustacheSections, DottedNamesTruthy) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}};
+  auto T = Template("{{#a.b.c}}Here{{/a.b.c}} == Here");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Here == Here", Out);
+}
+
+TEST(MustacheSections, DottedNamesFalsey) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}};
+  auto T = Template("{{#a.b.c}}Here{{/a.b.c}} == ");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheSections, DottedNamesBrokenChains) {
+  Value D = Object{{"a", Object{}}};
+  auto T = Template("{{#a.b.c}}Here{{/a.b.c}} == ");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheSections, SurroundingWhitespace) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template(" | {{#boolean}}\t|\t{{/boolean}} | \n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" | \t|\t | \n", Out);
+}
+
+TEST(MustacheSections, InternalWhitespace) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template(
+      " | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" |  \n  | \n", Out);
+}
+
+TEST(MustacheSections, IndentedInlineSections) {
+  Value D = Object{{"boolean", true}};
+  auto T =
+      Template(" {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" YES\n GOOD\n", Out);
+}
+
+TEST(MustacheSections, StandaloneLines) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("| This Is\n{{#boolean}}\n|\n{{/boolean}}\n| A Line\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheSections, IndentedStandaloneLines) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("| This Is\n  {{#boolean}}\n|\n  {{/boolean}}\n| A Line\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheSections, StandaloneLineEndings) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|\r\n|", Out);
+}
+
+TEST(MustacheSections, StandaloneWithoutPreviousLine) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("  {{#boolean}}\n#{{/boolean}}\n/");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("#\n/", Out);
+}
+
+TEST(MustacheSections, StandaloneWithoutNewline) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("#{{#boolean}}\n/\n  {{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("#\n/\n", Out);
+}
+
+TEST(MustacheSections, Padding) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("|{{# boolean }}={{/ boolean }}|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|=|", Out);
+}
+
+TEST(MustacheInvertedSections, Falsey) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("{{^boolean}}This should be rendered.{{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("This should be rendered.", Out);
+}
+
+TEST(MustacheInvertedSections, Truthy) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("{{^boolean}}This should not be rendered.{{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInvertedSections, NullIsFalsey) {
+  Value D = Object{{"null", nullptr}};
+  auto T = Template("{{^null}}This should be rendered.{{/null}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("This should be rendered.", Out);
+}
+
+TEST(MustacheInvertedSections, Context) {
+  Value D = Object{{"context", Object{{"name", "Joe"}}}};
+  auto T = Template("{{^context}}Hi {{name}}.{{/context}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInvertedSections, List) {
+  Value D = Object{
+      {"list", Array{Object{{"n", 1}}, Object{{"n", 2}}, Object{{"n", 3}}}}};
+  auto T = Template("{{^list}}{{n}}{{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustacheInvertedSections, EmptyList) {
+  Value D = Object{{"list", Array{}}};
+  auto T = Template("{{^list}}Yay lists!{{/list}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Yay lists!", Out);
+}
+
+TEST(MustacheInvertedSections, Doubled) {
+  Value D = Object{{"bool", false}, {"two", "second"}};
+  auto T = Template("{{^bool}}\n* first\n{{/bool}}\n* "
+                    "{{two}}\n{{^bool}}\n* third\n{{/bool}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("* first\n* second\n* third\n", Out);
+}
+
+TEST(MustacheInvertedSections, NestedFalsey) {
+  Value D = Object{{"bool", false}};
+  auto T = Template("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| A B C D E |", Out);
+}
+
+TEST(MustacheInvertedSections, NestedTruthy) {
+  Value D = Object{{"bool", true}};
+  auto T = Template("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| A  E |", Out);
+}
+
+TEST(MustacheInvertedSections, ContextMisses) {
+  Value D = Object{};
+  auto T = Template("[{{^missing}}Cannot find key 'missing'!{{/missing}}]");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("[Cannot find key 'missing'!]", Out);
+}
+
+TEST(MustacheInvertedSections, DottedNamesTruthy) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}};
+  auto T = Template("{{^a.b.c}}Not Here{{/a.b.c}} == ");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" == ", Out);
+}
+
+TEST(MustacheInvertedSections, DottedNamesFalsey) {
+  Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}};
+  auto T = Template("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Not Here == Not Here", Out);
+}
+
+TEST(MustacheInvertedSections, DottedNamesBrokenChains) {
+  Value D = Object{{"a", Object{}}};
+  auto T = Template("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Not Here == Not Here", Out);
+}
+
+TEST(MustacheInvertedSections, SurroundingWhitespace) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template(" | {{^boolean}}\t|\t{{/boolean}} | \n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" | \t|\t | \n", Out);
+}
+
+TEST(MustacheInvertedSections, InternalWhitespace) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template(
+      " | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" |  \n  | \n", Out);
+}
+
+TEST(MustacheInvertedSections, IndentedInlineSections) {
+  Value D = Object{{"boolean", false}};
+  auto T =
+      Template(" {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ(" NO\n WAY\n", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneLines) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("| This Is\n{{^boolean}}\n|\n{{/boolean}}\n| A Line\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneIndentedLines) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("| This Is\n  {{^boolean}}\n|\n  {{/boolean}}\n| A Line\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| This Is\n|\n| A Line\n", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneLineEndings) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|\r\n|", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneWithoutPreviousLine) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("  {{^boolean}}\n^{{/boolean}}\n/");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("^\n/", Out);
+}
+
+TEST(MustacheInvertedSections, StandaloneWithoutNewline) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("^{{^boolean}}\n/\n  {{/boolean}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("^\n/\n", Out);
+}
+
+TEST(MustacheInvertedSections, Padding) {
+  Value D = Object{{"boolean", false}};
+  auto T = Template("|{{^ boolean }}={{/ boolean }}|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|=|", Out);
+}
+
+TEST(MustachePartials, BasicBehavior) {
+  Value D = Object{};
+  auto T = Template("{{>text}}");
+  T.registerPartial("text", "from partial");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("from partial", Out);
+}
+
+TEST(MustachePartials, FailedLookup) {
+  Value D = Object{};
+  auto T = Template("{{>text}}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("", Out);
+}
+
+TEST(MustachePartials, Context) {
+  Value D = Object{{"text", "content"}};
+  auto T = Template("{{>partial}}");
+  T.registerPartial("partial", "*{{text}}*");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("*content*", Out);
+}
+
+TEST(MustachePartials, Recursion) {
+  Value D =
+      Object{{"content", "X"},
+             {"nodes", Array{Object{{"content", "Y"}, {"nodes", Array{}}}}}};
+  auto T = Template("{{>node}}");
+  T.registerPartial("node", "{{content}}({{#nodes}}{{>node}}{{/nodes}})");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("X(Y())", Out);
+}
+
+TEST(MustachePartials, Nested) {
+  Value D = Object{{"a", "hello"}, {"b", "world"}};
+  auto T = Template("{{>outer}}");
+  T.registerPartial("outer", "*{{a}} {{>inner}}*");
+  T.registerPartial("inner", "{{b}}!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("*hello world!*", Out);
+}
+
+TEST(MustachePartials, SurroundingWhitespace) {
+  Value D = Object{};
+  auto T = Template("| {{>partial}} |");
+  T.registerPartial("partial", "\t|\t");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("| \t|\t |", Out);
+}
+
+TEST(MustachePartials, InlineIndentation) {
+  Value D = Object{{"data", "|"}};
+  auto T = Template("  {{data}}  {{> partial}}\n");
+  T.registerPartial("partial", "<\n<");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("  |  <\n<\n", Out);
+}
+
+TEST(MustachePartials, PaddingWhitespace) {
+  Value D = Object{{"boolean", true}};
+  auto T = Template("|{{> partial }}|");
+  T.registerPartial("partial", "[]");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|[]|", Out);
+}
+
+TEST(MustacheLambdas, BasicInterpolation) {
+  Value D = Object{};
+  auto T = Template("Hello, {{lambda}}!");
+  Lambda L = []() -> llvm::json::Value { return "World"; };
+  T.registerLambda("lambda", L);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Hello, World!", Out);
+}
+
+TEST(MustacheLambdas, InterpolationExpansion) {
+  Value D = Object{{"planet", "World"}};
+  auto T = Template("Hello, {{lambda}}!");
+  Lambda L = []() -> llvm::json::Value { return "{{planet}}"; };
+  T.registerLambda("lambda", L);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Hello, World!", Out);
+}
+
+TEST(MustacheLambdas, BasicMultipleCalls) {
+  Value D = Object{};
+  auto T = Template("{{lambda}} == {{lambda}} == {{lambda}}");
+  int I = 0;
+  Lambda L = [&I]() -> llvm::json::Value {
+    I += 1;
+    return I;
+  };
+  T.registerLambda("lambda", L);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("1 == 2 == 3", Out);
+}
+
+TEST(MustacheLambdas, Escaping) {
+  Value D = Object{};
+  auto T = Template("<{{lambda}}{{&lambda}}");
+  Lambda L = []() -> llvm::json::Value { return ">"; };
+  T.registerLambda("lambda", L);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("<>>", Out);
+}
+
+TEST(MustacheLambdas, Sections) {
+  Value D = Object{};
+  auto T = Template("<{{#lambda}}{{x}}{{/lambda}}>");
+  SectionLambda L = [](StringRef Text) -> llvm::json::Value {
+    if (Text == "{{x}}") {
+      return "yes";
+    }
+    return "no";
+  };
+  T.registerLambda("lambda", L);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("<yes>", Out);
+}
+
+TEST(MustacheLambdas, SectionExpansion) {
+  Value D = Object{
+      {"planet", "Earth"},
+  };
+  auto T = Template("<{{#lambda}}-{{/lambda}}>");
+  SectionLambda L = [](StringRef Text) -> llvm::json::Value {
+    SmallString<128> Result;
+    Result += Text;
+    Result += "{{planet}}";
+    Result += Text;
+    return Result;
+  };
+  T.registerLambda("lambda", L);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("<-Earth->", Out);
+}
+
+TEST(MustacheLambdas, SectionsMultipleCalls) {
+  Value D = Object{};
+  auto T = Template("{{#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);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("__FILE__ != __LINE__", Out);
+}
+
+TEST(MustacheLambdas, InvertedSections) {
+  Value D = Object{{"static", "static"}};
+  auto T = Template("<{{^lambda}}{{static}}{{/lambda}}>");
+  SectionLambda L = [](StringRef Text) -> llvm::json::Value { return false; };
+  T.registerLambda("lambda", L);
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("<>", Out);
+}
+
+TEST(MustacheComments, Inline) {
+  // Comment blocks should be removed from the template.
+  Value D = {};
+  auto T = Template("12345{{! Comment Block! }}67890");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("1234567890", Out);
+}
+
+TEST(MustacheComments, Multiline) {
+  // Multiline comments should be permitted.
+  Value D = {};
+  auto T =
+      Template("12345{{!\n  This is a\n  multi-line comment...\n}}67890\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("1234567890\n", Out);
+}
+
+TEST(MustacheComments, Standalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T = Template("Begin.\n{{! Comment Block! }}\nEnd.\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, IndentedStandalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T = Template("Begin.\n  {{! Indented Comment Block! }}\nEnd.\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, StandaloneLineEndings) {
+  // "\r\n" should be considered a newline for standalone tags.
+  Value D = {};
+  auto T = Template("|\r\n{{! Standalone Comment }}\r\n|");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("|\r\n|", Out);
+}
+
+TEST(MustacheComments, StandaloneWithoutPreviousLine) {
+  // Standalone tags should not require a newline to precede them.
+  Value D = {};
+  auto T = Template("  {{! I'm Still Standalone }}\n!");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("!", Out);
+}
+
+TEST(MustacheComments, StandaloneWithoutNewline) {
+  // Standalone tags should not require a newline to follow them.
+  Value D = {};
+  auto T = Template("!\n  {{! I'm Still Standalone }}");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("!\n", Out);
+}
+
+TEST(MustacheComments, MultilineStandalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T = Template("Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, IndentedMultilineStandalone) {
+  // All standalone comment lines should be removed.
+  Value D = {};
+  auto T =
+      Template("Begin.\n  {{!\n    Something's going on here...\n  }}\nEnd.\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("Begin.\nEnd.\n", Out);
+}
+
+TEST(MustacheComments, IndentedInline) {
+  // Inline comments should not strip whitespace.
+  Value D = {};
+  auto T = Template("  12 {{! 34 }}\n");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("  12 \n", Out);
+}
+
+TEST(MustacheComments, SurroundingWhitespace) {
+  // Comment removal should preserve surrounding whitespace.
+  Value D = {};
+  auto T = Template("12345 {{! Comment Block! }} 67890");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  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("comments never show: >{{! comment }}<");
+  std::string Out;
+  raw_string_ostream OS(Out);
+  T.render(D, OS);
+  EXPECT_EQ("comments never show: ><", Out);
+}


        


More information about the llvm-commits mailing list