[clang-tools-extra] [llvm] [WIP] add a mustache backend (PR #108653)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 8 13:46:00 PDT 2024
https://github.com/PeterChou1 updated https://github.com/llvm/llvm-project/pull/108653
>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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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/45] [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);
>From bb3b1acfedc0776e922c36ddec1ed38b7d9af5cf Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Thu, 12 Sep 2024 06:02:49 -0400
Subject: [PATCH 22/45] [llvm][support] address mustache comments
---
llvm/include/llvm/Support/Mustache.h | 59 ++---
llvm/lib/Support/Mustache.cpp | 304 +++++++++++++-----------
llvm/unittests/Support/MustacheTest.cpp | 275 ++++++++++-----------
3 files changed, 316 insertions(+), 322 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 6243e29f52282d..607e33b4ec2c9b 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -26,24 +26,30 @@
// - Parents
// - Dynamic Names
//
+// The Template class is container class outputs the Mustache template string
+// and is main class for users. It stores all the lambdas and the ASTNode Tree.
+// When the Template is instantiated it calls tokenize the Template String into
+// the Token class and 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::createTemplate("Hello, {{name}}!");
+// auto Template = Template("Hello, {{name}}!");
// Value Data = {{"name", "World"}};
// StringRef Rendered = Template.render(Data);
// // Rendered == "Hello, World!"
//
// // Creating a template with a partial and rendering it
-// auto Template = Template::createTemplate("{{>partial}}");
+// auto Template = Template("{{>partial}}");
// Template.registerPartial("partial", "Hello, {{name}}!");
// Value Data = {{"name", "World"}};
// StringRef Rendered = Template.render(Data);
// // Rendered == "Hello, World!"
//
// // Creating a template with a lambda and rendering it
-// auto Template = Template::createTemplate("{{#lambda}}Hello,
-// {{name}}!{{/lambda}}");
+// auto Template = Template("{{#lambda}}Hello, {{name}}!{{/lambda}}");
// Template.registerLambda("lambda", []() { return true; });
// Value Data = {{"name", "World"}};
// StringRef Rendered = Template.render(Data);
@@ -56,13 +62,14 @@
#include "Error.h"
#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/Allocator.h"
#include "llvm/Support/JSON.h"
#include <vector>
namespace llvm {
namespace mustache {
-using Accessor = std::vector<SmallString<128>>;
+using Accessor = SmallVector<SmallString<128>>;
using Lambda = std::function<llvm::json::Value()>;
using SectionLambda = std::function<llvm::json::Value(StringRef)>;
@@ -114,19 +121,17 @@ 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) {};
+ ASTNode(StringRef Body, 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)
+ ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- LocalContext(nullptr) {};
+ LocalContext(nullptr){};
- void addChild(std::shared_ptr<ASTNode> Child) {
- Children.emplace_back(Child);
- };
+ void addChild(ASTNode *Child) { Children.emplace_back(Child); };
SmallString<128> getBody() const { return Body; };
@@ -134,24 +139,20 @@ class ASTNode {
void setRawBody(StringRef NewBody) { RawBody = NewBody; };
- 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);
+ SmallString<128> render(llvm::json::Value Data,
+ llvm::BumpPtrAllocator &Allocator,
+ DenseMap<StringRef, ASTNode *> &Partials,
+ DenseMap<StringRef, Lambda> &Lambdas,
+ DenseMap<StringRef, SectionLambda> &SectionLambdas,
+ DenseMap<char, StringRef> &Escapes);
private:
llvm::json::Value findContext();
Type T;
SmallString<128> RawBody;
SmallString<128> Body;
- std::weak_ptr<ASTNode> Parent;
- std::vector<std::shared_ptr<ASTNode>> Children;
+ ASTNode *Parent;
+ std::vector<ASTNode *> Children;
const Accessor Accessor;
llvm::json::Value LocalContext;
};
@@ -160,7 +161,7 @@ class ASTNode {
// and Lambdas that are registered with it.
class Template {
public:
- static Template createTemplate(StringRef TemplateStr);
+ Template(StringRef TemplateStr);
SmallString<128> render(llvm::json::Value Data);
@@ -176,12 +177,12 @@ class Template {
void registerEscape(DenseMap<char, StringRef> Escapes);
private:
- Template(std::shared_ptr<ASTNode> Tree) : Tree(Tree) {};
- DenseMap<StringRef, std::shared_ptr<ASTNode>> Partials;
+ DenseMap<StringRef, ASTNode *> Partials;
DenseMap<StringRef, Lambda> Lambdas;
DenseMap<StringRef, SectionLambda> SectionLambdas;
DenseMap<char, StringRef> Escapes;
- std::shared_ptr<ASTNode> Tree;
+ llvm::BumpPtrAllocator Allocator;
+ ASTNode *Tree;
};
} // namespace mustache
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 7372a248d209bc..dd6e5ebb19209c 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -25,31 +25,29 @@ SmallString<128> escapeString(StringRef Input,
return EscapedString;
}
-std::vector<SmallString<128>> split(StringRef Str, char Delimiter) {
- std::vector<SmallString<128>> Tokens;
+Accessor split(StringRef Str, char Delimiter) {
+ Accessor Tokens;
if (Str == ".") {
- Tokens.push_back(Str);
+ Tokens.emplace_back(Str);
return Tokens;
}
StringRef Ref(Str);
while (!Ref.empty()) {
- llvm::StringRef Part;
+ StringRef Part;
std::tie(Part, Ref) = Ref.split(Delimiter);
- Tokens.push_back(Part.trim());
+ Tokens.emplace_back(Part.trim());
}
return Tokens;
}
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);
+ StringRef AccessorStr =
+ TokenType == Type::Variable ? InnerBody : InnerBody.substr(1);
Accessor = split(AccessorStr.trim(), '.');
}
@@ -76,86 +74,106 @@ Token::Type Token::getTokenType(char Identifier) {
}
}
-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("}}");
+// Function to check if there's no meaningful text behind
+bool noTextBehind(size_t Idx, const SmallVector<Token, 0> &Tokens) {
+ if (Idx == 0 || Tokens[Idx - 1].getType() != Token::Type::Text)
+ return false;
+ const Token &PrevToken = Tokens[Idx - 1];
+ StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
+ return TokenBody.ends_with("\n") || TokenBody.ends_with("\r\n") ||
+ (TokenBody.empty() && Idx == 1);
+}
+// Function to check if there's no meaningful text ahead
+bool noTextAhead(size_t Idx, const SmallVector<Token, 0> &Tokens) {
+ if (Idx >= Tokens.size() - 1 ||
+ Tokens[Idx + 1].getType() != Token::Type::Text)
+ return false;
+
+ const Token &NextToken = Tokens[Idx + 1];
+ StringRef TokenBody = NextToken.getRawBody().ltrim(" ");
+ return TokenBody.starts_with("\r\n") || TokenBody.starts_with("\n");
+}
+
+// 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}}
+SmallVector<Token, 0> tokenize(StringRef Template) {
+ SmallVector<Token, 0> Tokens;
+ StringRef Open("{{");
+ StringRef Close("}}");
std::size_t Start = 0;
std::size_t DelimiterStart = Template.find(Open);
if (DelimiterStart == StringRef::npos) {
- Tokens.push_back(Token(Template));
+ Tokens.emplace_back(Template);
return Tokens;
}
while (DelimiterStart != StringRef::npos) {
if (DelimiterStart != Start) {
- Token TextToken = Token(Template.substr(Start, DelimiterStart - Start));
- Tokens.push_back(TextToken);
+ Tokens.emplace_back(Template.substr(Start, DelimiterStart - Start));
}
- std::size_t DelimiterEnd = Template.find(Close, DelimiterStart);
+ size_t DelimiterEnd = Template.find(Close, DelimiterStart);
if (DelimiterEnd == StringRef::npos) {
break;
}
+ // Extract the Interpolated variable without {{ and }}
+ size_t InterpolatedStart = DelimiterStart + Open.size();
+ size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size();
SmallString<128> Interpolated =
- Template.substr(DelimiterStart + Open.size(),
- DelimiterEnd - DelimiterStart - Close.size());
+ Template.substr(InterpolatedStart, InterpolatedEnd);
SmallString<128> RawBody;
RawBody += Open;
RawBody += Interpolated;
RawBody += Close;
- Tokens.push_back(Token(RawBody, Interpolated, Interpolated[0]));
+ Tokens.emplace_back(RawBody, Interpolated, Interpolated[0]);
Start = DelimiterEnd + Close.size();
DelimiterStart = Template.find(Open, Start);
}
if (Start < Template.size())
- Tokens.push_back(Token(Template.substr(Start)));
+ Tokens.emplace_back(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.getRawBody().rtrim(" \t\v\t");
- if (TokenBody.ends_with("\n") || TokenBody.ends_with("\r\n") ||
- (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"))
- NoTextAhead = true;
- }
+ // This loop attempts to find standalone tokens and tries to trim out
+ // the whitespace around them
+ // for example:
+ // if you have the template string
+ // "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3"
+ // The output would be
+ // "Line 1\n Line 2\n Line 3"
+ size_t LastIdx = Tokens.size() - 1;
+ for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {
+ Token::Type CurrentType = Tokens[Idx].getType();
+ // Check if token type requires cleanup
+ bool RequiresCleanUp = (CurrentType == Token::Type::SectionOpen ||
+ CurrentType == Token::Type::InvertSectionOpen ||
+ CurrentType == Token::Type::SectionClose ||
+ CurrentType == Token::Type::Comment ||
+ CurrentType == Token::Type::Partial);
+
+ if (!RequiresCleanUp)
+ continue;
- if ((NoTextBehind && NoTextAhead) || (NoTextAhead && I == 0)) {
- Token &NextToken = Tokens[I + 1];
+ bool NoTextBehind = noTextBehind(Idx, Tokens);
+ bool NoTextAhead = noTextAhead(Idx, Tokens);
+
+ // Adjust next token body if no text ahead
+ if ((NoTextBehind && NoTextAhead) || (NoTextAhead && Idx == 0)) {
+ Token &NextToken = Tokens[Idx + 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 (NextTokenBody.starts_with("\n")) {
NextToken.setTokenBody(NextTokenBody.substr(1));
+ }
}
- if ((NoTextBehind && NoTextAhead) ||
- (NoTextBehind && I == Tokens.size() - 1)) {
- Token &PrevToken = Tokens[I - 1];
+ // Adjust previous token body if no text behind
+ if ((NoTextBehind && NoTextAhead) || (NoTextBehind && Idx == LastIdx)) {
+ Token &PrevToken = Tokens[Idx - 1];
StringRef PrevTokenBody = PrevToken.getTokenBody();
PrevToken.setTokenBody(PrevTokenBody.rtrim(" \t\v\t"));
}
@@ -165,84 +183,87 @@ std::vector<Token> tokenize(StringRef Template) {
class Parser {
public:
- Parser(StringRef TemplateStr) : TemplateStr(TemplateStr) {}
+ Parser(StringRef TemplateStr, BumpPtrAllocator &Allocator)
+ : Allocator(Allocator), TemplateStr(TemplateStr) {}
- std::shared_ptr<ASTNode> parse();
+ ASTNode *parse();
private:
- void parseMustache(std::shared_ptr<ASTNode> Parent);
+ void parseMustache(ASTNode *Parent);
- std::vector<Token> Tokens;
- std::size_t CurrentPtr;
+ BumpPtrAllocator &Allocator;
+ SmallVector<Token, 0> Tokens;
+ size_t CurrentPtr;
StringRef TemplateStr;
};
-std::shared_ptr<ASTNode> Parser::parse() {
+ASTNode *Parser::parse() {
Tokens = tokenize(TemplateStr);
CurrentPtr = 0;
- std::shared_ptr<ASTNode> Root = std::make_shared<ASTNode>();
- parseMustache(Root);
- return Root;
+ void *Root = Allocator.Allocate(sizeof(ASTNode), alignof(ASTNode));
+ ASTNode *RootNode = new (Root) ASTNode();
+ parseMustache(RootNode);
+ return RootNode;
}
-void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
+void Parser::parseMustache(ASTNode *Parent) {
while (CurrentPtr < Tokens.size()) {
Token CurrentToken = Tokens[CurrentPtr];
CurrentPtr++;
Accessor A = CurrentToken.getAccessor();
- std::shared_ptr<ASTNode> CurrentNode;
+ ASTNode *CurrentNode;
+ void *Node = Allocator.Allocate(sizeof(ASTNode), alignof(ASTNode));
switch (CurrentToken.getType()) {
case Token::Type::Text: {
- CurrentNode =
- std::make_shared<ASTNode>(CurrentToken.getTokenBody(), Parent);
+ CurrentNode = new (Node) ASTNode(CurrentToken.getTokenBody(), Parent);
Parent->addChild(CurrentNode);
break;
}
case Token::Type::Variable: {
- CurrentNode = std::make_shared<ASTNode>(ASTNode::Variable, A, Parent);
+ CurrentNode = new (Node) ASTNode(ASTNode::Variable, A, Parent);
Parent->addChild(CurrentNode);
break;
}
case Token::Type::UnescapeVariable: {
- CurrentNode =
- std::make_shared<ASTNode>(ASTNode::UnescapeVariable, A, Parent);
+ CurrentNode = new (Node) ASTNode(ASTNode::UnescapeVariable, A, Parent);
Parent->addChild(CurrentNode);
break;
}
case Token::Type::Partial: {
- CurrentNode = std::make_shared<ASTNode>(ASTNode::Partial, A, Parent);
+ CurrentNode = new (Node) ASTNode(ASTNode::Partial, A, Parent);
Parent->addChild(CurrentNode);
break;
}
case Token::Type::SectionOpen: {
- CurrentNode = std::make_shared<ASTNode>(ASTNode::Section, A, Parent);
- std::size_t Start = CurrentPtr;
+ CurrentNode = new (Node) ASTNode(ASTNode::Section, A, Parent);
+ size_t Start = CurrentPtr;
parseMustache(CurrentNode);
- std::size_t End = CurrentPtr;
+ size_t End = CurrentPtr;
SmallString<128> RawBody;
- if (Start + 1 < End - 1)
+ 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)
+ } 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;
+ CurrentNode = new (Node) ASTNode(ASTNode::InvertSection, A, Parent);
+ size_t Start = CurrentPtr;
parseMustache(CurrentNode);
- std::size_t End = CurrentPtr;
+ 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)
+ if (Start + 1 < End - 1) {
+ for (size_t Idx = Start + 1; Idx < End - 1; Idx++)
+ RawBody += Tokens[Idx].getRawBody();
+ } else if (Start + 1 == End - 1) {
RawBody = Tokens[Start].getRawBody();
+ }
CurrentNode->setRawBody(RawBody);
Parent->addChild(CurrentNode);
break;
@@ -255,27 +276,15 @@ void Parser::parseMustache(std::shared_ptr<ASTNode> Parent) {
}
}
-Template Template::createTemplate(StringRef 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
- DenseMap<char, StringRef> HtmlEntities = {{'&', "&"},
- {'<', "<"},
- {'>', ">"},
- {'"', """},
- {'\'', "'"}};
- T.registerEscape(HtmlEntities);
- return T;
-}
-
SmallString<128> Template::render(Value Data) {
- return Tree->render(Data, Partials, Lambdas, SectionLambdas, Escapes);
+ BumpPtrAllocator LocalAllocator;
+ return Tree->render(Data, LocalAllocator, Partials, Lambdas, SectionLambdas,
+ Escapes);
}
void Template::registerPartial(StringRef Name, StringRef Partial) {
- Parser P = Parser(Partial);
- std::shared_ptr<ASTNode> PartialTree = P.parse();
+ Parser P = Parser(Partial, Allocator);
+ ASTNode *PartialTree = P.parse();
Partials[Name] = PartialTree;
}
@@ -287,6 +296,18 @@ void Template::registerLambda(StringRef Name, SectionLambda L) {
void Template::registerEscape(DenseMap<char, StringRef> E) { Escapes = E; }
+Template::Template(StringRef TemplateStr) {
+ Parser P = Parser(TemplateStr, Allocator);
+ Tree = P.parse();
+ // the default behaviour is to escape html entities
+ DenseMap<char, StringRef> HtmlEntities = {{'&', "&"},
+ {'<', "<"},
+ {'>', ">"},
+ {'"', """},
+ {'\'', "'"}};
+ registerEscape(HtmlEntities);
+}
+
SmallString<128> printJson(Value &Data) {
SmallString<128> Result;
@@ -299,7 +320,7 @@ SmallString<128> printJson(Value &Data) {
Result += Data.getAsString()->str();
return Result;
}
- return llvm::formatv("{0:2}", Data);
+ return formatv("{0:2}", Data);
}
bool isFalsey(Value &V) {
@@ -309,8 +330,8 @@ bool isFalsey(Value &V) {
}
SmallString<128>
-ASTNode::render(Value Data,
- DenseMap<StringRef, std::shared_ptr<ASTNode>> &Partials,
+ASTNode::render(Value Data, BumpPtrAllocator &Allocator,
+ DenseMap<StringRef, ASTNode *> &Partials,
DenseMap<StringRef, Lambda> &Lambdas,
DenseMap<StringRef, SectionLambda> &SectionLambdas,
DenseMap<char, StringRef> &Escapes) {
@@ -319,18 +340,18 @@ ASTNode::render(Value Data,
SmallString<128> Result;
switch (T) {
case Root: {
- for (std::shared_ptr<ASTNode> Child : Children)
- Result +=
- Child->render(Context, Partials, Lambdas, SectionLambdas, Escapes);
+ for (ASTNode *Child : Children)
+ Result += Child->render(Context, Allocator, Partials, Lambdas,
+ SectionLambdas, Escapes);
return Result;
}
case Text:
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);
+ ASTNode *Partial = Partials[Accessor[0]];
+ Result += Partial->render(Data, Allocator, Partials, Lambdas,
+ SectionLambdas, Escapes);
return Result;
}
return Result;
@@ -340,10 +361,10 @@ ASTNode::render(Value Data,
Lambda &L = Lambdas[Accessor[0]];
Value LambdaResult = L();
StringRef LambdaStr = printJson(LambdaResult);
- Parser P = Parser(LambdaStr);
- std::shared_ptr<ASTNode> LambdaNode = P.parse();
- SmallString<128> RenderStr =
- LambdaNode->render(Data, Partials, Lambdas, SectionLambdas, Escapes);
+ Parser P = Parser(LambdaStr, Allocator);
+ ASTNode *LambdaNode = P.parse();
+ SmallString<128> RenderStr = LambdaNode->render(
+ Data, Allocator, Partials, Lambdas, SectionLambdas, Escapes);
return escapeString(RenderStr, Escapes);
}
return escapeString(printJson(Context), Escapes);
@@ -353,56 +374,51 @@ ASTNode::render(Value Data,
Lambda &L = Lambdas[Accessor[0]];
Value LambdaResult = L();
StringRef LambdaStr = printJson(LambdaResult);
- Parser P = Parser(LambdaStr);
- std::shared_ptr<ASTNode> LambdaNode = P.parse();
+ Parser P = Parser(LambdaStr, Allocator);
+ ASTNode *LambdaNode = P.parse();
DenseMap<char, StringRef> EmptyEscapes;
- return LambdaNode->render(Data, Partials, Lambdas, SectionLambdas,
- EmptyEscapes);
+ return LambdaNode->render(Data, Allocator, Partials, Lambdas,
+ SectionLambdas, EmptyEscapes);
}
return printJson(Context);
}
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.str());
- std::shared_ptr<ASTNode> LambdaNode = P.parse();
- return LambdaNode->render(Data, Partials, Lambdas, SectionLambdas,
- Escapes);
+ Parser P = Parser(LambdaStr.str(), Allocator);
+ ASTNode *LambdaNode = P.parse();
+ return LambdaNode->render(Data, Allocator, 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);
+ for (ASTNode *Child : Children)
+ Result += Child->render(V, Allocator, Partials, Lambdas,
+ SectionLambdas, Escapes);
}
return Result;
}
-
- for (std::shared_ptr<ASTNode> Child : Children)
- Result +=
- Child->render(Context, Partials, Lambdas, SectionLambdas, Escapes);
-
+ for (ASTNode *Child : Children)
+ Result += Child->render(Context, Allocator, 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);
+ for (ASTNode *Child : Children)
+ Result += Child->render(Context, Allocator, Partials, Lambdas,
+ SectionLambdas, Escapes);
return Result;
}
}
@@ -420,13 +436,13 @@ Value ASTNode::findContext() {
if (Accessor[0] == ".")
return LocalContext;
json::Object *CurrentContext = LocalContext.getAsObject();
- SmallString<128> CurrentAccessor = Accessor[0];
- std::weak_ptr<ASTNode> CurrentParent = Parent;
+ StringRef CurrentAccessor = Accessor[0];
+ ASTNode *CurrentParent = Parent;
while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
- if (auto Ptr = CurrentParent.lock()) {
- CurrentContext = Ptr->LocalContext.getAsObject();
- CurrentParent = Ptr->Parent;
+ if (CurrentParent->T != Root) {
+ CurrentContext = CurrentParent->LocalContext.getAsObject();
+ CurrentParent = CurrentParent->Parent;
continue;
}
return nullptr;
diff --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
index 3f3f04a9b55b13..d120736be40856 100644
--- a/llvm/unittests/Support/MustacheTest.cpp
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -22,7 +22,7 @@ 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 T = Template("Hello from {Mustache}!\n");
auto Out = T.render(D);
EXPECT_EQ("Hello from {Mustache}!\n", Out);
}
@@ -30,7 +30,7 @@ TEST(MustacheInterpolation, NoInterpolation) {
TEST(MustacheInterpolation, BasicInterpolation) {
// Unadorned tags should interpolate content into the template.
Value D = Object{{"subject", "World"}};
- auto T = Template::createTemplate("Hello, {{subject}}!");
+ auto T = Template("Hello, {{subject}}!");
auto Out = T.render(D);
EXPECT_EQ("Hello, World!", Out);
}
@@ -38,7 +38,7 @@ TEST(MustacheInterpolation, BasicInterpolation) {
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 T = Template("{{template}}: {{planet}}");
auto Out = T.render(D);
EXPECT_EQ("{{planet}}: Earth", Out);
}
@@ -48,8 +48,7 @@ TEST(MustacheInterpolation, HTMLEscaping) {
Value D = Object{
{"forbidden", "& \" < >"},
};
- auto T = Template::createTemplate(
- "These characters should be HTML escaped: {{forbidden}}\n");
+ auto T = Template("These characters should be HTML escaped: {{forbidden}}\n");
auto Out = T.render(D);
EXPECT_EQ("These characters should be HTML escaped: & " < >\n",
Out);
@@ -60,8 +59,8 @@ TEST(MustacheInterpolation, Ampersand) {
Value D = Object{
{"forbidden", "& \" < >"},
};
- auto T = Template::createTemplate(
- "These characters should not be HTML escaped: {{&forbidden}}\n");
+ auto T =
+ Template("These characters should not be HTML escaped: {{&forbidden}}\n");
auto Out = T.render(D);
EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out);
}
@@ -69,7 +68,7 @@ TEST(MustacheInterpolation, Ampersand) {
TEST(MustacheInterpolation, BasicIntegerInterpolation) {
// Integers should interpolate seamlessly.
Value D = Object{{"mph", 85}};
- auto T = Template::createTemplate("{{mph}} miles an hour!");
+ auto T = Template("{{mph}} miles an hour!");
auto Out = T.render(D);
EXPECT_EQ("85 miles an hour!", Out);
}
@@ -77,7 +76,7 @@ TEST(MustacheInterpolation, BasicIntegerInterpolation) {
TEST(MustacheInterpolation, AmpersandIntegerInterpolation) {
// Integers should interpolate seamlessly.
Value D = Object{{"mph", 85}};
- auto T = Template::createTemplate("{{&mph}} miles an hour!");
+ auto T = Template("{{&mph}} miles an hour!");
auto Out = T.render(D);
EXPECT_EQ("85 miles an hour!", Out);
}
@@ -85,7 +84,7 @@ TEST(MustacheInterpolation, AmpersandIntegerInterpolation) {
TEST(MustacheInterpolation, BasicDecimalInterpolation) {
// Decimals should interpolate seamlessly with proper significance.
Value D = Object{{"power", 1.21}};
- auto T = Template::createTemplate("{{power}} jiggawatts!");
+ auto T = Template("{{power}} jiggawatts!");
auto Out = T.render(D);
EXPECT_EQ("1.21 jiggawatts!", Out);
}
@@ -93,7 +92,7 @@ TEST(MustacheInterpolation, BasicDecimalInterpolation) {
TEST(MustacheInterpolation, BasicNullInterpolation) {
// Nulls should interpolate as the empty string.
Value D = Object{{"cannot", nullptr}};
- auto T = Template::createTemplate("I ({{cannot}}) be seen!");
+ auto T = Template("I ({{cannot}}) be seen!");
auto Out = T.render(D);
EXPECT_EQ("I () be seen!", Out);
}
@@ -101,7 +100,7 @@ TEST(MustacheInterpolation, BasicNullInterpolation) {
TEST(MustacheInterpolation, AmpersandNullInterpolation) {
// Nulls should interpolate as the empty string.
Value D = Object{{"cannot", nullptr}};
- auto T = Template::createTemplate("I ({{&cannot}}) be seen!");
+ auto T = Template("I ({{&cannot}}) be seen!");
auto Out = T.render(D);
EXPECT_EQ("I () be seen!", Out);
}
@@ -109,7 +108,7 @@ TEST(MustacheInterpolation, AmpersandNullInterpolation) {
TEST(MustacheInterpolation, BasicContextMissInterpolation) {
// Failed context lookups should default to empty strings.
Value D = Object{};
- auto T = Template::createTemplate("I ({{cannot}}) be seen!");
+ auto T = Template("I ({{cannot}}) be seen!");
auto Out = T.render(D);
EXPECT_EQ("I () be seen!", Out);
}
@@ -117,8 +116,7 @@ TEST(MustacheInterpolation, BasicContextMissInterpolation) {
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 T = Template("{{person.name}} == {{#person}}{{name}}{{/person}}");
auto Out = T.render(D);
EXPECT_EQ("Joe == Joe", Out);
}
@@ -126,8 +124,7 @@ TEST(MustacheInterpolation, DottedNamesBasicInterpolation) {
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 T = Template("{{&person.name}} == {{#person}}{{&name}}{{/person}}");
auto Out = T.render(D);
EXPECT_EQ("Joe == Joe", Out);
}
@@ -140,7 +137,7 @@ TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
Object{{"c",
Object{{"d",
Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}};
- auto T = Template::createTemplate("{{a.b.c.d.e.name}}");
+ auto T = Template("{{a.b.c.d.e.name}}");
auto Out = T.render(D);
EXPECT_EQ("Phil", Out);
}
@@ -148,7 +145,7 @@ TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
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 T = Template("{{a.b.c}} == ");
auto Out = T.render(D);
EXPECT_EQ(" == ", Out);
}
@@ -157,7 +154,7 @@ 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 T = Template("{{a.b.c.name}} == ");
auto Out = T.render(D);
EXPECT_EQ(" == ", Out);
}
@@ -172,7 +169,7 @@ 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}}");
+ auto T = Template("{{#a}}{{b.c.d.e.name}}{{/a}}");
auto Out = T.render(D);
EXPECT_EQ("Phil", Out);
}
@@ -181,7 +178,7 @@ 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 T = Template("{{#a}}{{b.c}}{{/a}}");
auto Out = T.render(D);
EXPECT_EQ("", Out);
}
@@ -189,7 +186,7 @@ TEST(MustacheInterpolation, DottedNamesContextPrecedence) {
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 T = Template("{{a.b}}");
auto Out = T.render(D);
EXPECT_EQ("", Out);
}
@@ -197,7 +194,7 @@ TEST(MustacheInterpolation, DottedNamesAreNotSingleKeys) {
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 T = Template("{{a.b}}");
auto Out = T.render(D);
EXPECT_EQ("d", Out);
}
@@ -205,7 +202,7 @@ TEST(MustacheInterpolation, DottedNamesNoMasking) {
TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) {
// Unadorned tags should interpolate content into the template.
Value D = "world";
- auto T = Template::createTemplate("Hello, {{.}}!\n");
+ auto T = Template("Hello, {{.}}!\n");
auto Out = T.render(D);
EXPECT_EQ("Hello, world!\n", Out);
}
@@ -213,8 +210,7 @@ TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) {
TEST(MustacheInterpolation, ImplicitIteratorsAmersand) {
// Basic interpolation should be HTML escaped.
Value D = "& \" < >";
- auto T = Template::createTemplate(
- "These characters should not be HTML escaped: {{&.}}\n");
+ auto T = Template("These characters should not be HTML escaped: {{&.}}\n");
auto Out = T.render(D);
EXPECT_EQ("These characters should not be HTML escaped: & \" < >\n", Out);
}
@@ -222,7 +218,7 @@ TEST(MustacheInterpolation, ImplicitIteratorsAmersand) {
TEST(MustacheInterpolation, ImplicitIteratorsInteger) {
// Integers should interpolate seamlessly.
Value D = 85;
- auto T = Template::createTemplate("{{.}} miles an hour!\n");
+ auto T = Template("{{.}} miles an hour!\n");
auto Out = T.render(D);
EXPECT_EQ("85 miles an hour!\n", Out);
}
@@ -230,7 +226,7 @@ TEST(MustacheInterpolation, ImplicitIteratorsInteger) {
TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) {
// Interpolation should not alter surrounding whitespace.
Value D = Object{{"string", "---"}};
- auto T = Template::createTemplate("| {{string}} |");
+ auto T = Template("| {{string}} |");
auto Out = T.render(D);
EXPECT_EQ("| --- |", Out);
}
@@ -238,7 +234,7 @@ TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) {
TEST(MustacheInterpolation, AmersandSurroundingWhitespace) {
// Interpolation should not alter surrounding whitespace.
Value D = Object{{"string", "---"}};
- auto T = Template::createTemplate("| {{&string}} |");
+ auto T = Template("| {{&string}} |");
auto Out = T.render(D);
EXPECT_EQ("| --- |", Out);
}
@@ -246,7 +242,7 @@ TEST(MustacheInterpolation, AmersandSurroundingWhitespace) {
TEST(MustacheInterpolation, StandaloneInterpolationWithWhitespace) {
// Standalone interpolation should not alter surrounding whitespace.
Value D = Object{{"string", "---"}};
- auto T = Template::createTemplate(" {{string}}\n");
+ auto T = Template(" {{string}}\n");
auto Out = T.render(D);
EXPECT_EQ(" ---\n", Out);
}
@@ -254,7 +250,7 @@ TEST(MustacheInterpolation, StandaloneInterpolationWithWhitespace) {
TEST(MustacheInterpolation, StandaloneAmpersandWithWhitespace) {
// Standalone interpolation should not alter surrounding whitespace.
Value D = Object{{"string", "---"}};
- auto T = Template::createTemplate(" {{&string}}\n");
+ auto T = Template(" {{&string}}\n");
auto Out = T.render(D);
EXPECT_EQ(" ---\n", Out);
}
@@ -262,7 +258,7 @@ TEST(MustacheInterpolation, StandaloneAmpersandWithWhitespace) {
TEST(MustacheInterpolation, InterpolationWithPadding) {
// Superfluous in-tag whitespace should be ignored.
Value D = Object{{"string", "---"}};
- auto T = Template::createTemplate("|{{ string }}|");
+ auto T = Template("|{{ string }}|");
auto Out = T.render(D);
EXPECT_EQ("|---|", Out);
}
@@ -270,7 +266,7 @@ TEST(MustacheInterpolation, InterpolationWithPadding) {
TEST(MustacheInterpolation, AmpersandWithPadding) {
// Superfluous in-tag whitespace should be ignored.
Value D = Object{{"string", "---"}};
- auto T = Template::createTemplate("|{{& string }}|");
+ auto T = Template("|{{& string }}|");
auto Out = T.render(D);
EXPECT_EQ("|---|", Out);
}
@@ -278,38 +274,35 @@ TEST(MustacheInterpolation, AmpersandWithPadding) {
TEST(MustacheInterpolation, InterpolationWithPaddingAndNewlines) {
// Superfluous in-tag whitespace should be ignored.
Value D = Object{{"string", "---"}};
- auto T = Template::createTemplate("|{{ string \n\n\n }}|");
+ auto T = Template("|{{ 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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#context}}Hi {{name}}.{{/context}}");
auto Out = T.render(D);
EXPECT_EQ("Hi Joe.", Out);
}
@@ -319,14 +312,14 @@ TEST(MustacheSections, ParentContexts) {
{"b", "wrong"},
{"sec", Object{{"b", "bar"}}},
{"c", Object{{"d", "baz"}}}};
- auto T = Template::createTemplate("{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}");
+ auto T = Template("{{#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 T = Template("{{#foo}}{{.}} is {{foo}}{{/foo}}");
auto Out = T.render(D);
EXPECT_EQ("bar is bar", Out);
}
@@ -340,14 +333,14 @@ TEST(MustacheSections, ListContexts) {
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 T = Template("{{#tops}}"
+ "{{#middles}}"
+ "{{tname.lower}}{{mname}}."
+ "{{#bottoms}}"
+ "{{tname.upper}}{{mname}}{{bname}}."
+ "{{/bottoms}}"
+ "{{/middles}}"
+ "{{/tops}}");
auto Out = T.render(D);
EXPECT_EQ("a1.A1x.A1y.", Out);
}
@@ -357,7 +350,7 @@ TEST(MustacheSections, DeeplyNestedContexts) {
{"a", Object{{"one", 1}}},
{"b", Object{{"two", 2}}},
{"c", Object{{"three", 3}, {"d", Object{{"four", 4}, {"five", 5}}}}}};
- auto T = Template::createTemplate(
+ 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}"
@@ -376,123 +369,120 @@ TEST(MustacheSections, DeeplyNestedContexts) {
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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#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 T = Template("| 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 T = Template("| 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 T = Template("[{{#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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#.}}({{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 T = Template("{{#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 T = Template("{{#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 T = Template("{{#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 T = Template(" | {{#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(
+ auto T = Template(
" | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
auto Out = T.render(D);
EXPECT_EQ(" | \n | \n", Out);
@@ -500,83 +490,78 @@ TEST(MustacheSections, InternalWhitespace) {
TEST(MustacheSections, IndentedInlineSections) {
Value D = Object{{"boolean", true}};
- auto T = Template::createTemplate(
- " {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n");
+ auto T =
+ Template(" {{#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 T = Template("| 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 T = Template("| 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 T = Template("|\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 T = Template(" {{#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 T = Template("#{{#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 T = Template("|{{# 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 T = Template("{{^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 T = Template("{{^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 T = Template("{{^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 T = Template("{{^context}}Hi {{name}}.{{/context}}");
auto Out = T.render(D);
EXPECT_EQ("", Out);
}
@@ -584,81 +569,78 @@ TEST(MustacheInvertedSections, Context) {
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 T = Template("{{^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 T = Template("{{^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 T = Template("{{^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 T = Template("| 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 T = Template("| 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 T = Template("[{{^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 T = Template("{{^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 T = Template("{{^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 T = Template("{{^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 T = Template(" | {{^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(
+ auto T = Template(
" | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
auto Out = T.render(D);
EXPECT_EQ(" | \n | \n", Out);
@@ -666,59 +648,57 @@ TEST(MustacheInvertedSections, InternalWhitespace) {
TEST(MustacheInvertedSections, IndentedInlineSections) {
Value D = Object{{"boolean", false}};
- auto T = Template::createTemplate(
- " {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n");
+ auto T =
+ Template(" {{^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 T = Template("| 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 T = Template("| 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 T = Template("|\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 T = Template(" {{^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 T = Template("^{{^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 T = Template("|{{^ boolean }}={{/ boolean }}|");
auto Out = T.render(D);
EXPECT_EQ("|=|", Out);
}
TEST(MustachePartials, BasicBehavior) {
Value D = Object{};
- auto T = Template::createTemplate("{{>text}}");
+ auto T = Template("{{>text}}");
T.registerPartial("text", "from partial");
auto Out = T.render(D);
EXPECT_EQ("from partial", Out);
@@ -726,14 +706,14 @@ TEST(MustachePartials, BasicBehavior) {
TEST(MustachePartials, FailedLookup) {
Value D = Object{};
- auto T = Template::createTemplate("{{>text}}");
+ auto T = Template("{{>text}}");
auto Out = T.render(D);
EXPECT_EQ("", Out);
}
TEST(MustachePartials, Context) {
Value D = Object{{"text", "content"}};
- auto T = Template::createTemplate("{{>partial}}");
+ auto T = Template("{{>partial}}");
T.registerPartial("partial", "*{{text}}*");
auto Out = T.render(D);
EXPECT_EQ("*content*", Out);
@@ -743,7 +723,7 @@ TEST(MustachePartials, Recursion) {
Value D =
Object{{"content", "X"},
{"nodes", Array{Object{{"content", "Y"}, {"nodes", Array{}}}}}};
- auto T = Template::createTemplate("{{>node}}");
+ auto T = Template("{{>node}}");
T.registerPartial("node", "{{content}}({{#nodes}}{{>node}}{{/nodes}})");
auto Out = T.render(D);
EXPECT_EQ("X(Y())", Out);
@@ -751,7 +731,7 @@ TEST(MustachePartials, Recursion) {
TEST(MustachePartials, Nested) {
Value D = Object{{"a", "hello"}, {"b", "world"}};
- auto T = Template::createTemplate("{{>outer}}");
+ auto T = Template("{{>outer}}");
T.registerPartial("outer", "*{{a}} {{>inner}}*");
T.registerPartial("inner", "{{b}}!");
auto Out = T.render(D);
@@ -760,7 +740,7 @@ TEST(MustachePartials, Nested) {
TEST(MustachePartials, SurroundingWhitespace) {
Value D = Object{};
- auto T = Template::createTemplate("| {{>partial}} |");
+ auto T = Template("| {{>partial}} |");
T.registerPartial("partial", "\t|\t");
auto Out = T.render(D);
EXPECT_EQ("| \t|\t |", Out);
@@ -768,7 +748,7 @@ TEST(MustachePartials, SurroundingWhitespace) {
TEST(MustachePartials, InlineIndentation) {
Value D = Object{{"data", "|"}};
- auto T = Template::createTemplate(" {{data}} {{> partial}}\n");
+ auto T = Template(" {{data}} {{> partial}}\n");
T.registerPartial("partial", "<\n<");
auto Out = T.render(D);
EXPECT_EQ(" | <\n<\n", Out);
@@ -776,7 +756,7 @@ TEST(MustachePartials, InlineIndentation) {
TEST(MustachePartials, PaddingWhitespace) {
Value D = Object{{"boolean", true}};
- auto T = Template::createTemplate("|{{> partial }}|");
+ auto T = Template("|{{> partial }}|");
T.registerPartial("partial", "[]");
auto Out = T.render(D);
EXPECT_EQ("|[]|", Out);
@@ -784,7 +764,7 @@ TEST(MustachePartials, PaddingWhitespace) {
TEST(MustacheLambdas, BasicInterpolation) {
Value D = Object{};
- auto T = Template::createTemplate("Hello, {{lambda}}!");
+ auto T = Template("Hello, {{lambda}}!");
Lambda L = []() -> llvm::json::Value { return "World"; };
T.registerLambda("lambda", L);
auto Out = T.render(D);
@@ -793,7 +773,7 @@ TEST(MustacheLambdas, BasicInterpolation) {
TEST(MustacheLambdas, InterpolationExpansion) {
Value D = Object{{"planet", "World"}};
- auto T = Template::createTemplate("Hello, {{lambda}}!");
+ auto T = Template("Hello, {{lambda}}!");
Lambda L = []() -> llvm::json::Value { return "{{planet}}"; };
T.registerLambda("lambda", L);
auto Out = T.render(D);
@@ -802,7 +782,7 @@ TEST(MustacheLambdas, InterpolationExpansion) {
TEST(MustacheLambdas, BasicMultipleCalls) {
Value D = Object{};
- auto T = Template::createTemplate("{{lambda}} == {{lambda}} == {{lambda}}");
+ auto T = Template("{{lambda}} == {{lambda}} == {{lambda}}");
int I = 0;
Lambda L = [&I]() -> llvm::json::Value {
I += 1;
@@ -815,7 +795,7 @@ TEST(MustacheLambdas, BasicMultipleCalls) {
TEST(MustacheLambdas, Escaping) {
Value D = Object{};
- auto T = Template::createTemplate("<{{lambda}}{{&lambda}}");
+ auto T = Template("<{{lambda}}{{&lambda}}");
Lambda L = []() -> llvm::json::Value { return ">"; };
T.registerLambda("lambda", L);
auto Out = T.render(D);
@@ -824,7 +804,7 @@ TEST(MustacheLambdas, Escaping) {
TEST(MustacheLambdas, Sections) {
Value D = Object{};
- auto T = Template::createTemplate("<{{#lambda}}{{x}}{{/lambda}}>");
+ auto T = Template("<{{#lambda}}{{x}}{{/lambda}}>");
SectionLambda L = [](StringRef Text) -> llvm::json::Value {
if (Text == "{{x}}") {
return "yes";
@@ -840,7 +820,7 @@ TEST(MustacheLambdas, SectionExpansion) {
Value D = Object{
{"planet", "Earth"},
};
- auto T = Template::createTemplate("<{{#lambda}}-{{/lambda}}>");
+ auto T = Template("<{{#lambda}}-{{/lambda}}>");
SectionLambda L = [](StringRef Text) -> llvm::json::Value {
SmallString<128> Result;
Result += Text;
@@ -855,8 +835,7 @@ TEST(MustacheLambdas, SectionExpansion) {
TEST(MustacheLambdas, SectionsMultipleCalls) {
Value D = Object{};
- auto T = Template::createTemplate(
- "{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}");
+ auto T = Template("{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}");
SectionLambda L = [](StringRef Text) -> llvm::json::Value {
SmallString<128> Result;
Result += "__";
@@ -871,7 +850,7 @@ TEST(MustacheLambdas, SectionsMultipleCalls) {
TEST(MustacheLambdas, InvertedSections) {
Value D = Object{{"static", "static"}};
- auto T = Template::createTemplate("<{{^lambda}}{{static}}{{/lambda}}>");
+ auto T = Template("<{{^lambda}}{{static}}{{/lambda}}>");
SectionLambda L = [](StringRef Text) -> llvm::json::Value { return false; };
T.registerLambda("lambda", L);
auto Out = T.render(D);
@@ -881,7 +860,7 @@ TEST(MustacheLambdas, InvertedSections) {
TEST(MustacheComments, Inline) {
// Comment blocks should be removed from the template.
Value D = {};
- auto T = Template::createTemplate("12345{{! Comment Block! }}67890");
+ auto T = Template("12345{{! Comment Block! }}67890");
auto Out = T.render(D);
EXPECT_EQ("1234567890", Out);
}
@@ -889,8 +868,8 @@ TEST(MustacheComments, Inline) {
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 T =
+ Template("12345{{!\n This is a\n multi-line comment...\n}}67890\n");
auto Out = T.render(D);
EXPECT_EQ("1234567890\n", Out);
}
@@ -898,7 +877,7 @@ TEST(MustacheComments, Multiline) {
TEST(MustacheComments, Standalone) {
// All standalone comment lines should be removed.
Value D = {};
- auto T = Template::createTemplate("Begin.\n{{! Comment Block! }}\nEnd.\n");
+ auto T = Template("Begin.\n{{! Comment Block! }}\nEnd.\n");
auto Out = T.render(D);
EXPECT_EQ("Begin.\nEnd.\n", Out);
}
@@ -906,8 +885,7 @@ TEST(MustacheComments, Standalone) {
TEST(MustacheComments, IndentedStandalone) {
// All standalone comment lines should be removed.
Value D = {};
- auto T = Template::createTemplate(
- "Begin.\n {{! Indented Comment Block! }}\nEnd.\n");
+ auto T = Template("Begin.\n {{! Indented Comment Block! }}\nEnd.\n");
auto Out = T.render(D);
EXPECT_EQ("Begin.\nEnd.\n", Out);
}
@@ -915,7 +893,7 @@ TEST(MustacheComments, IndentedStandalone) {
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 T = Template("|\r\n{{! Standalone Comment }}\r\n|");
auto Out = T.render(D);
EXPECT_EQ("|\r\n|", Out);
}
@@ -923,7 +901,7 @@ TEST(MustacheComments, StandaloneLineEndings) {
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 T = Template(" {{! I'm Still Standalone }}\n!");
auto Out = T.render(D);
EXPECT_EQ("!", Out);
}
@@ -931,7 +909,7 @@ TEST(MustacheComments, StandaloneWithoutPreviousLine) {
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 T = Template("!\n {{! I'm Still Standalone }}");
auto Out = T.render(D);
EXPECT_EQ("!\n", Out);
}
@@ -939,8 +917,7 @@ TEST(MustacheComments, StandaloneWithoutNewline) {
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 T = Template("Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n");
auto Out = T.render(D);
EXPECT_EQ("Begin.\nEnd.\n", Out);
}
@@ -948,8 +925,8 @@ TEST(MustacheComments, MultilineStandalone) {
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 T =
+ Template("Begin.\n {{!\n Something's going on here...\n }}\nEnd.\n");
auto Out = T.render(D);
EXPECT_EQ("Begin.\nEnd.\n", Out);
}
@@ -957,7 +934,7 @@ TEST(MustacheComments, IndentedMultilineStandalone) {
TEST(MustacheComments, IndentedInline) {
// Inline comments should not strip whitespace.
Value D = {};
- auto T = Template::createTemplate(" 12 {{! 34 }}\n");
+ auto T = Template(" 12 {{! 34 }}\n");
auto Out = T.render(D);
EXPECT_EQ(" 12 \n", Out);
}
@@ -965,7 +942,7 @@ TEST(MustacheComments, IndentedInline) {
TEST(MustacheComments, SurroundingWhitespace) {
// Comment removal should preserve surrounding whitespace.
Value D = {};
- auto T = Template::createTemplate("12345 {{! Comment Block! }} 67890");
+ auto T = Template("12345 {{! Comment Block! }} 67890");
auto Out = T.render(D);
EXPECT_EQ("12345 67890", Out);
}
@@ -974,7 +951,7 @@ 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 T = Template("comments never show: >{{! comment }}<");
auto Out = T.render(D);
EXPECT_EQ("comments never show: ><", Out);
}
\ No newline at end of file
>From d8aa85c2e87b9ae9c40ea0758969f5bc5720820e Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Thu, 12 Sep 2024 06:24:27 -0400
Subject: [PATCH 23/45] [llvm][support] clang-format
---
llvm/include/llvm/Support/Mustache.h | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 607e33b4ec2c9b..98843897707a82 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -28,10 +28,10 @@
//
// The Template class is container class outputs the Mustache template string
// and is main class for users. It stores all the lambdas and the ASTNode Tree.
-// When the Template is instantiated it calls tokenize the Template String into
-// the Token class and 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
+// When the Template is instantiated it tokenize 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
@@ -121,15 +121,15 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), LocalContext(nullptr){};
+ ASTNode() : T(Type::Root), LocalContext(nullptr) {};
ASTNode(StringRef Body, 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, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- LocalContext(nullptr){};
+ LocalContext(nullptr) {};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
>From 0534a054574ec18980e130a1d54a98af2bc59d5d Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Thu, 12 Sep 2024 06:54:19 -0400
Subject: [PATCH 24/45] [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 98843897707a82..88a2cb0d988097 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -28,9 +28,9 @@
//
// The Template class is container class outputs the Mustache template string
// and is main class for users. It stores all the lambdas and the ASTNode Tree.
-// When the Template is instantiated it tokenize 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
+// When the Template is instantiated it tokenize 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:
>From 06da7a5897347f2d22e406e2bec7800cbb29fd1d Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Thu, 12 Sep 2024 07:46:30 -0400
Subject: [PATCH 25/45] [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 88a2cb0d988097..073e3d0148dcc4 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -28,7 +28,7 @@
//
// The Template class is container class outputs the Mustache template string
// and is main class for users. It stores all the lambdas and the ASTNode Tree.
-// When the Template is instantiated it tokenize the Template String and
+// When the Template is instantiated it tokenize 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
>From 1e2b68a28a346af3aeb0cae7dcf287c51796d419 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 13 Sep 2024 02:26:22 -0400
Subject: [PATCH 26/45] [clang-doc] init mustache implementation
---
clang-tools-extra/clang-doc/CMakeLists.txt | 1 +
clang-tools-extra/clang-doc/HTMLGenerator.cpp | 3 +-
.../clang-doc/HTMLMustacheGenerator.cpp | 349 ++++++++++++++++++
clang-tools-extra/clang-doc/Representation.h | 4 +-
.../clang-doc/assets/template.mustache | 23 ++
.../clang-doc/tool/CMakeLists.txt | 1 +
.../clang-doc/tool/ClangDocMain.cpp | 31 ++
7 files changed, 410 insertions(+), 2 deletions(-)
create mode 100644 clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
create mode 100644 clang-tools-extra/clang-doc/assets/template.mustache
diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt
index 975ad8e242e490..82aded940222db 100644
--- a/clang-tools-extra/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/CMakeLists.txt
@@ -15,6 +15,7 @@ add_clang_library(clangDoc
Representation.cpp
Serialize.cpp
YAMLGenerator.cpp
+ HTMLMustacheGenerator.cpp
DEPENDS
omp_gen
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index e3532559a32fcc..ed65ac965114b5 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -1133,7 +1133,8 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) {
return llvm::Error::success();
}
-static llvm::Error copyFile(StringRef FilePath, StringRef OutDirectory) {
+static llvm::Error
+copyFile(StringRef FilePath, StringRef OutDirectory) {
llvm::SmallString<128> PathWrite;
llvm::sys::path::native(OutDirectory, PathWrite);
llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath));
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
new file mode 100644
index 00000000000000..63def07c5fa804
--- /dev/null
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -0,0 +1,349 @@
+//===-- HTMLMustacheGenerator.cpp - HTML Mustache Generator -----*- 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
+//
+//===----------------------------------------------------------------------===//
+#include "Generators.h"
+#include "Representation.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Mustache.h"
+
+using namespace llvm;
+using namespace llvm::json;
+using namespace llvm::mustache;
+
+namespace clang {
+namespace doc {
+
+
+class MustacheHTMLGenerator : public Generator {
+public:
+ static const char *Format;
+ llvm::Error generateDocs(StringRef RootDir,
+ llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+ const ClangDocContext &CDCtx) override;
+ llvm::Error createResources(ClangDocContext &CDCtx) override;
+ llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
+ const ClangDocContext &CDCtx) override;
+
+};
+
+class MustacheTemplateFile : public Template {
+public:
+ static ErrorOr<std::unique_ptr<MustacheTemplateFile>> createMustacheFile
+ (StringRef FileName) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrError =
+ MemoryBuffer::getFile(FileName);
+
+ if (auto EC = BufferOrError.getError()) {
+ return EC;
+ }
+ std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
+ llvm::StringRef FileContent = Buffer->getBuffer();
+ return std::make_unique<MustacheTemplateFile>(FileContent);
+ }
+
+ Error registerPartialFile(StringRef Name, StringRef FileName) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrError =
+ MemoryBuffer::getFile(FileName);
+ if (auto EC = BufferOrError.getError()) {
+ return llvm::createFileError("cannot open file", EC);
+ }
+ std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
+ llvm::StringRef FileContent = Buffer->getBuffer();
+ registerPartial(Name, FileContent);
+ }
+
+ MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {}
+};
+
+static std::unique_ptr<MustacheTemplateFile> NamespaceTemplate = nullptr;
+
+static std::unique_ptr<MustacheTemplateFile> RecordTemplate = nullptr;
+
+
+llvm::Error
+setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) {
+ auto TemplateFilePath = CDCtx.MustacheTemplates.lookup("template");
+ auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath);
+ if (auto EC = Template.getError()) {
+ return llvm::createFileError("cannot open file", EC);
+ }
+ NamespaceTemplate = std::move(Template.get());
+}
+
+llvm::Error
+MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
+ llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+ const clang::doc::ClangDocContext &CDCtx) {
+ if (auto Err = setupTemplateFiles(CDCtx)) {
+ return Err;
+ }
+ // Track which directories we already tried to create.
+ llvm::StringSet<> CreatedDirs;
+ // Collect all output by file name and create the necessary directories.
+ llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
+ for (const auto &Group : Infos) {
+ doc::Info *Info = Group.getValue().get();
+
+ llvm::SmallString<128> Path;
+ llvm::sys::path::native(RootDir, Path);
+ llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
+ if (!CreatedDirs.contains(Path)) {
+ if (std::error_code Err = llvm::sys::fs::create_directories(Path);
+ Err != std::error_code()) {
+ return llvm::createStringError(Err, "Failed to create directory '%s'.",
+ Path.c_str());
+ }
+ CreatedDirs.insert(Path);
+ }
+
+ llvm::sys::path::append(Path, Info->getFileBaseName() + ".html");
+ FileToInfos[Path].push_back(Info);
+ }
+
+ for (const auto &Group : FileToInfos) {
+ std::error_code FileErr;
+ llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
+ llvm::sys::fs::OF_None);
+ if (FileErr) {
+ return llvm::createStringError(FileErr, "Error opening file '%s'",
+ Group.getKey().str().c_str());
+ }
+ for (const auto &Info : Group.getValue()) {
+ if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
+ return Err;
+ }
+ }
+ }
+}
+
+Value extractValue(const Location &L,
+ std::optional<StringRef> RepositoryUrl = std::nullopt) {
+ Object Obj = Object();
+ if (!L.IsFileInRootDir || !RepositoryUrl) {
+ Obj.insert({"LineNumber", L.LineNumber});
+ Obj.insert({"Filename", L.Filename});
+ }
+ SmallString<128> FileURL(*RepositoryUrl);
+ llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename);
+ Obj.insert({"FileURL", FileURL});
+}
+
+Value extractValue(const Reference &I, StringRef CurrentDirectory) {
+ llvm::SmallString<64> Path = I.getRelativeFilePath(CurrentDirectory);
+ llvm::sys::path::append(Path, I.getFileBaseName() + ".html");
+ llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
+ Object Obj = Object();
+ Obj.insert({"Link", Path});
+ Obj.insert({"Name", I.Name});
+ return Obj;
+}
+
+
+Value extractValue(const TypedefInfo &I) {
+ // Not Supported
+ return nullptr;
+}
+
+Value extractValue(const CommentInfo &I) {
+ Object Obj = Object();
+ Value Child = Object();
+
+ if (I.Kind == "FullComment") {
+ Value ChildArr = Array();
+ for (const auto& C: I.Children)
+ ChildArr.getAsArray()->emplace_back(extractValue(*C));
+ Child.getAsObject()->insert({"Children", ChildArr});
+ Obj.insert({"FullComment", Child});
+ }
+ if (I.Kind == "ParagraphComment") {
+ Value ChildArr = Array();
+ for (const auto& C: I.Children)
+ ChildArr.getAsArray()->emplace_back(extractValue(*C));
+ Child.getAsObject()->insert({"Children", ChildArr});
+ Obj.insert({"ParagraphComment", Child});
+ }
+ if (I.Kind == "BlockCommandComment") {
+ Child.getAsObject()->insert({"Command", I.Name});
+ Value ChildArr = Array();
+ for (const auto& C: I.Children)
+ ChildArr.getAsArray()->emplace_back(extractValue(*C));
+ Child.getAsObject()->insert({"Children", ChildArr});
+ Obj.insert({"BlockCommandComment", Child});
+ }
+ if (I.Kind == "TextComment")
+ Obj.insert({"TextComment", I.Text});
+
+ return Obj;
+}
+
+Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir,
+ const ClangDocContext &CDCtx) {
+ Object Obj = Object();
+ Obj.insert({"Name", I.Name});
+ Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))});
+ Obj.insert({"Access", getAccessSpelling(I.Access).str()});
+ Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)});
+
+ Value ParamArr = Array();
+ for (const auto &P : I.Params) {
+ ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir));
+ }
+ Obj.insert({"Params", ParamArr});
+
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ Obj.insert({"FunctionComments", ArrDesc});
+ }
+ if (I.DefLoc) {
+ if (!CDCtx.RepositoryUrl)
+ Obj.insert({"Location", extractValue(*I.DefLoc)});
+ else
+ Obj.insert({"Location", extractValue(*I.DefLoc,
+ StringRef{*CDCtx.RepositoryUrl})});
+ }
+ return Obj;
+}
+
+Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) {
+ Object Obj = Object();
+ std::string EnumType = I.Scoped ? "enum class " : "enum ";
+ EnumType += I.Name;
+ bool HasComment = std::any_of(
+ I.Members.begin(), I.Members.end(),
+ [](const EnumValueInfo &M) { return !M.Description.empty(); });
+ Obj.insert({"EnumName", EnumType});
+ Obj.insert({"HasComment", HasComment});
+ Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))});
+
+ Value Arr = Array();
+ for (const EnumValueInfo& M: I.Members) {
+ Value EnumValue = Object();
+ EnumValue.getAsObject()->insert({"Name", M.Name});
+ if (!M.ValueExpr.empty())
+ EnumValue.getAsObject()->insert({"ValueExpr", M.ValueExpr});
+ else
+ EnumValue.getAsObject()->insert({"Value", M.Value});
+
+ if (!M.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : M.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ EnumValue.getAsObject()->insert({"EnumValueComments", ArrDesc});
+ }
+ Arr.getAsArray()->emplace_back(EnumValue);
+ }
+ Obj.insert({"EnumValues", Arr});
+
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ Obj.insert({"EnumComments", ArrDesc});
+ }
+
+ if (I.DefLoc) {
+ if (!CDCtx.RepositoryUrl)
+ Obj.insert({"Location", extractValue(*I.DefLoc)});
+ else
+ Obj.insert({"Location", extractValue(*I.DefLoc,
+ StringRef{*CDCtx.RepositoryUrl})});
+ }
+
+ return Obj;
+}
+
+Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) {
+ Object NamespaceValue = Object();
+ std::string InfoTitle;
+ if (I.Name.str() == "")
+ InfoTitle = "Global Namespace";
+ else
+ InfoTitle = ("namespace " + I.Name).str();
+
+ StringRef BasePath = I.getRelativeFilePath("");
+ NamespaceValue.insert({"NamespaceTitle", InfoTitle});
+ NamespaceValue.insert({"NamespacePath", I.getRelativeFilePath("")});
+
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ NamespaceValue.insert({"NamespaceComments", ArrDesc });
+ }
+
+ Value ArrNamespace = Array();
+ for (const Reference& Child : I.Children.Namespaces)
+ ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath));
+ NamespaceValue.insert({"Namespace", ArrNamespace});
+
+ Value ArrRecord = Array();
+ for (const Reference& Child : I.Children.Records)
+ ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath));
+ NamespaceValue.insert({"Record", ArrRecord});
+
+ Value ArrFunction = Array();
+ for (const FunctionInfo& Child : I.Children.Functions)
+ ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath,
+ CDCtx));
+ NamespaceValue.insert({"Function", ArrRecord});
+
+ Value ArrEnum = Array();
+ for (const EnumInfo& Child : I.Children.Enums)
+ ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx));
+ NamespaceValue.insert({"Enums", ArrEnum });
+
+ Value ArrTypedefs = Array();
+ for (const TypedefInfo& Child : I.Children.Typedefs)
+ ArrTypedefs.getAsArray()->emplace_back(extractValue(Child));
+ NamespaceValue.insert({"Typedefs", ArrTypedefs });
+
+}
+
+
+
+llvm::Error
+MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
+ const ClangDocContext &CDCtx) {
+ switch (I->IT) {
+ case InfoType::IT_namespace: {
+ Value V = extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
+ OS << NamespaceTemplate->render(V);
+ break;
+ }
+ case InfoType::IT_record:
+ break;
+ case InfoType::IT_enum:
+ break;
+ case InfoType::IT_function:
+ break;
+ case InfoType::IT_typedef:
+ break;
+ case InfoType::IT_default:
+ return createStringError(llvm::inconvertibleErrorCode(),
+ "unexpected InfoType");
+ }
+ return llvm::Error::success();
+}
+
+llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) {
+
+}
+
+const char *MustacheHTMLGenerator::Format = "mustache";
+
+
+static GeneratorRegistry::Add<MustacheHTMLGenerator> MHTML(MustacheHTMLGenerator::Format,
+ "Generator for mustache HTML output.");
+
+// This anchor is used to force the linker to link in the generated object
+// file and thus register the generator.
+volatile int HTMLGeneratorAnchorSource = 0;
+
+} // namespace doc
+} // namespace clang
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index bb0c534af7b740..3175b8209e3be3 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -521,8 +521,10 @@ struct ClangDocContext {
// Path of CSS stylesheets that will be copied to OutDirectory and used to
// style all HTML files.
std::vector<std::string> UserStylesheets;
- // JavaScript files that will be imported in allHTML file.
+ // JavaScript files that will be imported in all HTML file.
std::vector<std::string> JsScripts;
+ // Mustache Template files
+ llvm::StringMap<std::string> MustacheTemplates;
Index Idx;
};
diff --git a/clang-tools-extra/clang-doc/assets/template.mustache b/clang-tools-extra/clang-doc/assets/template.mustache
new file mode 100644
index 00000000000000..af4c60182ae52c
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/template.mustache
@@ -0,0 +1,23 @@
+{{!
+ 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
+
+ This file defines the template
+}}
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <meta charset="utf-8"/>
+ <title>{{NamespaceTitle}}</title>
+ </head>
+ {{#NamespaceComments}}
+ <p>Namespace Comment Present!</p>
+ {{/NamespaceComments}}
+ {{#Namespace}}
+ <p>Namespace Present!</p>
+ {{/Namespace}}
+ {{#Record}}
+ <p>Record Present!</p>
+ {{/Record}}
+</html>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/tool/CMakeLists.txt b/clang-tools-extra/clang-doc/tool/CMakeLists.txt
index 601a0460d76b38..8eb067dbe6de87 100644
--- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt
@@ -21,6 +21,7 @@ target_link_libraries(clang-doc
set(assets
index.js
+ template.mustache
clang-doc-default-stylesheet.css
)
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index 04773fb8a6e721..fc5a9529111b16 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -205,6 +205,30 @@ llvm::Error getHtmlAssetFiles(const char *Argv0,
return getDefaultAssetFiles(Argv0, CDCtx);
}
+llvm::Error getMustacheHtmlFiles(const char *Argv0,
+ clang::doc::ClangDocContext &CDCtx) {
+ if (!UserAssetPath.empty() &&
+ !llvm::sys::fs::is_directory(std::string(UserAssetPath)))
+ llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath
+ << " falling back to default\n";
+ if (llvm::sys::fs::is_directory(std::string(UserAssetPath)))
+ return getAssetFiles(CDCtx);
+
+ void *MainAddr = (void *)(intptr_t)getExecutablePath;
+ std::string ClangDocPath = getExecutablePath(Argv0, MainAddr);
+ llvm::SmallString<128> NativeClangDocPath;
+ llvm::sys::path::native(ClangDocPath, NativeClangDocPath);
+
+ llvm::SmallString<128> AssetsPath;
+ AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath);
+ llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc");
+
+ llvm::SmallString<128> MustacheTemplate;
+ llvm::sys::path::native(AssetsPath, MustacheTemplate);
+ llvm::sys::path::append(MustacheTemplate, "template.mustache");
+ CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()});
+}
+
/// Make the output of clang-doc deterministic by sorting the children of
/// namespaces and records.
void sortUsrToInfo(llvm::StringMap<std::unique_ptr<doc::Info>> &USRToInfo) {
@@ -277,6 +301,13 @@ Example usage for a project using a compile commands database:
return 1;
}
}
+
+ if (Format == "mhtml") {
+ if (auto Err = getMustacheHtmlFiles(argv[0], CDCtx)) {
+ llvm::errs() << toString(std::move(Err)) << "\n";
+ return 1;
+ }
+ }
// Mapping phase
llvm::outs() << "Mapping decls...\n";
>From 8d141ba80297ce024b0144f2522f7d662bf36568 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 13 Sep 2024 17:50:03 -0400
Subject: [PATCH 27/45] [clang-doc] add a mustache backend init implementation
---
clang-tools-extra/clang-doc/Generators.cpp | 4 +-
.../clang-doc/HTMLMustacheGenerator.cpp | 32 ++++++++++++----
.../clang-doc/assets/template.mustache | 37 +++++++++++++++++--
.../clang-doc/tool/ClangDocMain.cpp | 9 ++++-
.../Inputs/basic-project/include/Shape.h | 2 -
5 files changed, 68 insertions(+), 16 deletions(-)
diff --git a/clang-tools-extra/clang-doc/Generators.cpp b/clang-tools-extra/clang-doc/Generators.cpp
index a3986b66f3c742..ae5b556d17063b 100644
--- a/clang-tools-extra/clang-doc/Generators.cpp
+++ b/clang-tools-extra/clang-doc/Generators.cpp
@@ -100,12 +100,14 @@ void Generator::addInfoToIndex(Index &Idx, const doc::Info *Info) {
extern volatile int YAMLGeneratorAnchorSource;
extern volatile int MDGeneratorAnchorSource;
extern volatile int HTMLGeneratorAnchorSource;
+extern volatile int MHTMLGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED YAMLGeneratorAnchorDest =
YAMLGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED MDGeneratorAnchorDest =
MDGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest =
HTMLGeneratorAnchorSource;
-
+static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest =
+ MHTMLGeneratorAnchorSource;
} // namespace doc
} // namespace clang
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 63def07c5fa804..bfa563ebdc7b37 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -54,6 +54,7 @@ class MustacheTemplateFile : public Template {
std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
llvm::StringRef FileContent = Buffer->getBuffer();
registerPartial(Name, FileContent);
+ return llvm::Error::success();
}
MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {}
@@ -72,6 +73,7 @@ setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) {
return llvm::createFileError("cannot open file", EC);
}
NamespaceTemplate = std::move(Template.get());
+ return llvm::Error::success();
}
llvm::Error
@@ -118,6 +120,7 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
}
}
}
+ return llvm::Error::success();
}
Value extractValue(const Location &L,
@@ -130,6 +133,8 @@ Value extractValue(const Location &L,
SmallString<128> FileURL(*RepositoryUrl);
llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename);
Obj.insert({"FileURL", FileURL});
+
+ return Obj;
}
Value extractValue(const Reference &I, StringRef CurrentDirectory) {
@@ -280,29 +285,39 @@ Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) {
Value ArrNamespace = Array();
for (const Reference& Child : I.Children.Namespaces)
ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath));
- NamespaceValue.insert({"Namespace", ArrNamespace});
+
+ if (!ArrNamespace.getAsArray()->empty())
+ NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}});
Value ArrRecord = Array();
for (const Reference& Child : I.Children.Records)
ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath));
- NamespaceValue.insert({"Record", ArrRecord});
+
+ if (!ArrRecord.getAsArray()->empty())
+ NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}});
Value ArrFunction = Array();
for (const FunctionInfo& Child : I.Children.Functions)
ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath,
CDCtx));
- NamespaceValue.insert({"Function", ArrRecord});
+ if (!ArrFunction.getAsArray()->empty())
+ NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}});
Value ArrEnum = Array();
for (const EnumInfo& Child : I.Children.Enums)
ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx));
- NamespaceValue.insert({"Enums", ArrEnum });
+
+ if (!ArrEnum.getAsArray()->empty())
+ NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}});
Value ArrTypedefs = Array();
for (const TypedefInfo& Child : I.Children.Typedefs)
ArrTypedefs.getAsArray()->emplace_back(extractValue(Child));
- NamespaceValue.insert({"Typedefs", ArrTypedefs });
+ if (!ArrTypedefs.getAsArray()->empty())
+ NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}});
+
+ return NamespaceValue;
}
@@ -313,6 +328,7 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
switch (I->IT) {
case InfoType::IT_namespace: {
Value V = extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
+ llvm::outs() << V << "\n";
OS << NamespaceTemplate->render(V);
break;
}
@@ -332,10 +348,10 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
}
llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) {
-
+ return llvm::Error::success();
}
-const char *MustacheHTMLGenerator::Format = "mustache";
+const char *MustacheHTMLGenerator::Format = "mhtml";
static GeneratorRegistry::Add<MustacheHTMLGenerator> MHTML(MustacheHTMLGenerator::Format,
@@ -343,7 +359,7 @@ static GeneratorRegistry::Add<MustacheHTMLGenerator> MHTML(MustacheHTMLGenerator
// This anchor is used to force the linker to link in the generated object
// file and thus register the generator.
-volatile int HTMLGeneratorAnchorSource = 0;
+volatile int MHTMLGeneratorAnchorSource = 0;
} // namespace doc
} // namespace clang
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/template.mustache b/clang-tools-extra/clang-doc/assets/template.mustache
index af4c60182ae52c..1d3407f8b52920 100644
--- a/clang-tools-extra/clang-doc/assets/template.mustache
+++ b/clang-tools-extra/clang-doc/assets/template.mustache
@@ -3,7 +3,7 @@
See https://llvm.org/LICENSE.txt for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- This file defines the template
+ This file defines the template for generating Namespaces
}}
<!DOCTYPE html>
<html lang="en-US">
@@ -11,13 +11,42 @@
<meta charset="utf-8"/>
<title>{{NamespaceTitle}}</title>
</head>
+ <h1>{{NamespaceTitle}}</h1>
{{#NamespaceComments}}
- <p>Namespace Comment Present!</p>
+ <p>Namespace Comment</p>
{{/NamespaceComments}}
{{#Namespace}}
- <p>Namespace Present!</p>
+ <h2 id="Namespace">Namespace</h2>
+ <ul>
+ {{#Links}}
+ <li>
+ <a href="{{Link}}">{{Name}}</a>
+ </li>
+ {{/Links}}
+ </ul>
{{/Namespace}}
{{#Record}}
- <p>Record Present!</p>
+ <h2 id="Class">Class</h2>
+ <ul>
+ {{#Links}}
+ <li>
+ <a href="{{Link}}">{{Name}}</a>
+ </li>
+ {{/Links}}
+ </ul>
{{/Record}}
+ {{#Function}}
+ <h2 id="Function">Function</h2>
+ <div>
+ {{#Obj}}
+ {{/Obj}}
+ </div>
+ {{/Function}}
+ {{#Enums}}
+ <h2 id="Enums">Enums</h2>
+ <div>
+ {{#Obj}}
+ {{/Obj}}
+ </div>
+ {{/Enums}}
</html>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index fc5a9529111b16..eee8dbf093cc53 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -103,6 +103,7 @@ enum OutputFormatTy {
md,
yaml,
html,
+ mhtml
};
static llvm::cl::opt<OutputFormatTy>
@@ -112,7 +113,9 @@ static llvm::cl::opt<OutputFormatTy>
clEnumValN(OutputFormatTy::md, "md",
"Documentation in MD format."),
clEnumValN(OutputFormatTy::html, "html",
- "Documentation in HTML format.")),
+ "Documentation in HTML format."),
+ clEnumValN(OutputFormatTy::mhtml, "mhtml",
+ "Documentation in mHTML format")),
llvm::cl::init(OutputFormatTy::yaml),
llvm::cl::cat(ClangDocCategory));
@@ -124,6 +127,8 @@ std::string getFormatString() {
return "md";
case OutputFormatTy::html:
return "html";
+ case OutputFormatTy::mhtml:
+ return "mhtml";
}
llvm_unreachable("Unknown OutputFormatTy");
}
@@ -227,6 +232,8 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0,
llvm::sys::path::native(AssetsPath, MustacheTemplate);
llvm::sys::path::append(MustacheTemplate, "template.mustache");
CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()});
+
+ return llvm::Error::success();
}
/// Make the output of clang-doc deterministic by sorting the children of
diff --git a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
index e5c5d4c9e4412b..5354032f4d8323 100644
--- a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
+++ b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
@@ -26,5 +26,3 @@ class Shape {
*/
virtual double perimeter() const = 0;
};
-
-
>From f713198d1c4dbc286fa180973ccf604f2656c287 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 02:08:00 -0400
Subject: [PATCH 28/45] [llvm] mustache address comments
---
llvm/include/llvm/Support/Mustache.h | 67 ++++--
llvm/lib/Support/Mustache.cpp | 331 +++++++++++++++++----------
2 files changed, 251 insertions(+), 147 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 073e3d0148dcc4..4cd66d08dd39fa 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -64,12 +64,13 @@
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/JSON.h"
+#include "llvm/Support/StringSaver.h"
#include <vector>
namespace llvm {
namespace mustache {
-using Accessor = SmallVector<SmallString<128>>;
+using Accessor = SmallVector<SmallString<0>>;
using Lambda = std::function<llvm::json::Value()>;
using SectionLambda = std::function<llvm::json::Value(StringRef)>;
@@ -100,13 +101,20 @@ class Token {
Type getType() const { return TokenType; };
+ void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
+
+ size_t getIndentation() const { return Indentation; };
+
static Type getTokenType(char Identifier);
private:
+ size_t Indentation;
Type TokenType;
+ // RawBody is the original string that was tokenized
SmallString<128> RawBody;
Accessor Accessor;
- SmallString<128> TokenBody;
+ // TokenBody is the original string with the identifier removed
+ SmallString<0> TokenBody;
};
class ASTNode {
@@ -121,36 +129,55 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), LocalContext(nullptr) {};
+ ASTNode() : T(Type::Root), LocalContext(nullptr){};
ASTNode(StringRef Body, ASTNode *Parent)
- : T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr) {};
+ : T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr),
+ Indentation(0){};
// Constructor for Section/InvertSection/Variable/UnescapeVariable
ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- LocalContext(nullptr) {};
+ LocalContext(nullptr), Indentation(0){};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
- SmallString<128> getBody() const { return Body; };
-
void setBody(StringRef NewBody) { Body = NewBody; };
void setRawBody(StringRef NewBody) { RawBody = NewBody; };
- SmallString<128> render(llvm::json::Value Data,
- llvm::BumpPtrAllocator &Allocator,
- DenseMap<StringRef, ASTNode *> &Partials,
- DenseMap<StringRef, Lambda> &Lambdas,
- DenseMap<StringRef, SectionLambda> &SectionLambdas,
- DenseMap<char, StringRef> &Escapes);
+ void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
+
+ void render(llvm::json::Value Data, SmallString<0> &Output);
+
+ void setUpNode(llvm::BumpPtrAllocator &Allocator,
+ StringMap<ASTNode *> &Partials, StringMap<Lambda> &Lambdas,
+ StringMap<SectionLambda> &SectionLambdas,
+ DenseMap<char, StringRef> &Escapes);
private:
+ void renderLambdas(llvm::json::Value &Contexts, SmallString<0> &Output,
+ Lambda &L);
+
+ void renderSectionLambdas(llvm::json::Value &Contexts, SmallString<0> &Output,
+ SectionLambda &L);
+
+ void renderPartial(llvm::json::Value &Contexts, SmallString<0> &Output,
+ ASTNode *Partial);
+
+ void renderChild(llvm::json::Value &Context, SmallString<0> &Output);
+
llvm::json::Value findContext();
+
+ llvm::BumpPtrAllocator *Allocator;
+ StringMap<ASTNode *> *Partials;
+ StringMap<Lambda> *Lambdas;
+ StringMap<SectionLambda> *SectionLambdas;
+ DenseMap<char, StringRef> *Escapes;
Type T;
- SmallString<128> RawBody;
- SmallString<128> Body;
+ size_t Indentation;
+ SmallString<0> RawBody;
+ SmallString<0> Body;
ASTNode *Parent;
std::vector<ASTNode *> Children;
const Accessor Accessor;
@@ -163,7 +190,7 @@ class Template {
public:
Template(StringRef TemplateStr);
- SmallString<128> render(llvm::json::Value Data);
+ StringRef render(llvm::json::Value Data);
void registerPartial(StringRef Name, StringRef Partial);
@@ -177,14 +204,14 @@ class Template {
void registerEscape(DenseMap<char, StringRef> Escapes);
private:
- DenseMap<StringRef, ASTNode *> Partials;
- DenseMap<StringRef, Lambda> Lambdas;
- DenseMap<StringRef, SectionLambda> SectionLambdas;
+ SmallString<0> Output;
+ StringMap<ASTNode *> Partials;
+ StringMap<Lambda> Lambdas;
+ StringMap<SectionLambda> SectionLambdas;
DenseMap<char, StringRef> Escapes;
llvm::BumpPtrAllocator Allocator;
ASTNode *Tree;
};
-
} // namespace mustache
} // end namespace llvm
#endif // LLVM_SUPPORT_MUSTACHE
\ No newline at end of file
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index dd6e5ebb19209c..76532161fd18b9 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -8,21 +8,23 @@
#include "llvm/Support/Mustache.h"
#include "llvm/Support/Error.h"
+#include <sstream>
using namespace llvm;
using namespace llvm::json;
using namespace llvm::mustache;
-SmallString<128> escapeString(StringRef Input,
- DenseMap<char, StringRef> &Escape) {
- SmallString<128> EscapedString("");
+SmallString<0> escapeString(StringRef Input,
+ DenseMap<char, StringRef> &Escape) {
+
+ SmallString<0> Output;
for (char C : Input) {
if (Escape.find(C) != Escape.end())
- EscapedString += Escape[C];
+ Output += Escape[C];
else
- EscapedString += C;
+ Output += C;
}
- return EscapedString;
+ return Output;
}
Accessor split(StringRef Str, char Delimiter) {
@@ -40,8 +42,19 @@ Accessor split(StringRef Str, char Delimiter) {
return Tokens;
}
+void addIndentation(llvm::SmallString<0> &PartialStr, size_t IndentationSize) {
+ std::string Indent(IndentationSize, ' ');
+ llvm::SmallString<0> Result;
+ for (size_t I = 0; I < PartialStr.size(); ++I) {
+ Result.push_back(PartialStr[I]);
+ if (PartialStr[I] == '\n' && I < PartialStr.size() - 1)
+ Result.append(Indent);
+ }
+ PartialStr = Result;
+}
+
Token::Token(StringRef RawBody, StringRef InnerBody, char Identifier)
- : RawBody(RawBody), TokenBody(InnerBody) {
+ : RawBody(RawBody), TokenBody(InnerBody), Indentation(0) {
TokenType = getTokenType(Identifier);
if (TokenType == Type::Comment)
return;
@@ -53,7 +66,8 @@ Token::Token(StringRef RawBody, StringRef InnerBody, char Identifier)
}
Token::Token(StringRef Str)
- : TokenType(Type::Text), RawBody(Str), Accessor({}), TokenBody(Str) {}
+ : TokenType(Type::Text), RawBody(Str), Accessor({}), TokenBody(Str),
+ Indentation(0) {}
Token::Type Token::getTokenType(char Identifier) {
switch (Identifier) {
@@ -80,8 +94,11 @@ bool noTextBehind(size_t Idx, const SmallVector<Token, 0> &Tokens) {
return false;
const Token &PrevToken = Tokens[Idx - 1];
StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
- return TokenBody.ends_with("\n") || TokenBody.ends_with("\r\n") ||
- (TokenBody.empty() && Idx == 1);
+ // Check if the token body ends with a newline
+ // or if the previous token is empty and the current token is the first token
+ // eg. " {{#section}}A{{#section}}" would be considered as no text behind
+ // and should be render as "A" instead of " A"
+ return TokenBody.ends_with("\n") || (TokenBody.empty() && Idx == 1);
}
// Function to check if there's no meaningful text ahead
bool noTextAhead(size_t Idx, const SmallVector<Token, 0> &Tokens) {
@@ -94,6 +111,13 @@ bool noTextAhead(size_t Idx, const SmallVector<Token, 0> &Tokens) {
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;
+}
+
// Simple tokenizer that splits the template into tokens
// the mustache spec allows {{{ }}} to unescape variables
// but we don't support that here unescape variable
@@ -102,8 +126,8 @@ SmallVector<Token, 0> tokenize(StringRef Template) {
SmallVector<Token, 0> Tokens;
StringRef Open("{{");
StringRef Close("}}");
- std::size_t Start = 0;
- std::size_t DelimiterStart = Template.find(Open);
+ size_t Start = 0;
+ size_t DelimiterStart = Template.find(Open);
if (DelimiterStart == StringRef::npos) {
Tokens.emplace_back(Template);
return Tokens;
@@ -121,13 +145,9 @@ SmallVector<Token, 0> tokenize(StringRef Template) {
// Extract the Interpolated variable without {{ and }}
size_t InterpolatedStart = DelimiterStart + Open.size();
size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size();
- SmallString<128> Interpolated =
+ SmallString<0> Interpolated =
Template.substr(InterpolatedStart, InterpolatedEnd);
- SmallString<128> RawBody;
- RawBody += Open;
- RawBody += Interpolated;
- RawBody += Close;
-
+ SmallString<0> RawBody({Open, Interpolated, Close});
Tokens.emplace_back(RawBody, Interpolated, Interpolated[0]);
Start = DelimiterEnd + Close.size();
DelimiterStart = Template.find(Open, Start);
@@ -136,46 +156,66 @@ SmallVector<Token, 0> tokenize(StringRef Template) {
if (Start < Template.size())
Tokens.emplace_back(Template.substr(Start));
- // fix up white spaces for
- // open sections/inverted sections/close section/comment
+ // Fix up white spaces for:
+ // open sections/inverted sections/close section/comment
// This loop attempts to find standalone tokens and tries to trim out
- // the whitespace around them
- // for example:
+ // the surrounding whitespace.
+ // For example:
// if you have the template string
// "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3"
// The output would be
// "Line 1\n Line 2\n Line 3"
size_t LastIdx = Tokens.size() - 1;
for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {
- Token::Type CurrentType = Tokens[Idx].getType();
+ Token &CurrentToken = Tokens[Idx];
+ Token::Type CurrentType = CurrentToken.getType();
// Check if token type requires cleanup
- bool RequiresCleanUp = (CurrentType == Token::Type::SectionOpen ||
- CurrentType == Token::Type::InvertSectionOpen ||
- CurrentType == Token::Type::SectionClose ||
- CurrentType == Token::Type::Comment ||
- CurrentType == Token::Type::Partial);
+ bool RequiresCleanUp = requiresCleanUp(CurrentType);
if (!RequiresCleanUp)
continue;
+ // We adjust the token body if there's no text behind or ahead a token is
+ // considered surrounded by no text if the right of the previous token
+ // is a newline followed by spaces or 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 NoTextBehind = noTextBehind(Idx, Tokens);
bool NoTextAhead = noTextAhead(Idx, Tokens);
- // Adjust next token body if no text ahead
+ // Adjust next token body if there is no text ahead
+ // eg.
+ // The template string
+ // "{{! Comment }} \nLine 2"
+ // would be considered as no text ahead and should be render as
+ // " Line 2"
if ((NoTextBehind && NoTextAhead) || (NoTextAhead && Idx == 0)) {
Token &NextToken = Tokens[Idx + 1];
StringRef NextTokenBody = NextToken.getTokenBody();
- if (NextTokenBody.starts_with("\r\n")) {
+ // cut off the leading newline which could be \n or \r\n
+ if (NextTokenBody.starts_with("\r\n"))
NextToken.setTokenBody(NextTokenBody.substr(2));
- } else if (NextTokenBody.starts_with("\n")) {
+ else if (NextTokenBody.starts_with("\n"))
NextToken.setTokenBody(NextTokenBody.substr(1));
- }
}
- // Adjust previous token body if no text behind
- if ((NoTextBehind && NoTextAhead) || (NoTextBehind && Idx == LastIdx)) {
+ // Adjust previous token body if there no text behind
+ // eg.
+ // The template string
+ // " \t{{#section}}A{{/section}}"
+ // would be considered as no text ahead and should be render as
+ // "A"
+ // The exception for this is partial tag which requires us to
+ // keep track of the indentation once it's rendered
+ if (((NoTextBehind && NoTextAhead) || (NoTextBehind && Idx == LastIdx))) {
Token &PrevToken = Tokens[Idx - 1];
StringRef PrevTokenBody = PrevToken.getTokenBody();
- PrevToken.setTokenBody(PrevTokenBody.rtrim(" \t\v\t"));
+ StringRef Unindented = PrevTokenBody.rtrim(" \t\v");
+ size_t Indentation = PrevTokenBody.size() - Unindented.size();
+ if (CurrentType != Token::Type::Partial)
+ PrevToken.setTokenBody(Unindented);
+ CurrentToken.setIndentation(Indentation);
}
}
return Tokens;
@@ -233,6 +273,7 @@ void Parser::parseMustache(ASTNode *Parent) {
}
case Token::Type::Partial: {
CurrentNode = new (Node) ASTNode(ASTNode::Partial, A, Parent);
+ CurrentNode->setIndentation(CurrentToken.getIndentation());
Parent->addChild(CurrentNode);
break;
}
@@ -241,12 +282,12 @@ void Parser::parseMustache(ASTNode *Parent) {
size_t Start = CurrentPtr;
parseMustache(CurrentNode);
size_t End = CurrentPtr;
- SmallString<128> RawBody;
+ SmallString<0> 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();
+ RawBody = Tokens[Start + 1].getRawBody();
}
CurrentNode->setRawBody(RawBody);
Parent->addChild(CurrentNode);
@@ -257,12 +298,12 @@ void Parser::parseMustache(ASTNode *Parent) {
size_t Start = CurrentPtr;
parseMustache(CurrentNode);
size_t End = CurrentPtr;
- SmallString<128> RawBody;
+ SmallString<0> RawBody;
if (Start + 1 < End - 1) {
for (size_t Idx = Start + 1; Idx < End - 1; Idx++)
RawBody += Tokens[Idx].getRawBody();
} else if (Start + 1 == End - 1) {
- RawBody = Tokens[Start].getRawBody();
+ RawBody = Tokens[Start + 1].getRawBody();
}
CurrentNode->setRawBody(RawBody);
Parent->addChild(CurrentNode);
@@ -276,16 +317,17 @@ void Parser::parseMustache(ASTNode *Parent) {
}
}
-SmallString<128> Template::render(Value Data) {
+StringRef Template::render(Value Data) {
BumpPtrAllocator LocalAllocator;
- return Tree->render(Data, LocalAllocator, Partials, Lambdas, SectionLambdas,
- Escapes);
+ Tree->setUpNode(LocalAllocator, Partials, Lambdas, SectionLambdas, Escapes);
+ Tree->render(Data, Output);
+ return Output.str();
}
void Template::registerPartial(StringRef Name, StringRef Partial) {
Parser P = Parser(Partial, Allocator);
ASTNode *PartialTree = P.parse();
- Partials[Name] = PartialTree;
+ Partials.insert(std::make_pair(Name, PartialTree));
}
void Template::registerLambda(StringRef Name, Lambda L) { Lambdas[Name] = L; }
@@ -308,19 +350,23 @@ Template::Template(StringRef TemplateStr) {
registerEscape(HtmlEntities);
}
-SmallString<128> printJson(Value &Data) {
-
- SmallString<128> Result;
+void toJsonString(Value &Data, SmallString<0> &Output) {
if (Data.getAsNull())
- return Result;
+ return;
if (auto *Arr = Data.getAsArray())
if (Arr->empty())
- return Result;
+ return;
if (Data.getAsString()) {
- Result += Data.getAsString()->str();
- return Result;
+ Output = Data.getAsString()->str();
+ return;
+ }
+ if (auto Num = Data.getAsNumber()) {
+ std::ostringstream Oss;
+ Oss << *Num;
+ Output = Oss.str();
+ return;
}
- return formatv("{0:2}", Data);
+ Output = formatv("{0:2}", Data);
}
bool isFalsey(Value &V) {
@@ -329,97 +375,70 @@ bool isFalsey(Value &V) {
(V.getAsObject() && V.getAsObject()->empty());
}
-SmallString<128>
-ASTNode::render(Value Data, BumpPtrAllocator &Allocator,
- DenseMap<StringRef, ASTNode *> &Partials,
- DenseMap<StringRef, Lambda> &Lambdas,
- DenseMap<StringRef, SectionLambda> &SectionLambdas,
- DenseMap<char, StringRef> &Escapes) {
+void ASTNode::render(Value Data, SmallString<0> &Output) {
LocalContext = Data;
Value Context = T == Root ? Data : findContext();
- SmallString<128> Result;
switch (T) {
- case Root: {
- for (ASTNode *Child : Children)
- Result += Child->render(Context, Allocator, Partials, Lambdas,
- SectionLambdas, Escapes);
- return Result;
- }
+ case Root:
+ renderChild(Data, Output);
+ return;
case Text:
- return Body;
+ Output = Body;
+ return;
case Partial: {
- if (Partials.find(Accessor[0]) != Partials.end()) {
- ASTNode *Partial = Partials[Accessor[0]];
- Result += Partial->render(Data, Allocator, Partials, Lambdas,
- SectionLambdas, Escapes);
- return Result;
- }
- return Result;
+ auto Partial = Partials->find(Accessor[0]);
+ if (Partial != Partials->end())
+ renderPartial(Context, Output, Partial->getValue());
+ return;
}
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, Allocator);
- ASTNode *LambdaNode = P.parse();
- SmallString<128> RenderStr = LambdaNode->render(
- Data, Allocator, Partials, Lambdas, SectionLambdas, Escapes);
- return escapeString(RenderStr, Escapes);
+ auto Lambda = Lambdas->find(Accessor[0]);
+ if (Lambda != Lambdas->end())
+ renderLambdas(Data, Output, Lambda->getValue());
+ else {
+ toJsonString(Context, Output);
+ Output = escapeString(Output, *Escapes);
}
- return escapeString(printJson(Context), Escapes);
+ return;
}
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, Allocator);
- ASTNode *LambdaNode = P.parse();
- DenseMap<char, StringRef> EmptyEscapes;
- return LambdaNode->render(Data, Allocator, Partials, Lambdas,
- SectionLambdas, EmptyEscapes);
- }
- return printJson(Context);
+ auto Lambda = Lambdas->find(Accessor[0]);
+ if (Lambda != Lambdas->end())
+ renderLambdas(Data, Output, Lambda->getValue());
+ else
+ toJsonString(Context, Output);
+ return;
}
case Section: {
// Sections are not rendered if the context is falsey
- bool IsLambda = SectionLambdas.find(Accessor[0]) != SectionLambdas.end();
+ auto SectionLambda = SectionLambdas->find(Accessor[0]);
+ bool IsLambda = SectionLambda != SectionLambdas->end();
if (isFalsey(Context) && !IsLambda)
- return Result;
+ return;
+
if (IsLambda) {
- SectionLambda &Lambda = SectionLambdas[Accessor[0]];
- Value Return = Lambda(RawBody);
- if (isFalsey(Return))
- return Result;
- StringRef LambdaStr = printJson(Return);
- Parser P = Parser(LambdaStr.str(), Allocator);
- ASTNode *LambdaNode = P.parse();
- return LambdaNode->render(Data, Allocator, Partials, Lambdas,
- SectionLambdas, Escapes);
+ renderSectionLambdas(Data, Output, SectionLambda->getValue());
+ return;
}
+
if (Context.getAsArray()) {
+ SmallString<0> Result;
json::Array *Arr = Context.getAsArray();
- for (Value &V : *Arr) {
- for (ASTNode *Child : Children)
- Result += Child->render(V, Allocator, Partials, Lambdas,
- SectionLambdas, Escapes);
- }
- return Result;
+ for (Value &V : *Arr)
+ renderChild(V, Result);
+ Output = Result;
+ return;
}
- for (ASTNode *Child : Children)
- Result += Child->render(Context, Allocator, Partials, Lambdas,
- SectionLambdas, Escapes);
- return Result;
+
+ renderChild(Context, Output);
}
case InvertSection: {
- bool IsLambda = SectionLambdas.find(Accessor[0]) != SectionLambdas.end();
+ bool IsLambda = SectionLambdas->find(Accessor[0]) != SectionLambdas->end();
+
if (!isFalsey(Context) || IsLambda)
- return Result;
- for (ASTNode *Child : Children)
- Result += Child->render(Context, Allocator, Partials, Lambdas,
- SectionLambdas, Escapes);
- return Result;
+ return;
+
+ renderChild(Context, Output);
}
}
llvm_unreachable("Invalid ASTNode type");
@@ -427,10 +446,10 @@ ASTNode::render(Value Data, BumpPtrAllocator &Allocator,
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
+ // 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 (Accessor.empty())
return nullptr;
if (Accessor[0] == ".")
@@ -448,11 +467,11 @@ Value ASTNode::findContext() {
return nullptr;
}
Value Context = nullptr;
- for (auto E : enumerate(Accessor)) {
- Value *CurrentValue = CurrentContext->get(E.value());
+ for (auto [Idx, Acc] : enumerate(Accessor)) {
+ Value *CurrentValue = CurrentContext->get(Acc);
if (!CurrentValue)
return nullptr;
- if (E.index() < Accessor.size() - 1) {
+ if (Idx < Accessor.size() - 1) {
CurrentContext = CurrentValue->getAsObject();
if (!CurrentContext)
return nullptr;
@@ -461,3 +480,61 @@ Value ASTNode::findContext() {
}
return Context;
}
+
+void ASTNode::renderChild(Value &Context, SmallString<0> &Output) {
+ for (ASTNode *Child : Children) {
+ SmallString<0> ChildResult;
+ Child->render(Context, ChildResult);
+ Output += ChildResult;
+ }
+}
+
+void ASTNode::renderPartial(Value &Context, SmallString<0> &Output,
+ ASTNode *Partial) {
+ Partial->render(Context, Output);
+ addIndentation(Output, Indentation);
+}
+
+void ASTNode::renderLambdas(Value &Context, SmallString<0> &Output, Lambda &L) {
+ Value LambdaResult = L();
+ SmallString<0> LambdaStr;
+ toJsonString(LambdaResult, LambdaStr);
+ Parser P = Parser(LambdaStr, *Allocator);
+ ASTNode *LambdaNode = P.parse();
+ LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
+ *Escapes);
+ LambdaNode->render(Context, Output);
+ if (T == Variable)
+ Output = escapeString(Output, *Escapes);
+ return;
+}
+
+void ASTNode::renderSectionLambdas(Value &Contexts, SmallString<0> &Output,
+ SectionLambda &L) {
+ Value Return = L(RawBody);
+ if (isFalsey(Return))
+ return;
+ SmallString<0> LambdaStr;
+ toJsonString(Return, LambdaStr);
+ Parser P = Parser(LambdaStr, *Allocator);
+ ASTNode *LambdaNode = P.parse();
+ LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
+ *Escapes);
+ LambdaNode->render(Contexts, Output);
+ return;
+}
+
+void ASTNode::setUpNode(BumpPtrAllocator &Alloc, StringMap<ASTNode *> &Par,
+ StringMap<Lambda> &L, StringMap<SectionLambda> &SC,
+ DenseMap<char, StringRef> &E) {
+
+ // Passed down datastructures needed for rendering to
+ // the children nodes
+ Allocator = &Alloc;
+ Partials = &Par;
+ Lambdas = &L;
+ SectionLambdas = &SC;
+ Escapes = &E;
+ for (ASTNode *Child : Children)
+ Child->setUpNode(Alloc, Par, L, SC, E);
+}
>From 6b4f5cd29a687974f346c46bc02425854a14589b Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 02:21:47 -0400
Subject: [PATCH 29/45] [llvm] 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 4cd66d08dd39fa..21e233eb12d25c 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -129,16 +129,16 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), LocalContext(nullptr){};
+ ASTNode() : T(Type::Root), LocalContext(nullptr) {};
ASTNode(StringRef Body, ASTNode *Parent)
: T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr),
- Indentation(0){};
+ Indentation(0) {};
// Constructor for Section/InvertSection/Variable/UnescapeVariable
ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- LocalContext(nullptr), Indentation(0){};
+ LocalContext(nullptr), Indentation(0) {};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
>From d400c29c76f05f29cc5a84c616b926876c9ed085 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 03:01:32 -0400
Subject: [PATCH 30/45] [llvm] fix errors
---
llvm/lib/Support/Mustache.cpp | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 76532161fd18b9..d4d26245433175 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -287,7 +287,7 @@ void Parser::parseMustache(ASTNode *Parent) {
for (std::size_t I = Start + 1; I < End - 1; I++)
RawBody += Tokens[I].getRawBody();
} else if (Start + 1 == End - 1) {
- RawBody = Tokens[Start + 1].getRawBody();
+ RawBody = Tokens[Start].getRawBody();
}
CurrentNode->setRawBody(RawBody);
Parent->addChild(CurrentNode);
@@ -303,7 +303,7 @@ void Parser::parseMustache(ASTNode *Parent) {
for (size_t Idx = Start + 1; Idx < End - 1; Idx++)
RawBody += Tokens[Idx].getRawBody();
} else if (Start + 1 == End - 1) {
- RawBody = Tokens[Start + 1].getRawBody();
+ RawBody = Tokens[Start].getRawBody();
}
CurrentNode->setRawBody(RawBody);
Parent->addChild(CurrentNode);
@@ -388,7 +388,7 @@ void ASTNode::render(Value Data, SmallString<0> &Output) {
case Partial: {
auto Partial = Partials->find(Accessor[0]);
if (Partial != Partials->end())
- renderPartial(Context, Output, Partial->getValue());
+ renderPartial(Data, Output, Partial->getValue());
return;
}
case Variable: {
@@ -439,6 +439,7 @@ void ASTNode::render(Value Data, SmallString<0> &Output) {
return;
renderChild(Context, Output);
+ return;
}
}
llvm_unreachable("Invalid ASTNode type");
@@ -491,6 +492,8 @@ void ASTNode::renderChild(Value &Context, SmallString<0> &Output) {
void ASTNode::renderPartial(Value &Context, SmallString<0> &Output,
ASTNode *Partial) {
+ Partial->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
+ *Escapes);
Partial->render(Context, Output);
addIndentation(Output, Indentation);
}
>From 8fa5fd78b0e870d128da0dfb0f235ae9cb62bf20 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 10:23:51 -0400
Subject: [PATCH 31/45] [llvm] fix more bugs
---
llvm/lib/Support/Mustache.cpp | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index d4d26245433175..4bf31db15d634a 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -94,12 +94,14 @@ bool noTextBehind(size_t Idx, const SmallVector<Token, 0> &Tokens) {
return false;
const Token &PrevToken = Tokens[Idx - 1];
StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
- // Check if the token body ends with a newline
- // or if the previous token is empty and the current token is the first token
- // eg. " {{#section}}A{{#section}}" would be considered as no text behind
- // and should be render as "A" instead of " A"
+ // We make a special exception for when previous token is empty
+ // and the current token is the second token
+ // ex.
+ // " {{#section}}A{{/section}}"
+ // that's why we check if the token body is empty and the index is 1
return TokenBody.ends_with("\n") || (TokenBody.empty() && Idx == 1);
}
+
// Function to check if there's no meaningful text ahead
bool noTextAhead(size_t Idx, const SmallVector<Token, 0> &Tokens) {
if (Idx >= Tokens.size() - 1 ||
@@ -431,6 +433,7 @@ void ASTNode::render(Value Data, SmallString<0> &Output) {
}
renderChild(Context, Output);
+ return;
}
case InvertSection: {
bool IsLambda = SectionLambdas->find(Accessor[0]) != SectionLambdas->end();
>From fd7c106b30289ab5966799c4524535e6aa9393bb Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 14:40:28 -0400
Subject: [PATCH 32/45] [llvm] change mustache to pass by reference
---
llvm/include/llvm/Support/Mustache.h | 32 ++++----
llvm/lib/Support/Mustache.cpp | 107 ++++++++++++++-------------
2 files changed, 73 insertions(+), 66 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 21e233eb12d25c..433e2dfe4103b9 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -129,16 +129,16 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), LocalContext(nullptr) {};
+ ASTNode() : T(Type::Root), ParentContext(nullptr), LocalContext(nullptr){};
ASTNode(StringRef Body, ASTNode *Parent)
- : T(Type::Text), Body(Body), Parent(Parent), LocalContext(nullptr),
- Indentation(0) {};
+ : T(Type::Text), Body(Body), Parent(Parent), ParentContext(nullptr),
+ LocalContext(nullptr), Indentation(0){};
// Constructor for Section/InvertSection/Variable/UnescapeVariable
ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- LocalContext(nullptr), Indentation(0) {};
+ ParentContext(nullptr), LocalContext(nullptr), Indentation(0){};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
@@ -148,26 +148,27 @@ class ASTNode {
void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
- void render(llvm::json::Value Data, SmallString<0> &Output);
+ void render(const llvm::json::Value &Data, SmallString<0> &Output);
- void setUpNode(llvm::BumpPtrAllocator &Allocator,
- StringMap<ASTNode *> &Partials, StringMap<Lambda> &Lambdas,
+ void setUpNode(StringMap<ASTNode *> &Partials, StringMap<Lambda> &Lambdas,
StringMap<SectionLambda> &SectionLambdas,
DenseMap<char, StringRef> &Escapes);
+ void setUpContext(llvm::BumpPtrAllocator *Alloc);
+
private:
- void renderLambdas(llvm::json::Value &Contexts, SmallString<0> &Output,
+ void renderLambdas(const llvm::json::Value &Contexts, SmallString<0> &Output,
Lambda &L);
- void renderSectionLambdas(llvm::json::Value &Contexts, SmallString<0> &Output,
- SectionLambda &L);
+ void renderSectionLambdas(const llvm::json::Value &Contexts,
+ SmallString<0> &Output, SectionLambda &L);
- void renderPartial(llvm::json::Value &Contexts, SmallString<0> &Output,
+ void renderPartial(const llvm::json::Value &Contexts, SmallString<0> &Output,
ASTNode *Partial);
- void renderChild(llvm::json::Value &Context, SmallString<0> &Output);
+ void renderChild(const llvm::json::Value &Context, SmallString<0> &Output);
- llvm::json::Value findContext();
+ const llvm::json::Value *findContext();
llvm::BumpPtrAllocator *Allocator;
StringMap<ASTNode *> *Partials;
@@ -181,7 +182,8 @@ class ASTNode {
ASTNode *Parent;
std::vector<ASTNode *> Children;
const Accessor Accessor;
- llvm::json::Value LocalContext;
+ const llvm::json::Value *ParentContext;
+ const llvm::json::Value *LocalContext;
};
// A Template represents the container for the AST and the partials
@@ -190,7 +192,7 @@ class Template {
public:
Template(StringRef TemplateStr);
- StringRef render(llvm::json::Value Data);
+ StringRef render(llvm::json::Value &Data);
void registerPartial(StringRef Name, StringRef Partial);
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 4bf31db15d634a..87cded1ff0822b 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -94,8 +94,8 @@ bool noTextBehind(size_t Idx, const SmallVector<Token, 0> &Tokens) {
return false;
const Token &PrevToken = Tokens[Idx - 1];
StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
- // We make a special exception for when previous token is empty
- // and the current token is the second token
+ // We make a special exception for when previous token is empty
+ // and the current token is the second token
// ex.
// " {{#section}}A{{/section}}"
// that's why we check if the token body is empty and the index is 1
@@ -144,7 +144,7 @@ SmallVector<Token, 0> tokenize(StringRef Template) {
break;
}
- // Extract the Interpolated variable without {{ and }}
+ // Extract the Interpolated variable without delimiters {{ and }}
size_t InterpolatedStart = DelimiterStart + Open.size();
size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size();
SmallString<0> Interpolated =
@@ -285,12 +285,8 @@ void Parser::parseMustache(ASTNode *Parent) {
parseMustache(CurrentNode);
size_t End = CurrentPtr;
SmallString<0> 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();
- }
+ for (std::size_t I = Start; I < End - 1; I++)
+ RawBody += Tokens[I].getRawBody();
CurrentNode->setRawBody(RawBody);
Parent->addChild(CurrentNode);
break;
@@ -301,12 +297,8 @@ void Parser::parseMustache(ASTNode *Parent) {
parseMustache(CurrentNode);
size_t End = CurrentPtr;
SmallString<0> RawBody;
- if (Start + 1 < End - 1) {
- for (size_t Idx = Start + 1; Idx < End - 1; Idx++)
- RawBody += Tokens[Idx].getRawBody();
- } else if (Start + 1 == End - 1) {
- RawBody = Tokens[Start].getRawBody();
- }
+ for (size_t Idx = Start; Idx < End - 1; Idx++)
+ RawBody += Tokens[Idx].getRawBody();
CurrentNode->setRawBody(RawBody);
Parent->addChild(CurrentNode);
break;
@@ -319,9 +311,9 @@ void Parser::parseMustache(ASTNode *Parent) {
}
}
-StringRef Template::render(Value Data) {
+StringRef Template::render(Value &Data) {
BumpPtrAllocator LocalAllocator;
- Tree->setUpNode(LocalAllocator, Partials, Lambdas, SectionLambdas, Escapes);
+ Tree->setUpContext(&LocalAllocator);
Tree->render(Data, Output);
return Output.str();
}
@@ -329,6 +321,7 @@ StringRef Template::render(Value Data) {
void Template::registerPartial(StringRef Name, StringRef Partial) {
Parser P = Parser(Partial, Allocator);
ASTNode *PartialTree = P.parse();
+ PartialTree->setUpNode(Partials, Lambdas, SectionLambdas, Escapes);
Partials.insert(std::make_pair(Name, PartialTree));
}
@@ -350,9 +343,10 @@ Template::Template(StringRef TemplateStr) {
{'"', """},
{'\'', "'"}};
registerEscape(HtmlEntities);
+ Tree->setUpNode(Partials, Lambdas, SectionLambdas, Escapes);
}
-void toJsonString(Value &Data, SmallString<0> &Output) {
+void toJsonString(const Value &Data, SmallString<0> &Output) {
if (Data.getAsNull())
return;
if (auto *Arr = Data.getAsArray())
@@ -371,15 +365,18 @@ void toJsonString(Value &Data, SmallString<0> &Output) {
Output = formatv("{0:2}", Data);
}
-bool isFalsey(Value &V) {
+bool isFalsey(const Value &V) {
return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||
(V.getAsArray() && V.getAsArray()->empty()) ||
(V.getAsObject() && V.getAsObject()->empty());
}
-void ASTNode::render(Value Data, SmallString<0> &Output) {
- LocalContext = Data;
- Value Context = T == Root ? Data : findContext();
+void ASTNode::render(const Value &Data, SmallString<0> &Output) {
+
+ ParentContext = &Data;
+ const Value *ContextPtr = T == Root ? ParentContext : findContext();
+ const Value &Context = ContextPtr ? *ContextPtr : nullptr;
+
switch (T) {
case Root:
renderChild(Data, Output);
@@ -425,8 +422,8 @@ void ASTNode::render(Value Data, SmallString<0> &Output) {
if (Context.getAsArray()) {
SmallString<0> Result;
- json::Array *Arr = Context.getAsArray();
- for (Value &V : *Arr)
+ const json::Array *Arr = Context.getAsArray();
+ for (const Value &V : *Arr)
renderChild(V, Result);
Output = Result;
return;
@@ -448,7 +445,7 @@ void ASTNode::render(Value Data, SmallString<0> &Output) {
llvm_unreachable("Invalid ASTNode type");
}
-Value ASTNode::findContext() {
+const 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
@@ -457,22 +454,23 @@ Value ASTNode::findContext() {
if (Accessor.empty())
return nullptr;
if (Accessor[0] == ".")
- return LocalContext;
- json::Object *CurrentContext = LocalContext.getAsObject();
+ return ParentContext;
+
+ const json::Object *CurrentContext = ParentContext->getAsObject();
StringRef CurrentAccessor = Accessor[0];
ASTNode *CurrentParent = Parent;
while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
if (CurrentParent->T != Root) {
- CurrentContext = CurrentParent->LocalContext.getAsObject();
+ CurrentContext = CurrentParent->ParentContext->getAsObject();
CurrentParent = CurrentParent->Parent;
continue;
}
return nullptr;
}
- Value Context = nullptr;
+ const Value *Context = nullptr;
for (auto [Idx, Acc] : enumerate(Accessor)) {
- Value *CurrentValue = CurrentContext->get(Acc);
+ const Value *CurrentValue = CurrentContext->get(Acc);
if (!CurrentValue)
return nullptr;
if (Idx < Accessor.size() - 1) {
@@ -480,43 +478,43 @@ Value ASTNode::findContext() {
if (!CurrentContext)
return nullptr;
} else
- Context = *CurrentValue;
+ Context = CurrentValue;
}
return Context;
}
-void ASTNode::renderChild(Value &Context, SmallString<0> &Output) {
+void ASTNode::renderChild(const Value &Contexts, SmallString<0> &Output) {
for (ASTNode *Child : Children) {
SmallString<0> ChildResult;
- Child->render(Context, ChildResult);
+ Child->render(Contexts, ChildResult);
Output += ChildResult;
}
}
-void ASTNode::renderPartial(Value &Context, SmallString<0> &Output,
+void ASTNode::renderPartial(const Value &Contexts, SmallString<0> &Output,
ASTNode *Partial) {
- Partial->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
- *Escapes);
- Partial->render(Context, Output);
+ Partial->setUpContext(Allocator);
+ Partial->render(Contexts, Output);
addIndentation(Output, Indentation);
}
-void ASTNode::renderLambdas(Value &Context, SmallString<0> &Output, Lambda &L) {
+void ASTNode::renderLambdas(const Value &Contexts, SmallString<0> &Output,
+ Lambda &L) {
Value LambdaResult = L();
SmallString<0> LambdaStr;
toJsonString(LambdaResult, LambdaStr);
Parser P = Parser(LambdaStr, *Allocator);
ASTNode *LambdaNode = P.parse();
- LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
- *Escapes);
- LambdaNode->render(Context, Output);
+ LambdaNode->setUpNode(*Partials, *Lambdas, *SectionLambdas, *Escapes);
+ LambdaNode->setUpContext(Allocator);
+ LambdaNode->render(Contexts, Output);
if (T == Variable)
Output = escapeString(Output, *Escapes);
return;
}
-void ASTNode::renderSectionLambdas(Value &Contexts, SmallString<0> &Output,
- SectionLambda &L) {
+void ASTNode::renderSectionLambdas(const Value &Contexts,
+ SmallString<0> &Output, SectionLambda &L) {
Value Return = L(RawBody);
if (isFalsey(Return))
return;
@@ -524,23 +522,30 @@ void ASTNode::renderSectionLambdas(Value &Contexts, SmallString<0> &Output,
toJsonString(Return, LambdaStr);
Parser P = Parser(LambdaStr, *Allocator);
ASTNode *LambdaNode = P.parse();
- LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
- *Escapes);
+ LambdaNode->setUpNode(*Partials, *Lambdas, *SectionLambdas, *Escapes);
+ LambdaNode->setUpContext(Allocator);
LambdaNode->render(Contexts, Output);
return;
}
-void ASTNode::setUpNode(BumpPtrAllocator &Alloc, StringMap<ASTNode *> &Par,
- StringMap<Lambda> &L, StringMap<SectionLambda> &SC,
+void ASTNode::setUpNode(StringMap<ASTNode *> &Par, StringMap<Lambda> &L,
+ StringMap<SectionLambda> &SC,
DenseMap<char, StringRef> &E) {
-
// Passed down datastructures needed for rendering to
- // the children nodes
- Allocator = &Alloc;
+ // the children nodes. This must be called before rendering
Partials = &Par;
Lambdas = &L;
SectionLambdas = &SC;
Escapes = &E;
for (ASTNode *Child : Children)
- Child->setUpNode(Alloc, Par, L, SC, E);
+ Child->setUpNode(Par, L, SC, E);
+}
+
+void ASTNode::setUpContext(llvm::BumpPtrAllocator *Alloc) {
+ // Passed down the allocator to the children nodes.
+ // Each render call requires a allocator for generating
+ // Partial/Section nodes
+ Allocator = Alloc;
+ for (ASTNode *Child : Children)
+ Child->setUpContext(Alloc);
}
>From 29bba68935ef7f8cc5dff0477853ac25b8cf65bc Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 14:58:04 -0400
Subject: [PATCH 33/45] [llvm] fix failing mustache regression test
---
llvm/include/llvm/Support/Mustache.h | 6 +++---
llvm/lib/Support/Mustache.cpp | 5 +++--
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 433e2dfe4103b9..6d9cc8393a2f1e 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -129,16 +129,16 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), ParentContext(nullptr), LocalContext(nullptr){};
+ ASTNode() : T(Type::Root), ParentContext(nullptr), LocalContext(nullptr) {};
ASTNode(StringRef Body, ASTNode *Parent)
: T(Type::Text), Body(Body), Parent(Parent), ParentContext(nullptr),
- LocalContext(nullptr), Indentation(0){};
+ LocalContext(nullptr), Indentation(0) {};
// Constructor for Section/InvertSection/Variable/UnescapeVariable
ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- ParentContext(nullptr), LocalContext(nullptr), Indentation(0){};
+ ParentContext(nullptr), LocalContext(nullptr), Indentation(0) {};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 87cded1ff0822b..422ab60a672e57 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -19,8 +19,9 @@ SmallString<0> escapeString(StringRef Input,
SmallString<0> Output;
for (char C : Input) {
- if (Escape.find(C) != Escape.end())
- Output += Escape[C];
+ auto It = Escape.find(C);
+ if (It != Escape.end())
+ Output += It->getSecond();
else
Output += C;
}
>From 7eed82f343478e317b89dd9e48faeff2c9ce65f1 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 15:03:12 -0400
Subject: [PATCH 34/45] [llvm] format
---
llvm/include/llvm/Support/Mustache.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 6d9cc8393a2f1e..1b6a8c76f5218a 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -55,6 +55,7 @@
// StringRef Rendered = Template.render(Data);
// // Rendered == "Hello, World!"
// \endcode
+//
//===----------------------------------------------------------------------===//
#ifndef LLVM_SUPPORT_MUSTACHE
@@ -157,6 +158,7 @@ class ASTNode {
void setUpContext(llvm::BumpPtrAllocator *Alloc);
private:
+
void renderLambdas(const llvm::json::Value &Contexts, SmallString<0> &Output,
Lambda &L);
>From e73454d198155a595efde5401ea0d344a8970765 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 15:05:24 -0400
Subject: [PATCH 35/45] [llvm] remove unused mustache member
---
llvm/include/llvm/Support/Mustache.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 1b6a8c76f5218a..8b22fdfb74cf26 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -185,7 +185,6 @@ class ASTNode {
std::vector<ASTNode *> Children;
const Accessor Accessor;
const llvm::json::Value *ParentContext;
- const llvm::json::Value *LocalContext;
};
// A Template represents the container for the AST and the partials
>From b58fbcbbcbf93c077d63af64e67977e53f17cb9c Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 15:08:47 -0400
Subject: [PATCH 36/45] [llvm] remove unused mustache member
---
llvm/include/llvm/Support/Mustache.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 8b22fdfb74cf26..50435d4a04cd0f 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -134,12 +134,12 @@ class ASTNode {
ASTNode(StringRef Body, ASTNode *Parent)
: T(Type::Text), Body(Body), Parent(Parent), ParentContext(nullptr),
- LocalContext(nullptr), Indentation(0) {};
+ Indentation(0) {};
// Constructor for Section/InvertSection/Variable/UnescapeVariable
ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- ParentContext(nullptr), LocalContext(nullptr), Indentation(0) {};
+ ParentContext(nullptr), Indentation(0) {};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
>From bb4ba4096446218b33e232a87d5309a174858092 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 16:03:11 -0400
Subject: [PATCH 37/45] [llvm] address more comments
---
llvm/include/llvm/Support/Mustache.h | 15 +--
llvm/lib/Support/Mustache.cpp | 153 +++++++++++++++------------
2 files changed, 95 insertions(+), 73 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 50435d4a04cd0f..62c15e05850e60 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -130,7 +130,7 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), ParentContext(nullptr), LocalContext(nullptr) {};
+ ASTNode() : T(Type::Root), ParentContext(nullptr) {};
ASTNode(StringRef Body, ASTNode *Parent)
: T(Type::Text), Body(Body), Parent(Parent), ParentContext(nullptr),
@@ -151,12 +151,12 @@ class ASTNode {
void render(const llvm::json::Value &Data, SmallString<0> &Output);
- void setUpNode(StringMap<ASTNode *> &Partials, StringMap<Lambda> &Lambdas,
+ void setUpNode(llvm::BumpPtrAllocator &Alloc,
+ StringMap<ASTNode *> &Partials,
+ StringMap<Lambda> &Lambdas,
StringMap<SectionLambda> &SectionLambdas,
DenseMap<char, StringRef> &Escapes);
-
- void setUpContext(llvm::BumpPtrAllocator *Alloc);
-
+
private:
void renderLambdas(const llvm::json::Value &Contexts, SmallString<0> &Output,
@@ -212,9 +212,12 @@ class Template {
StringMap<Lambda> Lambdas;
StringMap<SectionLambda> SectionLambdas;
DenseMap<char, StringRef> Escapes;
+ // The allocator for the ASTNode Tree
llvm::BumpPtrAllocator Allocator;
+ // Allocator for each render call resets after each render
+ llvm::BumpPtrAllocator LocalAllocator;
ASTNode *Tree;
};
} // namespace mustache
} // end namespace llvm
-#endif // LLVM_SUPPORT_MUSTACHE
\ No newline at end of file
+#endif // LLVM_SUPPORT_MUSTACHE
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 422ab60a672e57..0fa88c0896f6d3 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -16,8 +16,8 @@ using namespace llvm::mustache;
SmallString<0> escapeString(StringRef Input,
DenseMap<char, StringRef> &Escape) {
-
SmallString<0> Output;
+ Output.reserve(Input.size());
for (char C : Input) {
auto It = Escape.find(C);
if (It != Escape.end())
@@ -91,24 +91,33 @@ Token::Type Token::getTokenType(char Identifier) {
// Function to check if there's no meaningful text behind
bool noTextBehind(size_t Idx, const SmallVector<Token, 0> &Tokens) {
- if (Idx == 0 || Tokens[Idx - 1].getType() != Token::Type::Text)
+ if(Idx == 0)
+ return false;
+
+ int PrevIdx = Idx - 1;
+ if (Tokens[PrevIdx].getType() != Token::Type::Text)
return false;
+
const Token &PrevToken = Tokens[Idx - 1];
StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
- // We make a special exception for when previous token is empty
+ // We make an exception for when previous token is empty
// and the current token is the second token
// ex.
// " {{#section}}A{{/section}}"
- // that's why we check if the token body is empty and the index is 1
+ // the output of this is
+ // "A"
return TokenBody.ends_with("\n") || (TokenBody.empty() && Idx == 1);
}
// Function to check if there's no meaningful text ahead
bool noTextAhead(size_t Idx, const SmallVector<Token, 0> &Tokens) {
- if (Idx >= Tokens.size() - 1 ||
- Tokens[Idx + 1].getType() != Token::Type::Text)
+ if (Idx >= Tokens.size() - 1)
return false;
-
+
+ int PrevIdx = Idx + 1;
+ if (Tokens[Idx + 1].getType() != Token::Type::Text)
+ return false;
+
const Token &NextToken = Tokens[Idx + 1];
StringRef TokenBody = NextToken.getRawBody().ltrim(" ");
return TokenBody.starts_with("\r\n") || TokenBody.starts_with("\n");
@@ -121,10 +130,47 @@ bool requiresCleanUp(Token::Type T) {
T == Token::Type::Partial;
}
-// 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}}
+// Adjust next token body if there is no text ahead
+// eg.
+// The template string
+// "{{! Comment }} \nLine 2"
+// would be considered as no text ahead and should be render as
+// " Line 2"
+void stripTokenAhead(SmallVector<Token, 0>& Tokens, size_t Idx) {
+ Token &NextToken = Tokens[Idx + 1];
+ StringRef NextTokenBody = NextToken.getTokenBody();
+ // cut off the leading newline which could be \n or \r\n
+ if (NextTokenBody.starts_with("\r\n"))
+ NextToken.setTokenBody(NextTokenBody.substr(2));
+ else if (NextTokenBody.starts_with("\n"))
+ NextToken.setTokenBody(NextTokenBody.substr(1));
+}
+
+// Adjust previous token body if there no text behind
+// eg.
+// The template string
+// " \t{{#section}}A{{/section}}"
+// would be considered as no text ahead and should 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(SmallVector<Token, 0>& Tokens,
+ size_t Idx,
+ Token& CurrentToken,
+ Token::Type CurrentType) {
+ Token &PrevToken = Tokens[Idx - 1];
+ StringRef PrevTokenBody = PrevToken.getTokenBody();
+ StringRef Unindented = PrevTokenBody.rtrim(" \t\v");
+ size_t Indentation = PrevTokenBody.size() - Unindented.size();
+ if (CurrentType != Token::Type::Partial)
+ PrevToken.setTokenBody(Unindented);
+ 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, 0> tokenize(StringRef Template) {
SmallVector<Token, 0> Tokens;
StringRef Open("{{");
@@ -160,7 +206,11 @@ SmallVector<Token, 0> tokenize(StringRef Template) {
Tokens.emplace_back(Template.substr(Start));
// Fix up white spaces for:
- // open sections/inverted sections/close section/comment
+ // - open sections
+ // - inverted sections
+ // - close sections
+ // - comments
+ //
// This loop attempts to find standalone tokens and tries to trim out
// the surrounding whitespace.
// For example:
@@ -178,47 +228,22 @@ SmallVector<Token, 0> tokenize(StringRef Template) {
if (!RequiresCleanUp)
continue;
- // We adjust the token body if there's no text behind or ahead a token is
- // considered surrounded by no text if the right of the previous token
- // is a newline followed by spaces or if the left of the next token
- // is spaces followed by a newline
+ // 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 NoTextBehind = noTextBehind(Idx, Tokens);
bool NoTextAhead = noTextAhead(Idx, Tokens);
-
- // Adjust next token body if there is no text ahead
- // eg.
- // The template string
- // "{{! Comment }} \nLine 2"
- // would be considered as no text ahead and should be render as
- // " Line 2"
+
if ((NoTextBehind && NoTextAhead) || (NoTextAhead && Idx == 0)) {
- Token &NextToken = Tokens[Idx + 1];
- StringRef NextTokenBody = NextToken.getTokenBody();
- // cut off the leading newline which could be \n or \r\n
- if (NextTokenBody.starts_with("\r\n"))
- NextToken.setTokenBody(NextTokenBody.substr(2));
- else if (NextTokenBody.starts_with("\n"))
- NextToken.setTokenBody(NextTokenBody.substr(1));
+ stripTokenAhead(Tokens, Idx);
}
- // Adjust previous token body if there no text behind
- // eg.
- // The template string
- // " \t{{#section}}A{{/section}}"
- // would be considered as no text ahead and should be render as
- // "A"
- // The exception for this is partial tag which requires us to
- // keep track of the indentation once it's rendered
+
if (((NoTextBehind && NoTextAhead) || (NoTextBehind && Idx == LastIdx))) {
- Token &PrevToken = Tokens[Idx - 1];
- StringRef PrevTokenBody = PrevToken.getTokenBody();
- StringRef Unindented = PrevTokenBody.rtrim(" \t\v");
- size_t Indentation = PrevTokenBody.size() - Unindented.size();
- if (CurrentType != Token::Type::Partial)
- PrevToken.setTokenBody(Unindented);
- CurrentToken.setIndentation(Indentation);
+ stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);
}
}
return Tokens;
@@ -304,25 +329,25 @@ void Parser::parseMustache(ASTNode *Parent) {
Parent->addChild(CurrentNode);
break;
}
+ case Token::Type::Comment:
+ break;
case Token::Type::SectionClose:
return;
- default:
- break;
}
}
}
StringRef Template::render(Value &Data) {
- BumpPtrAllocator LocalAllocator;
- Tree->setUpContext(&LocalAllocator);
Tree->render(Data, Output);
+ LocalAllocator.Reset();
return Output.str();
}
void Template::registerPartial(StringRef Name, StringRef Partial) {
Parser P = Parser(Partial, Allocator);
ASTNode *PartialTree = P.parse();
- PartialTree->setUpNode(Partials, Lambdas, SectionLambdas, Escapes);
+ PartialTree->setUpNode(LocalAllocator, Partials, Lambdas, SectionLambdas,
+ Escapes);
Partials.insert(std::make_pair(Name, PartialTree));
}
@@ -344,7 +369,8 @@ Template::Template(StringRef TemplateStr) {
{'"', """},
{'\'', "'"}};
registerEscape(HtmlEntities);
- Tree->setUpNode(Partials, Lambdas, SectionLambdas, Escapes);
+ Tree->setUpNode(LocalAllocator,
+ Partials, Lambdas, SectionLambdas, Escapes);
}
void toJsonString(const Value &Data, SmallString<0> &Output) {
@@ -494,7 +520,6 @@ void ASTNode::renderChild(const Value &Contexts, SmallString<0> &Output) {
void ASTNode::renderPartial(const Value &Contexts, SmallString<0> &Output,
ASTNode *Partial) {
- Partial->setUpContext(Allocator);
Partial->render(Contexts, Output);
addIndentation(Output, Indentation);
}
@@ -506,8 +531,8 @@ void ASTNode::renderLambdas(const Value &Contexts, SmallString<0> &Output,
toJsonString(LambdaResult, LambdaStr);
Parser P = Parser(LambdaStr, *Allocator);
ASTNode *LambdaNode = P.parse();
- LambdaNode->setUpNode(*Partials, *Lambdas, *SectionLambdas, *Escapes);
- LambdaNode->setUpContext(Allocator);
+ LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
+ *Escapes);
LambdaNode->render(Contexts, Output);
if (T == Variable)
Output = escapeString(Output, *Escapes);
@@ -523,30 +548,24 @@ void ASTNode::renderSectionLambdas(const Value &Contexts,
toJsonString(Return, LambdaStr);
Parser P = Parser(LambdaStr, *Allocator);
ASTNode *LambdaNode = P.parse();
- LambdaNode->setUpNode(*Partials, *Lambdas, *SectionLambdas, *Escapes);
- LambdaNode->setUpContext(Allocator);
+ LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
+ *Escapes);
LambdaNode->render(Contexts, Output);
return;
}
-void ASTNode::setUpNode(StringMap<ASTNode *> &Par, StringMap<Lambda> &L,
+void ASTNode::setUpNode(llvm::BumpPtrAllocator &Alloc,
+ StringMap<ASTNode *> &Par, StringMap<Lambda> &L,
StringMap<SectionLambda> &SC,
DenseMap<char, StringRef> &E) {
// Passed down datastructures needed for rendering to
// the children nodes. This must be called before rendering
+ Allocator = &Alloc;
Partials = &Par;
Lambdas = &L;
SectionLambdas = &SC;
Escapes = &E;
for (ASTNode *Child : Children)
- Child->setUpNode(Par, L, SC, E);
+ Child->setUpNode(Alloc, Par, L, SC, E);
}
-void ASTNode::setUpContext(llvm::BumpPtrAllocator *Alloc) {
- // Passed down the allocator to the children nodes.
- // Each render call requires a allocator for generating
- // Partial/Section nodes
- Allocator = Alloc;
- for (ASTNode *Child : Children)
- Child->setUpContext(Alloc);
-}
>From b0ce196c46d0b488afd013861889f1eaf8cfffc6 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 16:16:49 -0400
Subject: [PATCH 38/45] [llvm] address comments
---
llvm/include/llvm/Support/Mustache.h | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index 62c15e05850e60..e475d05e108a75 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -96,7 +96,7 @@ class Token {
StringRef getRawBody() const { return RawBody; };
- void setTokenBody(SmallString<128> NewBody) { TokenBody = NewBody; };
+ void setTokenBody(StringRef NewBody) { TokenBody = NewBody.str(); };
Accessor getAccessor() const { return Accessor; };
@@ -112,7 +112,7 @@ class Token {
size_t Indentation;
Type TokenType;
// RawBody is the original string that was tokenized
- SmallString<128> RawBody;
+ SmallString<0> RawBody;
Accessor Accessor;
// TokenBody is the original string with the identifier removed
SmallString<0> TokenBody;
@@ -130,16 +130,16 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), ParentContext(nullptr) {};
+ ASTNode() : T(Type::Root), ParentContext(nullptr){};
ASTNode(StringRef Body, ASTNode *Parent)
: T(Type::Text), Body(Body), Parent(Parent), ParentContext(nullptr),
- Indentation(0) {};
+ Indentation(0){};
// Constructor for Section/InvertSection/Variable/UnescapeVariable
ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- ParentContext(nullptr), Indentation(0) {};
+ ParentContext(nullptr), Indentation(0){};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
@@ -151,14 +151,12 @@ class ASTNode {
void render(const llvm::json::Value &Data, SmallString<0> &Output);
- void setUpNode(llvm::BumpPtrAllocator &Alloc,
- StringMap<ASTNode *> &Partials,
+ void setUpNode(llvm::BumpPtrAllocator &Alloc, StringMap<ASTNode *> &Partials,
StringMap<Lambda> &Lambdas,
StringMap<SectionLambda> &SectionLambdas,
DenseMap<char, StringRef> &Escapes);
-
+
private:
-
void renderLambdas(const llvm::json::Value &Contexts, SmallString<0> &Output,
Lambda &L);
>From 561c7eb095baf4648b0756d19fdd392f07a9586f Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 16:28:09 -0400
Subject: [PATCH 39/45] clang-format
---
llvm/lib/Support/Mustache.cpp | 36 ++++++++++++++++-------------------
1 file changed, 16 insertions(+), 20 deletions(-)
diff --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 0fa88c0896f6d3..2af4307fae02a3 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -91,13 +91,13 @@ Token::Type Token::getTokenType(char Identifier) {
// Function to check if there's no meaningful text behind
bool noTextBehind(size_t Idx, const SmallVector<Token, 0> &Tokens) {
- if(Idx == 0)
+ if (Idx == 0)
return false;
-
+
int PrevIdx = Idx - 1;
if (Tokens[PrevIdx].getType() != Token::Type::Text)
return false;
-
+
const Token &PrevToken = Tokens[Idx - 1];
StringRef TokenBody = PrevToken.getRawBody().rtrim(" \t\v\t");
// We make an exception for when previous token is empty
@@ -113,11 +113,11 @@ bool noTextBehind(size_t Idx, const SmallVector<Token, 0> &Tokens) {
bool noTextAhead(size_t Idx, const SmallVector<Token, 0> &Tokens) {
if (Idx >= Tokens.size() - 1)
return false;
-
+
int PrevIdx = Idx + 1;
if (Tokens[Idx + 1].getType() != Token::Type::Text)
return false;
-
+
const Token &NextToken = Tokens[Idx + 1];
StringRef TokenBody = NextToken.getRawBody().ltrim(" ");
return TokenBody.starts_with("\r\n") || TokenBody.starts_with("\n");
@@ -136,7 +136,7 @@ bool requiresCleanUp(Token::Type T) {
// "{{! Comment }} \nLine 2"
// would be considered as no text ahead and should be render as
// " Line 2"
-void stripTokenAhead(SmallVector<Token, 0>& Tokens, size_t Idx) {
+void stripTokenAhead(SmallVector<Token, 0> &Tokens, size_t Idx) {
Token &NextToken = Tokens[Idx + 1];
StringRef NextTokenBody = NextToken.getTokenBody();
// cut off the leading newline which could be \n or \r\n
@@ -154,10 +154,8 @@ void stripTokenAhead(SmallVector<Token, 0>& Tokens, size_t Idx) {
// "A"
// The exception for this is partial tag which requires us to
// keep track of the indentation once it's rendered
-void stripTokenBefore(SmallVector<Token, 0>& Tokens,
- size_t Idx,
- Token& CurrentToken,
- Token::Type CurrentType) {
+void stripTokenBefore(SmallVector<Token, 0> &Tokens, size_t Idx,
+ Token &CurrentToken, Token::Type CurrentType) {
Token &PrevToken = Tokens[Idx - 1];
StringRef PrevTokenBody = PrevToken.getTokenBody();
StringRef Unindented = PrevTokenBody.rtrim(" \t\v");
@@ -228,20 +226,20 @@ SmallVector<Token, 0> tokenize(StringRef Template) {
if (!RequiresCleanUp)
continue;
- // We adjust the token body if there's no text behind or ahead.
+ // 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
+ // 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 NoTextBehind = noTextBehind(Idx, Tokens);
bool NoTextAhead = noTextAhead(Idx, Tokens);
-
+
if ((NoTextBehind && NoTextAhead) || (NoTextAhead && Idx == 0)) {
stripTokenAhead(Tokens, Idx);
}
-
+
if (((NoTextBehind && NoTextAhead) || (NoTextBehind && Idx == LastIdx))) {
stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);
}
@@ -346,7 +344,7 @@ StringRef Template::render(Value &Data) {
void Template::registerPartial(StringRef Name, StringRef Partial) {
Parser P = Parser(Partial, Allocator);
ASTNode *PartialTree = P.parse();
- PartialTree->setUpNode(LocalAllocator, Partials, Lambdas, SectionLambdas,
+ PartialTree->setUpNode(LocalAllocator, Partials, Lambdas, SectionLambdas,
Escapes);
Partials.insert(std::make_pair(Name, PartialTree));
}
@@ -369,8 +367,7 @@ Template::Template(StringRef TemplateStr) {
{'"', """},
{'\'', "'"}};
registerEscape(HtmlEntities);
- Tree->setUpNode(LocalAllocator,
- Partials, Lambdas, SectionLambdas, Escapes);
+ Tree->setUpNode(LocalAllocator, Partials, Lambdas, SectionLambdas, Escapes);
}
void toJsonString(const Value &Data, SmallString<0> &Output) {
@@ -531,7 +528,7 @@ void ASTNode::renderLambdas(const Value &Contexts, SmallString<0> &Output,
toJsonString(LambdaResult, LambdaStr);
Parser P = Parser(LambdaStr, *Allocator);
ASTNode *LambdaNode = P.parse();
- LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
+ LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
*Escapes);
LambdaNode->render(Contexts, Output);
if (T == Variable)
@@ -548,7 +545,7 @@ void ASTNode::renderSectionLambdas(const Value &Contexts,
toJsonString(Return, LambdaStr);
Parser P = Parser(LambdaStr, *Allocator);
ASTNode *LambdaNode = P.parse();
- LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
+ LambdaNode->setUpNode(*Allocator, *Partials, *Lambdas, *SectionLambdas,
*Escapes);
LambdaNode->render(Contexts, Output);
return;
@@ -568,4 +565,3 @@ void ASTNode::setUpNode(llvm::BumpPtrAllocator &Alloc,
for (ASTNode *Child : Children)
Child->setUpNode(Alloc, Par, L, SC, E);
}
-
>From 96f69907c1be3418f617ecc10d192538e61633ec Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 8 Oct 2024 16:28:56 -0400
Subject: [PATCH 40/45] 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 e475d05e108a75..1130434a265e42 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -130,16 +130,16 @@ class ASTNode {
InvertSection,
};
- ASTNode() : T(Type::Root), ParentContext(nullptr){};
+ ASTNode() : T(Type::Root), ParentContext(nullptr) {};
ASTNode(StringRef Body, ASTNode *Parent)
: T(Type::Text), Body(Body), Parent(Parent), ParentContext(nullptr),
- Indentation(0){};
+ Indentation(0) {};
// Constructor for Section/InvertSection/Variable/UnescapeVariable
ASTNode(Type T, Accessor Accessor, ASTNode *Parent)
: T(T), Parent(Parent), Children({}), Accessor(Accessor),
- ParentContext(nullptr), Indentation(0){};
+ ParentContext(nullptr), Indentation(0) {};
void addChild(ASTNode *Child) { Children.emplace_back(Child); };
>From 3c53b5a27d8ae5b8306aaa74e7da5248f025231c Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 13 Sep 2024 02:26:22 -0400
Subject: [PATCH 41/45] [clang-doc] init mustache implementation
---
clang-tools-extra/clang-doc/CMakeLists.txt | 1 +
clang-tools-extra/clang-doc/HTMLGenerator.cpp | 3 +-
.../clang-doc/HTMLMustacheGenerator.cpp | 349 ++++++++++++++++++
clang-tools-extra/clang-doc/Representation.h | 4 +-
.../clang-doc/assets/template.mustache | 23 ++
.../clang-doc/tool/CMakeLists.txt | 1 +
.../clang-doc/tool/ClangDocMain.cpp | 31 ++
7 files changed, 410 insertions(+), 2 deletions(-)
create mode 100644 clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
create mode 100644 clang-tools-extra/clang-doc/assets/template.mustache
diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt
index 975ad8e242e490..82aded940222db 100644
--- a/clang-tools-extra/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/CMakeLists.txt
@@ -15,6 +15,7 @@ add_clang_library(clangDoc
Representation.cpp
Serialize.cpp
YAMLGenerator.cpp
+ HTMLMustacheGenerator.cpp
DEPENDS
omp_gen
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index e3532559a32fcc..ed65ac965114b5 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -1133,7 +1133,8 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) {
return llvm::Error::success();
}
-static llvm::Error copyFile(StringRef FilePath, StringRef OutDirectory) {
+static llvm::Error
+copyFile(StringRef FilePath, StringRef OutDirectory) {
llvm::SmallString<128> PathWrite;
llvm::sys::path::native(OutDirectory, PathWrite);
llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath));
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
new file mode 100644
index 00000000000000..63def07c5fa804
--- /dev/null
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -0,0 +1,349 @@
+//===-- HTMLMustacheGenerator.cpp - HTML Mustache Generator -----*- 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
+//
+//===----------------------------------------------------------------------===//
+#include "Generators.h"
+#include "Representation.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Mustache.h"
+
+using namespace llvm;
+using namespace llvm::json;
+using namespace llvm::mustache;
+
+namespace clang {
+namespace doc {
+
+
+class MustacheHTMLGenerator : public Generator {
+public:
+ static const char *Format;
+ llvm::Error generateDocs(StringRef RootDir,
+ llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+ const ClangDocContext &CDCtx) override;
+ llvm::Error createResources(ClangDocContext &CDCtx) override;
+ llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
+ const ClangDocContext &CDCtx) override;
+
+};
+
+class MustacheTemplateFile : public Template {
+public:
+ static ErrorOr<std::unique_ptr<MustacheTemplateFile>> createMustacheFile
+ (StringRef FileName) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrError =
+ MemoryBuffer::getFile(FileName);
+
+ if (auto EC = BufferOrError.getError()) {
+ return EC;
+ }
+ std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
+ llvm::StringRef FileContent = Buffer->getBuffer();
+ return std::make_unique<MustacheTemplateFile>(FileContent);
+ }
+
+ Error registerPartialFile(StringRef Name, StringRef FileName) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrError =
+ MemoryBuffer::getFile(FileName);
+ if (auto EC = BufferOrError.getError()) {
+ return llvm::createFileError("cannot open file", EC);
+ }
+ std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
+ llvm::StringRef FileContent = Buffer->getBuffer();
+ registerPartial(Name, FileContent);
+ }
+
+ MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {}
+};
+
+static std::unique_ptr<MustacheTemplateFile> NamespaceTemplate = nullptr;
+
+static std::unique_ptr<MustacheTemplateFile> RecordTemplate = nullptr;
+
+
+llvm::Error
+setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) {
+ auto TemplateFilePath = CDCtx.MustacheTemplates.lookup("template");
+ auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath);
+ if (auto EC = Template.getError()) {
+ return llvm::createFileError("cannot open file", EC);
+ }
+ NamespaceTemplate = std::move(Template.get());
+}
+
+llvm::Error
+MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
+ llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+ const clang::doc::ClangDocContext &CDCtx) {
+ if (auto Err = setupTemplateFiles(CDCtx)) {
+ return Err;
+ }
+ // Track which directories we already tried to create.
+ llvm::StringSet<> CreatedDirs;
+ // Collect all output by file name and create the necessary directories.
+ llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
+ for (const auto &Group : Infos) {
+ doc::Info *Info = Group.getValue().get();
+
+ llvm::SmallString<128> Path;
+ llvm::sys::path::native(RootDir, Path);
+ llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
+ if (!CreatedDirs.contains(Path)) {
+ if (std::error_code Err = llvm::sys::fs::create_directories(Path);
+ Err != std::error_code()) {
+ return llvm::createStringError(Err, "Failed to create directory '%s'.",
+ Path.c_str());
+ }
+ CreatedDirs.insert(Path);
+ }
+
+ llvm::sys::path::append(Path, Info->getFileBaseName() + ".html");
+ FileToInfos[Path].push_back(Info);
+ }
+
+ for (const auto &Group : FileToInfos) {
+ std::error_code FileErr;
+ llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
+ llvm::sys::fs::OF_None);
+ if (FileErr) {
+ return llvm::createStringError(FileErr, "Error opening file '%s'",
+ Group.getKey().str().c_str());
+ }
+ for (const auto &Info : Group.getValue()) {
+ if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
+ return Err;
+ }
+ }
+ }
+}
+
+Value extractValue(const Location &L,
+ std::optional<StringRef> RepositoryUrl = std::nullopt) {
+ Object Obj = Object();
+ if (!L.IsFileInRootDir || !RepositoryUrl) {
+ Obj.insert({"LineNumber", L.LineNumber});
+ Obj.insert({"Filename", L.Filename});
+ }
+ SmallString<128> FileURL(*RepositoryUrl);
+ llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename);
+ Obj.insert({"FileURL", FileURL});
+}
+
+Value extractValue(const Reference &I, StringRef CurrentDirectory) {
+ llvm::SmallString<64> Path = I.getRelativeFilePath(CurrentDirectory);
+ llvm::sys::path::append(Path, I.getFileBaseName() + ".html");
+ llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
+ Object Obj = Object();
+ Obj.insert({"Link", Path});
+ Obj.insert({"Name", I.Name});
+ return Obj;
+}
+
+
+Value extractValue(const TypedefInfo &I) {
+ // Not Supported
+ return nullptr;
+}
+
+Value extractValue(const CommentInfo &I) {
+ Object Obj = Object();
+ Value Child = Object();
+
+ if (I.Kind == "FullComment") {
+ Value ChildArr = Array();
+ for (const auto& C: I.Children)
+ ChildArr.getAsArray()->emplace_back(extractValue(*C));
+ Child.getAsObject()->insert({"Children", ChildArr});
+ Obj.insert({"FullComment", Child});
+ }
+ if (I.Kind == "ParagraphComment") {
+ Value ChildArr = Array();
+ for (const auto& C: I.Children)
+ ChildArr.getAsArray()->emplace_back(extractValue(*C));
+ Child.getAsObject()->insert({"Children", ChildArr});
+ Obj.insert({"ParagraphComment", Child});
+ }
+ if (I.Kind == "BlockCommandComment") {
+ Child.getAsObject()->insert({"Command", I.Name});
+ Value ChildArr = Array();
+ for (const auto& C: I.Children)
+ ChildArr.getAsArray()->emplace_back(extractValue(*C));
+ Child.getAsObject()->insert({"Children", ChildArr});
+ Obj.insert({"BlockCommandComment", Child});
+ }
+ if (I.Kind == "TextComment")
+ Obj.insert({"TextComment", I.Text});
+
+ return Obj;
+}
+
+Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir,
+ const ClangDocContext &CDCtx) {
+ Object Obj = Object();
+ Obj.insert({"Name", I.Name});
+ Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))});
+ Obj.insert({"Access", getAccessSpelling(I.Access).str()});
+ Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)});
+
+ Value ParamArr = Array();
+ for (const auto &P : I.Params) {
+ ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir));
+ }
+ Obj.insert({"Params", ParamArr});
+
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ Obj.insert({"FunctionComments", ArrDesc});
+ }
+ if (I.DefLoc) {
+ if (!CDCtx.RepositoryUrl)
+ Obj.insert({"Location", extractValue(*I.DefLoc)});
+ else
+ Obj.insert({"Location", extractValue(*I.DefLoc,
+ StringRef{*CDCtx.RepositoryUrl})});
+ }
+ return Obj;
+}
+
+Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) {
+ Object Obj = Object();
+ std::string EnumType = I.Scoped ? "enum class " : "enum ";
+ EnumType += I.Name;
+ bool HasComment = std::any_of(
+ I.Members.begin(), I.Members.end(),
+ [](const EnumValueInfo &M) { return !M.Description.empty(); });
+ Obj.insert({"EnumName", EnumType});
+ Obj.insert({"HasComment", HasComment});
+ Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))});
+
+ Value Arr = Array();
+ for (const EnumValueInfo& M: I.Members) {
+ Value EnumValue = Object();
+ EnumValue.getAsObject()->insert({"Name", M.Name});
+ if (!M.ValueExpr.empty())
+ EnumValue.getAsObject()->insert({"ValueExpr", M.ValueExpr});
+ else
+ EnumValue.getAsObject()->insert({"Value", M.Value});
+
+ if (!M.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : M.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ EnumValue.getAsObject()->insert({"EnumValueComments", ArrDesc});
+ }
+ Arr.getAsArray()->emplace_back(EnumValue);
+ }
+ Obj.insert({"EnumValues", Arr});
+
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ Obj.insert({"EnumComments", ArrDesc});
+ }
+
+ if (I.DefLoc) {
+ if (!CDCtx.RepositoryUrl)
+ Obj.insert({"Location", extractValue(*I.DefLoc)});
+ else
+ Obj.insert({"Location", extractValue(*I.DefLoc,
+ StringRef{*CDCtx.RepositoryUrl})});
+ }
+
+ return Obj;
+}
+
+Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) {
+ Object NamespaceValue = Object();
+ std::string InfoTitle;
+ if (I.Name.str() == "")
+ InfoTitle = "Global Namespace";
+ else
+ InfoTitle = ("namespace " + I.Name).str();
+
+ StringRef BasePath = I.getRelativeFilePath("");
+ NamespaceValue.insert({"NamespaceTitle", InfoTitle});
+ NamespaceValue.insert({"NamespacePath", I.getRelativeFilePath("")});
+
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ NamespaceValue.insert({"NamespaceComments", ArrDesc });
+ }
+
+ Value ArrNamespace = Array();
+ for (const Reference& Child : I.Children.Namespaces)
+ ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath));
+ NamespaceValue.insert({"Namespace", ArrNamespace});
+
+ Value ArrRecord = Array();
+ for (const Reference& Child : I.Children.Records)
+ ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath));
+ NamespaceValue.insert({"Record", ArrRecord});
+
+ Value ArrFunction = Array();
+ for (const FunctionInfo& Child : I.Children.Functions)
+ ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath,
+ CDCtx));
+ NamespaceValue.insert({"Function", ArrRecord});
+
+ Value ArrEnum = Array();
+ for (const EnumInfo& Child : I.Children.Enums)
+ ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx));
+ NamespaceValue.insert({"Enums", ArrEnum });
+
+ Value ArrTypedefs = Array();
+ for (const TypedefInfo& Child : I.Children.Typedefs)
+ ArrTypedefs.getAsArray()->emplace_back(extractValue(Child));
+ NamespaceValue.insert({"Typedefs", ArrTypedefs });
+
+}
+
+
+
+llvm::Error
+MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
+ const ClangDocContext &CDCtx) {
+ switch (I->IT) {
+ case InfoType::IT_namespace: {
+ Value V = extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
+ OS << NamespaceTemplate->render(V);
+ break;
+ }
+ case InfoType::IT_record:
+ break;
+ case InfoType::IT_enum:
+ break;
+ case InfoType::IT_function:
+ break;
+ case InfoType::IT_typedef:
+ break;
+ case InfoType::IT_default:
+ return createStringError(llvm::inconvertibleErrorCode(),
+ "unexpected InfoType");
+ }
+ return llvm::Error::success();
+}
+
+llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) {
+
+}
+
+const char *MustacheHTMLGenerator::Format = "mustache";
+
+
+static GeneratorRegistry::Add<MustacheHTMLGenerator> MHTML(MustacheHTMLGenerator::Format,
+ "Generator for mustache HTML output.");
+
+// This anchor is used to force the linker to link in the generated object
+// file and thus register the generator.
+volatile int HTMLGeneratorAnchorSource = 0;
+
+} // namespace doc
+} // namespace clang
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index bb0c534af7b740..3175b8209e3be3 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -521,8 +521,10 @@ struct ClangDocContext {
// Path of CSS stylesheets that will be copied to OutDirectory and used to
// style all HTML files.
std::vector<std::string> UserStylesheets;
- // JavaScript files that will be imported in allHTML file.
+ // JavaScript files that will be imported in all HTML file.
std::vector<std::string> JsScripts;
+ // Mustache Template files
+ llvm::StringMap<std::string> MustacheTemplates;
Index Idx;
};
diff --git a/clang-tools-extra/clang-doc/assets/template.mustache b/clang-tools-extra/clang-doc/assets/template.mustache
new file mode 100644
index 00000000000000..af4c60182ae52c
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/template.mustache
@@ -0,0 +1,23 @@
+{{!
+ 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
+
+ This file defines the template
+}}
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <meta charset="utf-8"/>
+ <title>{{NamespaceTitle}}</title>
+ </head>
+ {{#NamespaceComments}}
+ <p>Namespace Comment Present!</p>
+ {{/NamespaceComments}}
+ {{#Namespace}}
+ <p>Namespace Present!</p>
+ {{/Namespace}}
+ {{#Record}}
+ <p>Record Present!</p>
+ {{/Record}}
+</html>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/tool/CMakeLists.txt b/clang-tools-extra/clang-doc/tool/CMakeLists.txt
index 601a0460d76b38..8eb067dbe6de87 100644
--- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt
@@ -21,6 +21,7 @@ target_link_libraries(clang-doc
set(assets
index.js
+ template.mustache
clang-doc-default-stylesheet.css
)
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index 04773fb8a6e721..fc5a9529111b16 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -205,6 +205,30 @@ llvm::Error getHtmlAssetFiles(const char *Argv0,
return getDefaultAssetFiles(Argv0, CDCtx);
}
+llvm::Error getMustacheHtmlFiles(const char *Argv0,
+ clang::doc::ClangDocContext &CDCtx) {
+ if (!UserAssetPath.empty() &&
+ !llvm::sys::fs::is_directory(std::string(UserAssetPath)))
+ llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath
+ << " falling back to default\n";
+ if (llvm::sys::fs::is_directory(std::string(UserAssetPath)))
+ return getAssetFiles(CDCtx);
+
+ void *MainAddr = (void *)(intptr_t)getExecutablePath;
+ std::string ClangDocPath = getExecutablePath(Argv0, MainAddr);
+ llvm::SmallString<128> NativeClangDocPath;
+ llvm::sys::path::native(ClangDocPath, NativeClangDocPath);
+
+ llvm::SmallString<128> AssetsPath;
+ AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath);
+ llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc");
+
+ llvm::SmallString<128> MustacheTemplate;
+ llvm::sys::path::native(AssetsPath, MustacheTemplate);
+ llvm::sys::path::append(MustacheTemplate, "template.mustache");
+ CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()});
+}
+
/// Make the output of clang-doc deterministic by sorting the children of
/// namespaces and records.
void sortUsrToInfo(llvm::StringMap<std::unique_ptr<doc::Info>> &USRToInfo) {
@@ -277,6 +301,13 @@ Example usage for a project using a compile commands database:
return 1;
}
}
+
+ if (Format == "mhtml") {
+ if (auto Err = getMustacheHtmlFiles(argv[0], CDCtx)) {
+ llvm::errs() << toString(std::move(Err)) << "\n";
+ return 1;
+ }
+ }
// Mapping phase
llvm::outs() << "Mapping decls...\n";
>From 7067fab56f58ff565305ec03c5c8b5c23e748011 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Fri, 13 Sep 2024 17:50:03 -0400
Subject: [PATCH 42/45] [clang-doc] add a mustache backend init implementation
---
clang-tools-extra/clang-doc/Generators.cpp | 4 +-
.../clang-doc/HTMLMustacheGenerator.cpp | 32 ++++++++++++----
.../clang-doc/assets/template.mustache | 37 +++++++++++++++++--
.../clang-doc/tool/ClangDocMain.cpp | 9 ++++-
.../Inputs/basic-project/include/Shape.h | 2 -
5 files changed, 68 insertions(+), 16 deletions(-)
diff --git a/clang-tools-extra/clang-doc/Generators.cpp b/clang-tools-extra/clang-doc/Generators.cpp
index a3986b66f3c742..ae5b556d17063b 100644
--- a/clang-tools-extra/clang-doc/Generators.cpp
+++ b/clang-tools-extra/clang-doc/Generators.cpp
@@ -100,12 +100,14 @@ void Generator::addInfoToIndex(Index &Idx, const doc::Info *Info) {
extern volatile int YAMLGeneratorAnchorSource;
extern volatile int MDGeneratorAnchorSource;
extern volatile int HTMLGeneratorAnchorSource;
+extern volatile int MHTMLGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED YAMLGeneratorAnchorDest =
YAMLGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED MDGeneratorAnchorDest =
MDGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest =
HTMLGeneratorAnchorSource;
-
+static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest =
+ MHTMLGeneratorAnchorSource;
} // namespace doc
} // namespace clang
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 63def07c5fa804..bfa563ebdc7b37 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -54,6 +54,7 @@ class MustacheTemplateFile : public Template {
std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
llvm::StringRef FileContent = Buffer->getBuffer();
registerPartial(Name, FileContent);
+ return llvm::Error::success();
}
MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {}
@@ -72,6 +73,7 @@ setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) {
return llvm::createFileError("cannot open file", EC);
}
NamespaceTemplate = std::move(Template.get());
+ return llvm::Error::success();
}
llvm::Error
@@ -118,6 +120,7 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
}
}
}
+ return llvm::Error::success();
}
Value extractValue(const Location &L,
@@ -130,6 +133,8 @@ Value extractValue(const Location &L,
SmallString<128> FileURL(*RepositoryUrl);
llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename);
Obj.insert({"FileURL", FileURL});
+
+ return Obj;
}
Value extractValue(const Reference &I, StringRef CurrentDirectory) {
@@ -280,29 +285,39 @@ Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) {
Value ArrNamespace = Array();
for (const Reference& Child : I.Children.Namespaces)
ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath));
- NamespaceValue.insert({"Namespace", ArrNamespace});
+
+ if (!ArrNamespace.getAsArray()->empty())
+ NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}});
Value ArrRecord = Array();
for (const Reference& Child : I.Children.Records)
ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath));
- NamespaceValue.insert({"Record", ArrRecord});
+
+ if (!ArrRecord.getAsArray()->empty())
+ NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}});
Value ArrFunction = Array();
for (const FunctionInfo& Child : I.Children.Functions)
ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath,
CDCtx));
- NamespaceValue.insert({"Function", ArrRecord});
+ if (!ArrFunction.getAsArray()->empty())
+ NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}});
Value ArrEnum = Array();
for (const EnumInfo& Child : I.Children.Enums)
ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx));
- NamespaceValue.insert({"Enums", ArrEnum });
+
+ if (!ArrEnum.getAsArray()->empty())
+ NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}});
Value ArrTypedefs = Array();
for (const TypedefInfo& Child : I.Children.Typedefs)
ArrTypedefs.getAsArray()->emplace_back(extractValue(Child));
- NamespaceValue.insert({"Typedefs", ArrTypedefs });
+ if (!ArrTypedefs.getAsArray()->empty())
+ NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}});
+
+ return NamespaceValue;
}
@@ -313,6 +328,7 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
switch (I->IT) {
case InfoType::IT_namespace: {
Value V = extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
+ llvm::outs() << V << "\n";
OS << NamespaceTemplate->render(V);
break;
}
@@ -332,10 +348,10 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
}
llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) {
-
+ return llvm::Error::success();
}
-const char *MustacheHTMLGenerator::Format = "mustache";
+const char *MustacheHTMLGenerator::Format = "mhtml";
static GeneratorRegistry::Add<MustacheHTMLGenerator> MHTML(MustacheHTMLGenerator::Format,
@@ -343,7 +359,7 @@ static GeneratorRegistry::Add<MustacheHTMLGenerator> MHTML(MustacheHTMLGenerator
// This anchor is used to force the linker to link in the generated object
// file and thus register the generator.
-volatile int HTMLGeneratorAnchorSource = 0;
+volatile int MHTMLGeneratorAnchorSource = 0;
} // namespace doc
} // namespace clang
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/template.mustache b/clang-tools-extra/clang-doc/assets/template.mustache
index af4c60182ae52c..1d3407f8b52920 100644
--- a/clang-tools-extra/clang-doc/assets/template.mustache
+++ b/clang-tools-extra/clang-doc/assets/template.mustache
@@ -3,7 +3,7 @@
See https://llvm.org/LICENSE.txt for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- This file defines the template
+ This file defines the template for generating Namespaces
}}
<!DOCTYPE html>
<html lang="en-US">
@@ -11,13 +11,42 @@
<meta charset="utf-8"/>
<title>{{NamespaceTitle}}</title>
</head>
+ <h1>{{NamespaceTitle}}</h1>
{{#NamespaceComments}}
- <p>Namespace Comment Present!</p>
+ <p>Namespace Comment</p>
{{/NamespaceComments}}
{{#Namespace}}
- <p>Namespace Present!</p>
+ <h2 id="Namespace">Namespace</h2>
+ <ul>
+ {{#Links}}
+ <li>
+ <a href="{{Link}}">{{Name}}</a>
+ </li>
+ {{/Links}}
+ </ul>
{{/Namespace}}
{{#Record}}
- <p>Record Present!</p>
+ <h2 id="Class">Class</h2>
+ <ul>
+ {{#Links}}
+ <li>
+ <a href="{{Link}}">{{Name}}</a>
+ </li>
+ {{/Links}}
+ </ul>
{{/Record}}
+ {{#Function}}
+ <h2 id="Function">Function</h2>
+ <div>
+ {{#Obj}}
+ {{/Obj}}
+ </div>
+ {{/Function}}
+ {{#Enums}}
+ <h2 id="Enums">Enums</h2>
+ <div>
+ {{#Obj}}
+ {{/Obj}}
+ </div>
+ {{/Enums}}
</html>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index fc5a9529111b16..eee8dbf093cc53 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -103,6 +103,7 @@ enum OutputFormatTy {
md,
yaml,
html,
+ mhtml
};
static llvm::cl::opt<OutputFormatTy>
@@ -112,7 +113,9 @@ static llvm::cl::opt<OutputFormatTy>
clEnumValN(OutputFormatTy::md, "md",
"Documentation in MD format."),
clEnumValN(OutputFormatTy::html, "html",
- "Documentation in HTML format.")),
+ "Documentation in HTML format."),
+ clEnumValN(OutputFormatTy::mhtml, "mhtml",
+ "Documentation in mHTML format")),
llvm::cl::init(OutputFormatTy::yaml),
llvm::cl::cat(ClangDocCategory));
@@ -124,6 +127,8 @@ std::string getFormatString() {
return "md";
case OutputFormatTy::html:
return "html";
+ case OutputFormatTy::mhtml:
+ return "mhtml";
}
llvm_unreachable("Unknown OutputFormatTy");
}
@@ -227,6 +232,8 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0,
llvm::sys::path::native(AssetsPath, MustacheTemplate);
llvm::sys::path::append(MustacheTemplate, "template.mustache");
CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()});
+
+ return llvm::Error::success();
}
/// Make the output of clang-doc deterministic by sorting the children of
diff --git a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
index e5c5d4c9e4412b..5354032f4d8323 100644
--- a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
+++ b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
@@ -26,5 +26,3 @@ class Shape {
*/
virtual double perimeter() const = 0;
};
-
-
>From 1cf2b18b4066407a722ce7b84736dc32f6c2fbcd Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Tue, 24 Sep 2024 16:48:29 -0400
Subject: [PATCH 43/45] [clang-doc] update
---
.../clang-doc/HTMLMustacheGenerator.cpp | 16 ++++++++---
.../clang-doc/assets/comments.mustache | 27 +++++++++++++++++++
.../clang-doc/assets/template.mustache | 7 +++--
.../Inputs/basic-project/include/Shape.h | 2 ++
4 files changed, 46 insertions(+), 6 deletions(-)
create mode 100644 clang-tools-extra/clang-doc/assets/comments.mustache
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index bfa563ebdc7b37..96a685dbe2ae87 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -194,8 +194,12 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir,
Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)});
Value ParamArr = Array();
- for (const auto &P : I.Params) {
- ParamArr.getAsArray()->emplace_back(extractValue(P.Type, ParentInfoDir));
+ for (const auto Val : llvm::enumerate(I.Params)) {
+ Value V = Object();
+ V.getAsObject()->insert({"Name", Val.value().Name});
+ V.getAsObject()->insert({"Type", Val.value().Type.Name});
+ V.getAsObject()->insert({"End", Val.index() + 1 == I.Params.size()});
+ ParamArr.getAsArray()->emplace_back(V);
}
Obj.insert({"Params", ParamArr});
@@ -328,17 +332,21 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
switch (I->IT) {
case InfoType::IT_namespace: {
Value V = extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
- llvm::outs() << V << "\n";
- OS << NamespaceTemplate->render(V);
+ llvm::raw_ostream &OS = llvm::outs();
+ llvm::json::OStream J(OS, /*IndentSize=*/2);
+ J.value(V);
break;
}
case InfoType::IT_record:
break;
case InfoType::IT_enum:
+ llvm::outs() << "IT_enum\n";
break;
case InfoType::IT_function:
+ llvm::outs() << "IT_Function\n";
break;
case InfoType::IT_typedef:
+ llvm::outs() << "IT_typedef\n";
break;
case InfoType::IT_default:
return createStringError(llvm::inconvertibleErrorCode(),
diff --git a/clang-tools-extra/clang-doc/assets/comments.mustache b/clang-tools-extra/clang-doc/assets/comments.mustache
new file mode 100644
index 00000000000000..1eac4de91836ab
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/comments.mustache
@@ -0,0 +1,27 @@
+{{!
+ 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
+
+ This file defines templates for generating
+}}
+
+{{#FullComments}}
+ {{#Children}}
+ {{>Comment}}
+ {{/Children}}
+{{/FullComments}}
+{{#ParagraphComment}}
+ {{#Children}}
+ {{>Comment}}
+ {{/Children}}
+{{/ParagraphComment}}
+{{#BlockCommandComment}}
+ <div>{{Command}}</div>
+ {{#Children}}
+ {{>Comment}}
+ {{/Children}}
+{{/BlockCommandComment}}
+{{#TextComment}}
+ <p>{{TextComment}}</p>
+{{/TextComment}}
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/template.mustache b/clang-tools-extra/clang-doc/assets/template.mustache
index 1d3407f8b52920..3b77e0189f4e22 100644
--- a/clang-tools-extra/clang-doc/assets/template.mustache
+++ b/clang-tools-extra/clang-doc/assets/template.mustache
@@ -3,7 +3,7 @@
See https://llvm.org/LICENSE.txt for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- This file defines the template for generating Namespaces
+ This file defines the template for generating namespaces
}}
<!DOCTYPE html>
<html lang="en-US">
@@ -13,7 +13,9 @@
</head>
<h1>{{NamespaceTitle}}</h1>
{{#NamespaceComments}}
- <p>Namespace Comment</p>
+ <div>
+ {{>Comments}}
+ </div>
{{/NamespaceComments}}
{{#Namespace}}
<h2 id="Namespace">Namespace</h2>
@@ -39,6 +41,7 @@
<h2 id="Function">Function</h2>
<div>
{{#Obj}}
+
{{/Obj}}
</div>
{{/Function}}
diff --git a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
index 5354032f4d8323..e5c5d4c9e4412b 100644
--- a/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
+++ b/clang-tools-extra/test/clang-doc/Inputs/basic-project/include/Shape.h
@@ -26,3 +26,5 @@ class Shape {
*/
virtual double perimeter() const = 0;
};
+
+
>From f071bb77168252eb48fbfa05ab24ac6bd41c2e93 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Wed, 25 Sep 2024 05:08:40 -0400
Subject: [PATCH 44/45] [llvm] add mustache verification tool
---
llvm/tools/mustache/CMakeLists.txt | 3 +
llvm/tools/mustache/mustache.cpp | 102 +++++++++++++++++++++++++++++
2 files changed, 105 insertions(+)
create mode 100644 llvm/tools/mustache/CMakeLists.txt
create mode 100644 llvm/tools/mustache/mustache.cpp
diff --git a/llvm/tools/mustache/CMakeLists.txt b/llvm/tools/mustache/CMakeLists.txt
new file mode 100644
index 00000000000000..721b553741d05b
--- /dev/null
+++ b/llvm/tools/mustache/CMakeLists.txt
@@ -0,0 +1,3 @@
+set(LLVM_LINK_COMPONENTS Support)
+
+ add_llvm_tool(mustache mustache.cpp)
\ No newline at end of file
diff --git a/llvm/tools/mustache/mustache.cpp b/llvm/tools/mustache/mustache.cpp
new file mode 100644
index 00000000000000..07b7c0dc528696
--- /dev/null
+++ b/llvm/tools/mustache/mustache.cpp
@@ -0,0 +1,102 @@
+//===- mustache.cpp - The LLVM Modular Optimizer
+//-------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Simple drivers to test the mustache spec found here
+// https://github.com/mustache/
+// It is used to verify that the current implementation conforms to the spec
+// simply download the spec and pass the test files to the driver
+//
+// Currently Triple Mustache is not supported we expect the following spec
+// test to fail:
+// Triple Mustache
+// Triple Mustache Integer Interpolation
+// Triple Mustache Decimal Interpolation
+// Triple Mustache Null Interpolation
+// Triple Mustache Context Miss Interpolation
+// Dotted Names - Triple Mustache Interpolation
+// Implicit Iterators - Triple Mustache
+// Triple Mustache - Surrounding Whitespace
+// Triple Mustache - Standalone
+// Triple Mustache With Padding
+// Standalone Indentation
+// Implicit Iterator - Triple mustache
+//
+// Usage:
+// mustache path/to/test/file/test.json path/to/test/file/test2.json ...
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/Mustache.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include <string>
+
+using namespace llvm;
+using namespace llvm::json;
+using namespace llvm::mustache;
+
+cl::list<std::string> InputFiles(cl::Positional, cl::desc("<input files>"),
+ cl::OneOrMore);
+
+void runThroughTest(StringRef InputFile) {
+ llvm::outs() << "Running Tests: " << InputFile << "\n";
+ ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> BufferOrError =
+ MemoryBuffer::getFile(InputFile);
+
+ if (auto EC = BufferOrError.getError()) {
+ return;
+ }
+ std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
+ llvm::StringRef FileContent = Buffer->getBuffer();
+ Expected<Value> Json = parse(FileContent);
+
+ if (auto E = Json.takeError()) {
+ errs() << "Parsing error: " << toString(std::move(E)) << "\n";
+ return;
+ }
+ // Get test
+ Array *Obj = (*Json).getAsObject()->getArray("tests");
+ size_t Total = 0;
+ size_t Success = 0;
+ for (Value V : *Obj) {
+ Object *TestCase = V.getAsObject();
+ StringRef TemplateStr = TestCase->getString("template").value();
+ StringRef ExpectedStr = TestCase->getString("expected").value();
+ StringRef Name = TestCase->getString("name").value();
+ Value *Data = TestCase->get("data");
+ Value *Partials = TestCase->get("partials");
+
+ if (!Data)
+ continue;
+
+ Template T = Template(TemplateStr);
+ if (Partials) {
+ for (auto PartialPairs : *Partials->getAsObject()) {
+ StringRef Partial = PartialPairs.getSecond().getAsString().value();
+ StringRef Str = llvm::StringRef(PartialPairs.getFirst());
+ T.registerPartial(Str, Partial);
+ }
+ }
+ StringRef ActualStr = T.render(*Data);
+ if (ExpectedStr == ActualStr) {
+ Success++;
+ } else {
+ llvm::outs() << "Test Failed: " << Name << "\n";
+ }
+ Total++;
+ }
+
+ llvm::outs() << "Result " << Success << "/" << Total << " succeeded\n";
+}
+int main(int argc, char **argv) {
+ llvm::cl::ParseCommandLineOptions(argc, argv);
+ for (const auto &FileName : InputFiles) {
+ runThroughTest(FileName);
+ }
+ return 0;
+}
\ No newline at end of file
>From ca1331529c9e751a4ffed9f32d421eec39839622 Mon Sep 17 00:00:00 2001
From: PeterChou1 <peter.chou at mail.utoronto.ca>
Date: Mon, 7 Oct 2024 15:07:41 -0400
Subject: [PATCH 45/45] [clang-doc] add more templates
---
clang-tools-extra/clang-doc/CMakeLists.txt | 5 +-
.../clang-doc/FileHelpersClangDoc.cpp | 75 +++
.../clang-doc/FileHelpersClangDoc.h | 26 +
clang-tools-extra/clang-doc/HTMLGenerator.cpp | 59 +--
.../clang-doc/HTMLMustacheGenerator.cpp | 281 ++++++++---
.../clang-doc/assets/clang-doc-mustache.css | 476 ++++++++++++++++++
.../clang-doc/assets/class-template.mustache | 227 +++++++++
.../assets/comments-template.mustache | 34 ++
.../clang-doc/assets/comments.mustache | 27 -
.../clang-doc/assets/enum-template.mustache | 47 ++
.../assets/function-template.mustache | 23 +
.../clang-doc/assets/mustache-index.js | 33 ++
.../assets/namespace-template.mustache | 87 ++++
.../clang-doc/assets/template.mustache | 55 --
.../clang-doc/tool/CMakeLists.txt | 8 +-
.../clang-doc/tool/ClangDocMain.cpp | 53 +-
16 files changed, 1296 insertions(+), 220 deletions(-)
create mode 100644 clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp
create mode 100644 clang-tools-extra/clang-doc/FileHelpersClangDoc.h
create mode 100644 clang-tools-extra/clang-doc/assets/clang-doc-mustache.css
create mode 100644 clang-tools-extra/clang-doc/assets/class-template.mustache
create mode 100644 clang-tools-extra/clang-doc/assets/comments-template.mustache
delete mode 100644 clang-tools-extra/clang-doc/assets/comments.mustache
create mode 100644 clang-tools-extra/clang-doc/assets/enum-template.mustache
create mode 100644 clang-tools-extra/clang-doc/assets/function-template.mustache
create mode 100644 clang-tools-extra/clang-doc/assets/mustache-index.js
create mode 100644 clang-tools-extra/clang-doc/assets/namespace-template.mustache
delete mode 100644 clang-tools-extra/clang-doc/assets/template.mustache
diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt
index 82aded940222db..0793c343305184 100644
--- a/clang-tools-extra/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/CMakeLists.txt
@@ -8,15 +8,16 @@ add_clang_library(clangDoc
BitcodeReader.cpp
BitcodeWriter.cpp
ClangDoc.cpp
+ FileHelpersClangDoc.cpp
Generators.cpp
HTMLGenerator.cpp
+ HTMLMustacheGenerator.cpp
Mapper.cpp
MDGenerator.cpp
Representation.cpp
Serialize.cpp
YAMLGenerator.cpp
- HTMLMustacheGenerator.cpp
-
+
DEPENDS
omp_gen
ClangDriverOptions
diff --git a/clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp b/clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp
new file mode 100644
index 00000000000000..50209cfac1ca3d
--- /dev/null
+++ b/clang-tools-extra/clang-doc/FileHelpersClangDoc.cpp
@@ -0,0 +1,75 @@
+//===-- FileHelpersClangDoc.cpp - File Helpers -------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+#include "FileHelpersClangDoc.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+
+namespace clang {
+namespace doc {
+
+llvm::Error
+copyFile(llvm::StringRef FilePath, llvm::StringRef OutDirectory) {
+ llvm::SmallString<128> PathWrite;
+ llvm::sys::path::native(OutDirectory, PathWrite);
+ llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath));
+ llvm::SmallString<128> PathRead;
+ llvm::sys::path::native(FilePath, PathRead);
+ std::error_code OK;
+ std::error_code FileErr = llvm::sys::fs::copy_file(PathRead, PathWrite);
+ if (FileErr != OK) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "error creating file " +
+ llvm::sys::path::filename(FilePath) +
+ ": " + FileErr.message() + "\n");
+ }
+ return llvm::Error::success();
+}
+
+
+llvm::SmallString<128> computeRelativePath(llvm::StringRef Destination,
+ llvm::StringRef Origin) {
+ // If Origin is empty, the relative path to the Destination is its complete
+ // path.
+ if (Origin.empty())
+ return Destination;
+
+ // The relative path is an empty path if both directories are the same.
+ if (Destination == Origin)
+ return {};
+
+ // These iterators iterate through each of their parent directories
+ llvm::sys::path::const_iterator FileI = llvm::sys::path::begin(Destination);
+ llvm::sys::path::const_iterator FileE = llvm::sys::path::end(Destination);
+ llvm::sys::path::const_iterator DirI = llvm::sys::path::begin(Origin);
+ llvm::sys::path::const_iterator DirE = llvm::sys::path::end(Origin);
+ // Advance both iterators until the paths differ. Example:
+ // Destination = A/B/C/D
+ // Origin = A/B/E/F
+ // FileI will point to C and DirI to E. The directories behind them is the
+ // directory they share (A/B).
+ while (FileI != FileE && DirI != DirE && *FileI == *DirI) {
+ ++FileI;
+ ++DirI;
+ }
+ llvm::SmallString<128> Result; // This will hold the resulting path.
+ // Result has to go up one directory for each of the remaining directories in
+ // Origin
+ while (DirI != DirE) {
+ llvm::sys::path::append(Result, "..");
+ ++DirI;
+ }
+ // Result has to append each of the remaining directories in Destination
+ while (FileI != FileE) {
+ llvm::sys::path::append(Result, *FileI);
+ ++FileI;
+ }
+ return Result;
+}
+
+} // namespace doc
+} // namespace clang
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/FileHelpersClangDoc.h b/clang-tools-extra/clang-doc/FileHelpersClangDoc.h
new file mode 100644
index 00000000000000..9072a7bd08a4f8
--- /dev/null
+++ b/clang-tools-extra/clang-doc/FileHelpersClangDoc.h
@@ -0,0 +1,26 @@
+//===-- FileHelpersClangDoc.h --- File Helpers -------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_FILEHELPER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_FILEHELPER_H
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace doc {
+
+llvm::Error
+copyFile(llvm::StringRef FilePath, llvm::StringRef OutDirectory);
+
+llvm::SmallString<128>
+computeRelativePath(llvm::StringRef Destination,llvm::StringRef Origin);
+
+} // namespace doc
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_FILEHELPER_H
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index ed65ac965114b5..aa76d17d9411d6 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -8,6 +8,7 @@
#include "Generators.h"
#include "Representation.h"
+#include "FileHelpersClangDoc.h"
#include "clang/Basic/Version.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
@@ -251,46 +252,6 @@ static void appendVector(std::vector<Derived> &&New,
std::move(New.begin(), New.end(), std::back_inserter(Original));
}
-// Compute the relative path from an Origin directory to a Destination directory
-static SmallString<128> computeRelativePath(StringRef Destination,
- StringRef Origin) {
- // If Origin is empty, the relative path to the Destination is its complete
- // path.
- if (Origin.empty())
- return Destination;
-
- // The relative path is an empty path if both directories are the same.
- if (Destination == Origin)
- return {};
-
- // These iterators iterate through each of their parent directories
- llvm::sys::path::const_iterator FileI = llvm::sys::path::begin(Destination);
- llvm::sys::path::const_iterator FileE = llvm::sys::path::end(Destination);
- llvm::sys::path::const_iterator DirI = llvm::sys::path::begin(Origin);
- llvm::sys::path::const_iterator DirE = llvm::sys::path::end(Origin);
- // Advance both iterators until the paths differ. Example:
- // Destination = A/B/C/D
- // Origin = A/B/E/F
- // FileI will point to C and DirI to E. The directories behind them is the
- // directory they share (A/B).
- while (FileI != FileE && DirI != DirE && *FileI == *DirI) {
- ++FileI;
- ++DirI;
- }
- SmallString<128> Result; // This will hold the resulting path.
- // Result has to go up one directory for each of the remaining directories in
- // Origin
- while (DirI != DirE) {
- llvm::sys::path::append(Result, "..");
- ++DirI;
- }
- // Result has to append each of the remaining directories in Destination
- while (FileI != FileE) {
- llvm::sys::path::append(Result, *FileI);
- ++FileI;
- }
- return Result;
-}
// HTML generation
@@ -1133,24 +1094,6 @@ static llvm::Error genIndex(const ClangDocContext &CDCtx) {
return llvm::Error::success();
}
-static llvm::Error
-copyFile(StringRef FilePath, StringRef OutDirectory) {
- llvm::SmallString<128> PathWrite;
- llvm::sys::path::native(OutDirectory, PathWrite);
- llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath));
- llvm::SmallString<128> PathRead;
- llvm::sys::path::native(FilePath, PathRead);
- std::error_code OK;
- std::error_code FileErr = llvm::sys::fs::copy_file(PathRead, PathWrite);
- if (FileErr != OK) {
- return llvm::createStringError(llvm::inconvertibleErrorCode(),
- "error creating file " +
- llvm::sys::path::filename(FilePath) +
- ": " + FileErr.message() + "\n");
- }
- return llvm::Error::success();
-}
-
llvm::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) {
auto Err = serializeIndex(CDCtx);
if (Err)
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 96a685dbe2ae87..6be4c795a68654 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "Generators.h"
#include "Representation.h"
+#include "FileHelpersClangDoc.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Mustache.h"
@@ -48,9 +49,8 @@ class MustacheTemplateFile : public Template {
Error registerPartialFile(StringRef Name, StringRef FileName) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrError =
MemoryBuffer::getFile(FileName);
- if (auto EC = BufferOrError.getError()) {
+ if (auto EC = BufferOrError.getError())
return llvm::createFileError("cannot open file", EC);
- }
std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrError.get());
llvm::StringRef FileContent = Buffer->getBuffer();
registerPartial(Name, FileContent);
@@ -65,24 +65,54 @@ static std::unique_ptr<MustacheTemplateFile> NamespaceTemplate = nullptr;
static std::unique_ptr<MustacheTemplateFile> RecordTemplate = nullptr;
+llvm::Error setupTemplate(
+ std::unique_ptr<MustacheTemplateFile> &Template,
+ StringRef TemplatePath,
+ std::vector<std::pair<StringRef, StringRef>> Partials) {
+ auto T = MustacheTemplateFile::createMustacheFile(TemplatePath);
+ if (auto EC = T.getError())
+ return llvm::createFileError("cannot open file", EC);
+ Template = std::move(T.get());
+ for (const auto &P : Partials) {
+ auto Err = Template->registerPartialFile(P.first, P.second);
+ if (Err)
+ return Err;
+ }
+ return llvm::Error::success();
+}
+
llvm::Error
setupTemplateFiles(const clang::doc::ClangDocContext &CDCtx) {
- auto TemplateFilePath = CDCtx.MustacheTemplates.lookup("template");
- auto Template = MustacheTemplateFile::createMustacheFile(TemplateFilePath);
- if (auto EC = Template.getError()) {
- return llvm::createFileError("cannot open file", EC);
- }
- NamespaceTemplate = std::move(Template.get());
+ auto NamespaceFilePath = CDCtx.MustacheTemplates.lookup("namespace-template");
+ auto ClassFilePath = CDCtx.MustacheTemplates.lookup("class-template");
+ auto CommentFilePath = CDCtx.MustacheTemplates.lookup("comments-template");
+ auto FunctionFilePath = CDCtx.MustacheTemplates.lookup("function-template");
+ auto EnumFilePath = CDCtx.MustacheTemplates.lookup("enum-template");
+ std::vector<std::pair<StringRef, StringRef>> Partials = {
+ {"Comments", CommentFilePath},
+ {"FunctionPartial", FunctionFilePath},
+ {"EnumPartial", EnumFilePath}
+ };
+
+ auto Err = setupTemplate(NamespaceTemplate, NamespaceFilePath, Partials);
+ if (Err)
+ return Err;
+
+ Err = setupTemplate(RecordTemplate, ClassFilePath, Partials);
+
+ if (Err)
+ return Err;
+
return llvm::Error::success();
}
+
llvm::Error
MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
const clang::doc::ClangDocContext &CDCtx) {
- if (auto Err = setupTemplateFiles(CDCtx)) {
+ if (auto Err = setupTemplateFiles(CDCtx))
return Err;
- }
// Track which directories we already tried to create.
llvm::StringSet<> CreatedDirs;
// Collect all output by file name and create the necessary directories.
@@ -95,10 +125,9 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
if (!CreatedDirs.contains(Path)) {
if (std::error_code Err = llvm::sys::fs::create_directories(Path);
- Err != std::error_code()) {
+ Err != std::error_code())
return llvm::createStringError(Err, "Failed to create directory '%s'.",
Path.c_str());
- }
CreatedDirs.insert(Path);
}
@@ -110,14 +139,13 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
std::error_code FileErr;
llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
llvm::sys::fs::OF_None);
- if (FileErr) {
+ if (FileErr)
return llvm::createStringError(FileErr, "Error opening file '%s'",
Group.getKey().str().c_str());
- }
+
for (const auto &Info : Group.getValue()) {
- if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
+ if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
return Err;
- }
}
}
return llvm::Error::success();
@@ -126,12 +154,15 @@ MustacheHTMLGenerator::generateDocs(llvm::StringRef RootDir,
Value extractValue(const Location &L,
std::optional<StringRef> RepositoryUrl = std::nullopt) {
Object Obj = Object();
+ Obj.insert({"LineNumber", L.LineNumber});
+ Obj.insert({"Filename", L.Filename});
+
if (!L.IsFileInRootDir || !RepositoryUrl) {
- Obj.insert({"LineNumber", L.LineNumber});
- Obj.insert({"Filename", L.Filename});
+ return Obj;
}
SmallString<128> FileURL(*RepositoryUrl);
llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename);
+ FileURL += "#" + std::to_string(L.LineNumber);
Obj.insert({"FileURL", FileURL});
return Obj;
@@ -144,6 +175,8 @@ Value extractValue(const Reference &I, StringRef CurrentDirectory) {
Object Obj = Object();
Obj.insert({"Link", Path});
Obj.insert({"Name", I.Name});
+ Obj.insert({"QualName", I.QualName});
+ Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))});
return Obj;
}
@@ -209,12 +242,13 @@ Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir,
ArrDesc.getAsArray()->emplace_back(extractValue(Child));
Obj.insert({"FunctionComments", ArrDesc});
}
- if (I.DefLoc) {
- if (!CDCtx.RepositoryUrl)
- Obj.insert({"Location", extractValue(*I.DefLoc)});
+ if (I.DefLoc.has_value()) {
+ Location L = *I.DefLoc;
+ if (CDCtx.RepositoryUrl.has_value())
+ Obj.insert({"Location", extractValue(L,
+ StringRef{*CDCtx.RepositoryUrl})});
else
- Obj.insert({"Location", extractValue(*I.DefLoc,
- StringRef{*CDCtx.RepositoryUrl})});
+ Obj.insert({"Location", extractValue(L)});
}
return Obj;
}
@@ -229,7 +263,6 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) {
Obj.insert({"EnumName", EnumType});
Obj.insert({"HasComment", HasComment});
Obj.insert({"ID", llvm::toHex(llvm::toStringRef(I.USR))});
-
Value Arr = Array();
for (const EnumValueInfo& M: I.Members) {
Value EnumValue = Object();
@@ -256,75 +289,175 @@ Value extractValue(const EnumInfo &I, const ClangDocContext &CDCtx) {
Obj.insert({"EnumComments", ArrDesc});
}
- if (I.DefLoc) {
- if (!CDCtx.RepositoryUrl)
- Obj.insert({"Location", extractValue(*I.DefLoc)});
+ if (I.DefLoc.has_value()) {
+ Location L = *I.DefLoc;
+ if (CDCtx.RepositoryUrl.has_value())
+ Obj.insert({"Location", extractValue(L,
+ StringRef{*CDCtx.RepositoryUrl})});
else
- Obj.insert({"Location", extractValue(*I.DefLoc,
- StringRef{*CDCtx.RepositoryUrl})});
+ Obj.insert({"Location", extractValue(L)});
}
return Obj;
}
-Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) {
- Object NamespaceValue = Object();
- std::string InfoTitle;
- if (I.Name.str() == "")
- InfoTitle = "Global Namespace";
- else
- InfoTitle = ("namespace " + I.Name).str();
-
- StringRef BasePath = I.getRelativeFilePath("");
- NamespaceValue.insert({"NamespaceTitle", InfoTitle});
- NamespaceValue.insert({"NamespacePath", I.getRelativeFilePath("")});
-
- if (!I.Description.empty()) {
- Value ArrDesc = Array();
- for (const CommentInfo& Child : I.Description)
- ArrDesc.getAsArray()->emplace_back(extractValue(Child));
- NamespaceValue.insert({"NamespaceComments", ArrDesc });
- }
-
+void extractScopeChildren(const ScopeChildren &S, Object &Obj,
+ StringRef ParentInfoDir,
+ const ClangDocContext &CDCtx) {
Value ArrNamespace = Array();
- for (const Reference& Child : I.Children.Namespaces)
- ArrNamespace.getAsArray()->emplace_back(extractValue(Child, BasePath));
+ for (const Reference& Child : S.Namespaces)
+ ArrNamespace.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir));
if (!ArrNamespace.getAsArray()->empty())
- NamespaceValue.insert({"Namespace", Object{{"Links", ArrNamespace}}});
+ Obj.insert({"Namespace", Object{{"Links", ArrNamespace}}});
Value ArrRecord = Array();
- for (const Reference& Child : I.Children.Records)
- ArrRecord.getAsArray()->emplace_back(extractValue(Child, BasePath));
+ for (const Reference& Child : S.Records)
+ ArrRecord.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir));
if (!ArrRecord.getAsArray()->empty())
- NamespaceValue.insert({"Record", Object{{"Links", ArrRecord}}});
+ Obj.insert({"Record", Object{{"Links", ArrRecord}}});
Value ArrFunction = Array();
- for (const FunctionInfo& Child : I.Children.Functions)
- ArrFunction.getAsArray()->emplace_back(extractValue(Child, BasePath,
- CDCtx));
+ Value PublicFunction = Array();
+ Value ProtectedFunction = Array();
+ Value PrivateFunction = Array();
+
+ for (const FunctionInfo& Child : S.Functions) {
+ Value F = extractValue(Child, ParentInfoDir, CDCtx);
+ AccessSpecifier Access = Child.Access;
+ if (Access == AccessSpecifier::AS_public)
+ PublicFunction.getAsArray()->emplace_back(F);
+ else if (Access == AccessSpecifier::AS_protected)
+ ProtectedFunction.getAsArray()->emplace_back(F);
+ else
+ ArrFunction.getAsArray()->emplace_back(F);
+ }
if (!ArrFunction.getAsArray()->empty())
- NamespaceValue.insert({"Function", Object{{"Obj", ArrFunction}}});
+ Obj.insert({"Function", Object{{"Obj", ArrFunction}}});
+
+ if (!PublicFunction.getAsArray()->empty())
+ Obj.insert({"PublicFunction", Object{{"Obj", PublicFunction}}});
+
+ if (!ProtectedFunction.getAsArray()->empty())
+ Obj.insert({"ProtectedFunction", Object{{"Obj", ProtectedFunction}}});
+
Value ArrEnum = Array();
- for (const EnumInfo& Child : I.Children.Enums)
+ for (const EnumInfo& Child : S.Enums)
ArrEnum.getAsArray()->emplace_back(extractValue(Child, CDCtx));
if (!ArrEnum.getAsArray()->empty())
- NamespaceValue.insert({"Enums", Object{{"Obj", ArrEnum }}});
+ Obj.insert({"Enums", Object{{"Obj", ArrEnum }}});
Value ArrTypedefs = Array();
- for (const TypedefInfo& Child : I.Children.Typedefs)
+ for (const TypedefInfo& Child : S.Typedefs)
ArrTypedefs.getAsArray()->emplace_back(extractValue(Child));
if (!ArrTypedefs.getAsArray()->empty())
- NamespaceValue.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}});
+ Obj.insert({"Typedefs", Object{{"Obj", ArrTypedefs }}});
+}
+
+Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) {
+ Object NamespaceValue = Object();
+ std::string InfoTitle;
+ if (I.Name.str() == "")
+ InfoTitle = "Global Namespace";
+ else
+ InfoTitle = ("namespace " + I.Name).str();
+
+ StringRef BasePath = I.getRelativeFilePath("");
+ NamespaceValue.insert({"NamespaceTitle", InfoTitle});
+ NamespaceValue.insert({"NamespacePath", I.getRelativeFilePath("")});
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ NamespaceValue.insert({"NamespaceComments", ArrDesc });
+ }
+ extractScopeChildren(I.Children, NamespaceValue, BasePath, CDCtx);
return NamespaceValue;
}
+Value extractValue(const RecordInfo &I, const ClangDocContext &CDCtx) {
+ Object RecordValue = Object();
+
+ if (!I.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : I.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ RecordValue.insert({"RecordComments", ArrDesc });
+ }
+ RecordValue.insert({"Name", I.Name});
+ RecordValue.insert({"FullName", I.FullName});
+ RecordValue.insert({"RecordType", getTagType(I.TagType)});
+
+ if (I.DefLoc.has_value()) {
+ Location L = *I.DefLoc;
+ if (CDCtx.RepositoryUrl.has_value())
+ RecordValue.insert({"Location", extractValue(L,
+ StringRef{*CDCtx.RepositoryUrl})});
+ else
+ RecordValue.insert({"Location", extractValue(L)});
+ }
+
+ StringRef BasePath = I.getRelativeFilePath("");
+ extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx);
+ Value PublicMembers = Array();
+ Value ProtectedMembers = Array();
+ Value PrivateMembers = Array();
+ for (const MemberTypeInfo &Member : I.Members ) {
+ Value MemberValue = Object();
+ MemberValue.getAsObject()->insert({"Name", Member.Name});
+ MemberValue.getAsObject()->insert({"Type", Member.Type.Name});
+ if (!Member.Description.empty()) {
+ Value ArrDesc = Array();
+ for (const CommentInfo& Child : Member.Description)
+ ArrDesc.getAsArray()->emplace_back(extractValue(Child));
+ MemberValue.getAsObject()->insert({"MemberComments", ArrDesc });
+ }
+
+ if (Member.Access == AccessSpecifier::AS_public)
+ PublicMembers.getAsArray()->emplace_back(MemberValue);
+ else if (Member.Access == AccessSpecifier::AS_protected)
+ ProtectedMembers.getAsArray()->emplace_back(MemberValue);
+ else if (Member.Access == AccessSpecifier::AS_private)
+ PrivateMembers.getAsArray()->emplace_back(MemberValue);
+ }
+ if (!PublicMembers.getAsArray()->empty())
+ RecordValue.insert({"PublicMembers", Object{{"Obj", PublicMembers}}});
+ if (!ProtectedMembers.getAsArray()->empty())
+ RecordValue.insert({"ProtectedMembers", Object{{"Obj", ProtectedMembers}}});
+ if (!PrivateMembers.getAsArray()->empty())
+ RecordValue.insert({"PrivateMembers", Object{{"Obj", PrivateMembers}}});
+
+ return RecordValue;
+}
+void setupTemplateValue(const ClangDocContext &CDCtx, Value &V, Info *I) {
+ V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName});
+ Value StylesheetArr = Array();
+ auto InfoPath = I->getRelativeFilePath("");
+ SmallString<128> RelativePath = computeRelativePath("", InfoPath);
+ for (const auto &FilePath : CDCtx.UserStylesheets) {
+ SmallString<128> StylesheetPath = RelativePath;
+ llvm::sys::path::append(StylesheetPath,
+ llvm::sys::path::filename(FilePath));
+ llvm::sys::path::native(StylesheetPath, llvm::sys::path::Style::posix);
+ StylesheetArr.getAsArray()->emplace_back(StylesheetPath);
+ }
+ V.getAsObject()->insert({"Stylesheets", StylesheetArr});
+
+ Value ScriptArr = Array();
+ for (auto Script : CDCtx.JsScripts) {
+ SmallString<128> JsPath = RelativePath;
+ llvm::sys::path::append(JsPath, llvm::sys::path::filename(Script));
+ ScriptArr.getAsArray()->emplace_back(JsPath);
+ }
+ V.getAsObject()->insert({"Scripts", ScriptArr});
+}
+
llvm::Error
MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
@@ -332,13 +465,18 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
switch (I->IT) {
case InfoType::IT_namespace: {
Value V = extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
- llvm::raw_ostream &OS = llvm::outs();
- llvm::json::OStream J(OS, /*IndentSize=*/2);
- J.value(V);
+ setupTemplateValue(CDCtx, V, I);
+ OS << NamespaceTemplate->render(V);
break;
}
- case InfoType::IT_record:
+ case InfoType::IT_record: {
+ Value V = extractValue(*static_cast<clang::doc::RecordInfo *>(I), CDCtx);
+ setupTemplateValue(CDCtx, V, I);
+ // Serialize the JSON value to the output stream in a readable format.
+ llvm::outs() << llvm::formatv("{0:2}", V);
+ OS << RecordTemplate->render(V);
break;
+ }
case InfoType::IT_enum:
llvm::outs() << "IT_enum\n";
break;
@@ -356,6 +494,17 @@ MustacheHTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
}
llvm::Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) {
+ llvm::Error Err = llvm::Error::success();
+ for (const auto &FilePath : CDCtx.UserStylesheets) {
+ Err = copyFile(FilePath, CDCtx.OutDirectory);
+ if (Err)
+ return Err;
+ }
+ for (const auto &FilePath : CDCtx.JsScripts) {
+ Err = copyFile(FilePath, CDCtx.OutDirectory);
+ if (Err)
+ return Err;
+ }
return llvm::Error::success();
}
diff --git a/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css b/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css
new file mode 100644
index 00000000000000..2f07baa7ca0a1a
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/clang-doc-mustache.css
@@ -0,0 +1,476 @@
+ at import "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap";
+
+*,*::before *::after {
+ box-sizing:border-box
+}
+* {
+ margin:0;
+ padding:0
+}
+ol,
+ul {
+ list-style:none
+}
+img,
+picture,
+svg,
+video {
+ display:block;
+ max-width:100%
+}
+* {
+ --brand-light:#ce6300;
+ --text1-light:#000000;
+ --text2-light:#333333;
+ --surface1-light:#ffffff;
+ --surface2-light:#f5f5f5;
+ --brand-dark:#de9853;
+ --text1-dark:#ffffff;
+ --text2-dark:#cccccc;
+ --surface1-dark:#161212;
+ --surface2-dark:#272424
+}
+:root {
+ color-scheme:light;
+ --brand:var(--brand-light);
+ --text1:var(--text1-light);
+ --text2:var(--text2-light);
+ --text1-inverse:var(--text1-dark);
+ --text2-inverse:var(--text2-dark);
+ --surface1:var(--surface1-light);
+ --surface2:var(--surface2-light)
+}
+ at media(prefers-color-scheme:dark) {
+ :root {
+ color-scheme:dark;
+ --brand:var(--brand-dark);
+ --text1:var(--text1-dark);
+ --text2:var(--text2-dark);
+ --text1-inverse:var(--text1-light);
+ --text2-inverse:var(--text2-light);
+ --surface1:var(--surface1-dark);
+ --surface2:var(--surface2-dark)
+ }
+}
+[color-scheme=light] {
+ color-scheme:light;
+ --brand:var(--brand-light);
+ --text1:var(--text1-light);
+ --text2:var(--text2-light);
+ --text1-inverse:var(--text1-dark);
+ --text2-inverse:var(--text2-dark);
+ --surface1:var(--surface1-light);
+ --surface2:var(--surface2-light)
+}
+[color-scheme=dark] {
+ color-scheme:dark;
+ --brand:var(--brand-dark);
+ --text1:var(--text1-dark);
+ --text2:var(--text2-dark);
+ --text1-inverse:var(--text1-light);
+ --text2-inverse:var(--text2-light);
+ --surface1:var(--surface1-dark);
+ --surface2:var(--surface2-dark)
+}
+html {
+ background-color:var(--surface1)
+}
+
+html, body {
+ min-height: 100vh;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+.container {
+ display: flex;
+ margin-top: 60px;
+ height: calc(100% - 60px);
+ box-sizing: border-box;
+}
+
+body, html {
+ font-family:Inter,sans-serif;
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+
+/* Navbar Styles */
+.navbar {
+ background-color: var(--surface2);
+ border-bottom: 1px solid var(--text2);
+ position: fixed;
+ width: 100%;
+ top: 0;
+ left: 0;
+ height: 60px; /* Adjust as needed */
+ color: white;
+ display: flex;
+ align-items: center;
+ padding: 0 20px;
+ box-sizing: border-box;
+ z-index: 1000;
+}
+
+
+.navbar__container {
+ display:flex;
+ justify-content:space-between;
+ align-items:center;
+ padding:1rem;
+ color:var(--text1);
+ max-width:2048px;
+ margin:auto
+}
+.navbar__logo {
+ display:flex;
+ align-items:center;
+ height:40px
+}
+.navbar__logo a {
+ display:flex;
+ align-items:center;
+ text-decoration:none;
+ height:100%
+}
+.navbar__logo img {
+ height:100%;
+ width:auto
+}
+.navbar__toggle {
+ background:0 0;
+ color:var(--text2);
+ border:none;
+ cursor:pointer;
+ font-size:1.5rem;
+ width:2.5rem;
+ height:2.5rem;
+ margin-left:auto
+}
+.navbar__toggle:hover {
+ color:var(--text1)
+}
+ at media(min-width:769px) {
+ .navbar__toggle {
+ display:none
+ }
+}
+.navbar__menu {
+ display:flex;
+ justify-content:space-between;
+ align-items:center;
+ list-style:none;
+ margin:0;
+ padding:0;
+ gap:.25rem;
+ margin-left:auto
+}
+
+ at media(max-width:768px) {
+ .navbar__menu {
+ flex-direction:column;
+ justify-content:flex-start;
+ width:100%;
+ background-color:var(--surface2);
+ position:fixed;
+ top:0;
+ left:0;
+ right:0;
+ bottom:0;
+ padding:1.5rem;
+ transform:translateX(100%);
+ transition:transform .5s ease-in-out
+ }
+}
+ at media(max-width:768px) {
+ .navbar__menu.active {
+ transform:translateX(0)
+ }
+}
+.navbar__close {
+ background:0 0;
+ border:none;
+ cursor:pointer;
+ font-size:1.5rem;
+ color:var(--text2);
+ margin-left:auto
+}
+.navbar__close:hover {
+ color:var(--text1)
+}
+ at media(min-width:769px) {
+ .navbar__close {
+ display:none
+ }
+}
+.navbar__links {
+ display:flex;
+ gap:1rem;
+ align-items:center;
+ margin:0;
+ padding:0
+}
+ at media(max-width:768px) {
+ .navbar__links {
+ flex-direction:column
+ }
+}
+.navbar__item {
+ list-style-type:none
+}
+.navbar__link {
+ color:var(--text2);
+ text-decoration:none;
+ padding:.5rem
+}
+.navbar__link:hover {
+ color:var(--text1)
+}
+.navbar__theme-toggle-button {
+ background:0 0;
+ color:var(--text2);
+ border:none;
+ cursor:pointer;
+ font-size:1.5rem;
+ width:2.5rem;
+ height:2.5rem
+}
+.navbar__theme-toggle-button:hover {
+ color:var(--text1)
+}
+
+.hero__container {
+ margin-top:1rem;
+ display:flex;
+ justify-content:center;
+ align-items:center;
+ gap:2rem
+}
+.hero__title {
+ font-size:2.5rem;
+ margin-bottom:.5rem
+}
+.hero__title-large {
+ font-size:3rem
+}
+ at media(max-width:768px) {
+ .hero__title-large {
+ font-size:2.5rem
+ }
+}
+ at media(max-width:480px) {
+ .hero__title-large {
+ font-size:2rem
+ }
+}
+ at media(max-width:768px) {
+ .hero__title {
+ font-size:2rem
+ }
+}
+ at media(max-width:480px) {
+ .hero__title {
+ font-size:1.75rem
+ }
+}
+.hero__subtitle {
+ font-size:1.25rem;
+ font-weight:500
+}
+ at media(max-width:768px) {
+ .hero__subtitle {
+ font-size:1rem
+ }
+}
+ at media(max-width:480px) {
+ .hero__subtitle {
+ font-size:.875rem
+ }
+}
+
+.section-container {
+ max-width: 2048px;
+ margin-left:auto;
+ margin-right:auto;
+ margin-top:0;
+ margin-bottom: 1rem;
+ padding:1rem 2rem
+}
+
+
+ at media(max-width:768px) {
+ .section-container {
+ padding:1rem
+ }
+}
+.section-container h2 {
+ font-size:1.5rem;
+ margin-bottom:1rem;
+ color:var(--brand);
+ border-bottom: 1px solid var(--text2);
+}
+ at media(max-width:768px) {
+ .section-container h2 {
+ font-size:1.25rem
+ }
+}
+.section-container p {
+ font-size:1rem;
+ line-height:1.5
+}
+ at media(max-width:768px) {
+ .section-container p {
+ font-size:.875rem
+ }
+}
+.home__row {
+ display:grid;
+ grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
+ gap:2rem
+}
+
+
+.links-wrapper {
+ display:grid;
+ gap:1rem;
+ grid-template-columns:1fr 1fr 1fr 1fr
+}
+ at media(max-width:768px) {
+ .links-wrapper {
+ grid-template-columns:1fr 1fr
+ }
+}
+ at media(max-width:480px) {
+ .links-wrapper {
+ grid-template-columns:1fr
+ }
+}
+.links-wrapper .link {
+ display:flex;
+ flex-direction:column;
+ align-items:center;
+ padding:1rem;
+ border:1px solid var(--text1);
+ border-radius:.25rem
+}
+.links-wrapper .link__icon {
+ font-size:2rem
+}
+.links-wrapper .link__title {
+ font-size:1rem
+}
+
+.table-wrapper {
+ display:flex;
+ flex-direction:column;
+ padding:1rem;
+ border-collapse: collapse; /* Ensures there are no gaps between cells */
+}
+
+.table-wrapper th, .table-wrapper td {
+ padding: 0.5rem 1rem; /* Adds padding inside the cells */
+ border:1px solid var(--text1);
+ text-align: left;
+}
+
+
+.block-command-command {
+ font-weight: bold;
+}
+
+.code-clang-doc {
+ font-size: 1.1rem;
+}
+
+.delimiter-container {
+ padding: 0.5rem 1rem;
+ margin-bottom:1rem;
+}
+
+.resizer {
+ width: 5px;
+ cursor: col-resize;
+ background-color: var(--text2);
+}
+
+.resizer:hover {
+ background-color: var(--text2-inverse);
+}
+
+.sidebar {
+ width: 250px;
+ top: 0;
+ left: 0;
+ height: 100%;
+ position: fixed;
+ background-color: var(--surface1);
+ display: flex;
+ border-left: 1px solid var(--text2);
+ flex-direction: column;
+ overflow-y: auto;
+ scrollbar-width: thin;
+}
+
+.sidebar h2 {
+ margin-top: 0;
+ margin-bottom: 20px;
+ padding: 10px;
+}
+
+.sidebar ul {
+ width: 100%;
+ padding: 0;
+ list-style-type: none;
+}
+
+.sidebar ul li {
+ padding-right: 1rem;
+ padding-left: 2rem;
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+}
+
+.sidebar-section {
+ font-size:1.5rem;
+ font-weight: bold;
+ margin-bottom: 1rem;
+ padding: 3rem;
+}
+.sidebar-section a {
+ color: var(--brand)
+}
+
+
+/* Content */
+.content {
+ background-color: var(--text1-inverse);
+ padding: 20px;
+ left: 250px;
+ position: relative;
+ width: calc(100% - 250px);
+ height: 100vh;
+}
+.sidebar-item {
+ color: var(--text1);
+}
+
+.sidebar-item-container:hover {
+ width: 100%;
+ background-color: grey;
+}
+
+.sidebar-item-container:hover a {
+ width: 100%;
+ color: var(--text1-inverse);
+}
+
+.class-container {
+ padding: 0.5rem 1rem;
+}
+
+a, a:visited, a:hover, a:active {
+ text-decoration: none;
+ color: inherit;
+}
diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache b/clang-tools-extra/clang-doc/assets/class-template.mustache
new file mode 100644
index 00000000000000..7ce51c6e162110
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/class-template.mustache
@@ -0,0 +1,227 @@
+{{!
+ 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
+
+ This file defines the template for classes/struct
+}}
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <meta charset="utf-8"/>
+ <title>{{Name}}</title>
+ {{#Stylesheets}}
+ <link rel="stylesheet" type="text/css" href="{{.}}"/>
+ {{/Stylesheets}}
+ {{#Scripts}}
+ <script src="{{.}}"></script>
+ {{/Scripts}}
+ {{! Highlight.js dependency for syntax highlighting }}
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/cpp.min.js"></script>
+</head>
+<body>
+<nav class="navbar">
+ <div class="navbar__container">
+ {{#ProjectName}}
+ <div class="navbar__logo">
+ {{ProjectName}}
+ </div>
+ {{/ProjectName}}
+ <div class="navbar__menu">
+ <ul class="navbar__links">
+ <li class="navbar__item">
+ <a href="/" class="navbar__link">Namespace</a>
+ </li>
+ <li class="navbar__item">
+ <a href="/" class="navbar__link">Class</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+</nav>
+<main>
+ <div class="container">
+ <div class="sidebar">
+ <h2>{{RecordType}} {{Name}}</h2>
+ <ul>
+ {{#PublicMembers}}
+ <li class="sidebar-section">
+ <a class="sidebar-item" href="#PublicMethods">Public Members</a>
+ </li>
+ <ul>
+ {{#Obj}}
+ <li class="sidebar-item-container">
+ <a class="sidebar-item" href="#{{Name}}">{{Name}}</a>
+ </li>
+ {{/Obj}}
+ </ul>
+ {{/PublicMembers}}
+ {{#ProtectedMembers}}
+ <li class="sidebar-section">
+ <a class="sidebar-item" href="#PublicMethods">Protected Members</a>
+ </li>
+ <ul>
+ {{#Obj}}
+ <li class="sidebar-item-container">
+ <a class="sidebar-item" href="#{{Name}}">{{Name}}</a>
+ </li>
+ {{/Obj}}
+ </ul>
+ {{/ProtectedMembers}}
+ {{#PublicFunction}}
+ <li class="sidebar-section">
+ <a class="sidebar-item" href="#PublicMethods">Public Method</a>
+ </li>
+ <ul>
+ {{#Obj}}
+ <li class="sidebar-item-container">
+ <a class="sidebar-item" href="#{{ID}}">{{Name}}</a>
+ </li>
+ {{/Obj}}
+ </ul>
+ {{/PublicFunction}}
+ {{#ProtectedFunction}}
+ <li class="sidebar-section">
+ <a class="sidebar-item" href="#ProtectedFunction">Protected Method</a>
+ </li>
+ <ul>
+ {{#Obj}}
+ <li class="sidebar-item-container">
+ <a class="sidebar-item" href="#{{ID}}">{{Name}}</a>
+ </li>
+ {{/Obj}}
+ </ul>
+ {{/ProtectedFunction}}
+ {{#Enums}}
+ <li class="sidebar-section">
+ <a class="sidebar-item" href="#Enums">Enums</a>
+ </li>
+ <ul>
+ {{#Obj}}
+ <li class="sidebar-item-container">
+ <a class="sidebar-item" href="#{{ID}}">{{EnumName}}</a>
+ </li>
+ {{/Obj}}
+ </ul>
+ {{/Enums}}
+ {{#Typedef}}
+ <li class="sidebar-section">Typedef</li>
+ {{/Typedef}}
+ {{#Record}}
+ <li class="sidebar-section">
+ <a class="sidebar-item" href="#Classes">Inner Classes</a>
+ </li>
+ <ul>
+ {{#Links}}
+ <li class="sidebar-item-container">
+ <a class="sidebar-item" href="#{{ID}}">{{Name}}</a>
+ </li>
+ {{/Links}}
+ </ul>
+ {{/Record}}
+ </ul>
+ </div>
+ <div class="resizer" id="resizer"></div>
+ <div class="content">
+ <section class="hero section-container">
+ <div class="hero__title">
+ <h1 class="hero__title-large">{{RecordType}} {{Name}}</h1>
+ {{#RecordComments}}
+ <div class="hero__subtitle">
+ {{>Comments}}
+ </div>
+ {{/RecordComments}}
+ </div>
+ </section>
+ {{#PublicMembers}}
+ <section id="PublicMembers" class="section-container">
+ <h2>Public Members</h2>
+ <div>
+ {{#Obj}}
+ <div id="{{Name}}" class="delimiter-container">
+ <pre>
+<code class="language-cpp code-clang-doc" >{{Type}} {{Name}}</code>
+ </pre>
+ {{#MemberComments}}
+ <div>
+ {{>Comments}}
+ </div>
+ {{/MemberComments}}
+ </div>
+ {{/Obj}}
+ </div>
+ </section>
+ {{/PublicMembers}}
+ {{#ProtectedMembers}}
+ <section id="ProtectedMembers" class="section-container">
+ <h2>Protected Members</h2>
+ <div>
+ {{#Obj}}
+ <div id="{{Name}}" class="delimiter-container">
+ <pre>
+<code class="language-cpp code-clang-doc" >{{Type}} {{Name}}</code>
+ </pre>
+ {{#MemberComments}}
+ <div>
+ {{>Comments}}
+ </div>
+ {{/MemberComments}}
+ </div>
+ {{/Obj}}
+ </div>
+ </section>
+ {{/ProtectedMembers}}
+ {{#PublicFunction}}
+ <section id="PublicMethods" class="section-container">
+ <h2>Public Methods</h2>
+ <div>
+ {{#Obj}}
+{{>FunctionPartial}}
+ {{/Obj}}
+ </div>
+ </section>
+ {{/PublicFunction}}
+ {{#ProtectedFunction}}
+ <section id="ProtectedFunction" class="section-container">
+ <h2>Protected Methods</h2>
+ <div>
+ {{#Obj}}
+{{>FunctionPartial}}
+ {{/Obj}}
+ </div>
+ </section>
+ {{/ProtectedFunction}}
+ {{#Enums}}
+ <section id="Enums" class="section-container">
+ <h2>Enumerations</h2>
+ <div>
+ {{#Obj}}
+{{>EnumPartial}}
+ {{/Obj}}
+ </div>
+ </section>
+ {{/Enums}}
+ {{#Record}}
+ <section id="Classes" class="section-container">
+ <h2>Inner Classes</h2>
+ <ul class="class-container">
+ {{#Links}}
+ <li id="{{ID}}" style="max-height: 40px;">
+<a href="{{Link}}"><pre><code class="language-cpp code-clang-doc" >class {{Name}}</code></pre></a>
+ </li>
+ {{/Links}}
+ </ul>
+ </section>
+ {{/Record}}
+ {{#Typedef}}
+ <section class="section-container">
+ <h2 id="Enums">Enums</h2>
+ </section>
+ {{/Typedef}}
+ </div>
+ </div>
+</main>
+</body>
+</html>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/comments-template.mustache b/clang-tools-extra/clang-doc/assets/comments-template.mustache
new file mode 100644
index 00000000000000..f6b62b4407b9c4
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/comments-template.mustache
@@ -0,0 +1,34 @@
+{{!
+ 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
+
+ This file defines templates for generating comments
+}}
+{{#FullComment}}
+ {{#Children}}
+ {{>Comments}}
+ {{/Children}}
+{{/FullComment}}
+{{#ParagraphComment}}
+ {{#Children}}
+ {{>Comments}}
+ {{/Children}}
+{{/ParagraphComment}}
+{{#BlockCommandComment}}
+ <div class="block-command-comment__command">
+ <div class="block-command-command">
+ {{Command}}
+ </div>
+ <div>
+ {{#Children}}
+ {{>Comments}}
+ {{/Children}}
+ </div>
+ </div>
+{{/BlockCommandComment}}
+{{#TextComment}}
+ <div>
+ <p>{{TextComment}}</p>
+ </div>
+{{/TextComment}}
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/comments.mustache b/clang-tools-extra/clang-doc/assets/comments.mustache
deleted file mode 100644
index 1eac4de91836ab..00000000000000
--- a/clang-tools-extra/clang-doc/assets/comments.mustache
+++ /dev/null
@@ -1,27 +0,0 @@
-{{!
- 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
-
- This file defines templates for generating
-}}
-
-{{#FullComments}}
- {{#Children}}
- {{>Comment}}
- {{/Children}}
-{{/FullComments}}
-{{#ParagraphComment}}
- {{#Children}}
- {{>Comment}}
- {{/Children}}
-{{/ParagraphComment}}
-{{#BlockCommandComment}}
- <div>{{Command}}</div>
- {{#Children}}
- {{>Comment}}
- {{/Children}}
-{{/BlockCommandComment}}
-{{#TextComment}}
- <p>{{TextComment}}</p>
-{{/TextComment}}
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/enum-template.mustache b/clang-tools-extra/clang-doc/assets/enum-template.mustache
new file mode 100644
index 00000000000000..d63bf258f8f0fb
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/enum-template.mustache
@@ -0,0 +1,47 @@
+{{!
+ 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
+
+ This file defines the template for enums
+}}
+<div id="{{ID}}" class="delimiter-container">
+ <div>
+ <pre>
+ <code class="language-cpp code-clang-doc">
+{{EnumName}}
+ </code>
+ </pre>
+ </div>
+ {{! Enum Values }}
+ <table class="table-wrapper">
+ <tbody>
+ <tr>
+ <th>Name</th>
+ <th>Value</th>
+ {{#HasComment}}
+ <th>Comment</th>
+ {{/HasComment}}
+ </tr>
+ {{#EnumValues}}
+ <tr>
+ <td>{{Name}}</td>
+ <td>{{Value}}</td>
+ {{#EnumValueComments}}
+ <td>{{>Comments}}</td>
+ {{/EnumValueComments}}
+ </tr>
+ {{/EnumValues}}
+ </tbody>
+ </table>
+ {{#EnumComments}}
+ <div>
+ {{>Comments}}
+ </div>
+ {{/EnumComments}}
+ {{#Location}}
+ <div>
+ Defined at line {{LineNumber}} of file {{Filename}}
+ </div>
+ {{/Location}}
+</div>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/function-template.mustache b/clang-tools-extra/clang-doc/assets/function-template.mustache
new file mode 100644
index 00000000000000..0564647467aa60
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/function-template.mustache
@@ -0,0 +1,23 @@
+{{!
+ 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
+
+ This file defines the template for functions/methods
+}}
+<div class="delimiter-container">
+ <div id="{{ID}}">
+ {{! Function Prototype }}
+ <pre>
+ <code class="language-cpp code-clang-doc">
+{{ReturnType.Name}} {{Name}} ({{#Params}}{{^End}}{{Type}} {{Name}}, {{/End}}{{#End}}{{Type}} {{Name}}{{/End}}{{/Params}})
+ </code>
+ </pre>
+ {{! Function Comments }}
+ {{#FunctionComments}}
+ <div>
+ {{>Comments}}
+ </div>
+ {{/FunctionComments}}
+ </div>
+</div>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/mustache-index.js b/clang-tools-extra/clang-doc/assets/mustache-index.js
new file mode 100644
index 00000000000000..db320cd8ae403c
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/mustache-index.js
@@ -0,0 +1,33 @@
+document.addEventListener("DOMContentLoaded", function() {
+ const resizer = document.getElementById('resizer');
+ const sidebar = document.querySelector('.sidebar');
+
+ let isResizing = false;
+ resizer.addEventListener('mousedown', (e) => {
+ isResizing = true;
+ });
+
+ document.addEventListener('mousemove', (e) => {
+ if (!isResizing) return;
+ const newWidth = e.clientX;
+ if (newWidth > 100 && newWidth < window.innerWidth - 100) {
+ sidebar.style.width = `${newWidth}px`;
+ }
+ });
+
+ document.addEventListener('mouseup', () => {
+ isResizing = false;
+ });
+
+ document.querySelectorAll('pre code').forEach((el) => {
+ hljs.highlightElement(el);
+ el.classList.remove("hljs");
+ });
+
+ document.querySelectorAll('.sidebar-item-container').forEach(item => {
+ item.addEventListener('click', function() {
+ const anchor = item.getElementsByTagName("a");
+ window.location.hash = anchor[0].getAttribute('href');
+ });
+ });
+})
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/namespace-template.mustache b/clang-tools-extra/clang-doc/assets/namespace-template.mustache
new file mode 100644
index 00000000000000..4061fd026886e4
--- /dev/null
+++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache
@@ -0,0 +1,87 @@
+{{!
+ 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
+
+ This file defines the template for generating namespaces
+}}
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <meta charset="utf-8"/>
+ <title>{{NamespaceTitle}}</title>
+ {{#Stylesheets}}
+ <link rel="stylesheet" type="text/css" href="{{.}}"/>
+ {{/Stylesheets}}
+ {{#Scripts}}
+ <script src="{{.}}"></script>
+ {{/Scripts}}
+ {{! Highlight.js dependency for syntax highlighting }}
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/cpp.min.js"></script>
+ </head>
+ <body>
+ <nav class="navbar">
+ Navbar
+ </nav>
+ <main>
+ <div class="container">
+ <div class="sidebar">
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ sed do eiusmod tempor incididunt ut labore et dolore magna
+ aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ sed do eiusmod tempor incididunt ut labore et dolore magna
+ aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ sed do eiusmod tempor incididunt ut labore et dolore magna
+ aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ sed do eiusmod tempor incididunt ut labore et dolore magna
+ aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ sed do eiusmod tempor incididunt ut labore et dolore magna
+ aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ anim id est laborum.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ sed do eiusmod tempor incididunt ut labore et dolore magna
+ aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit
+ anim id est laborum.
+ </div>
+ <div class="resizer" id="resizer"></div>
+ <div class="content">
+ Content
+ </div>
+ </div>
+ </main>
+ </body>
+</html>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/assets/template.mustache b/clang-tools-extra/clang-doc/assets/template.mustache
deleted file mode 100644
index 3b77e0189f4e22..00000000000000
--- a/clang-tools-extra/clang-doc/assets/template.mustache
+++ /dev/null
@@ -1,55 +0,0 @@
-{{!
- 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
-
- This file defines the template for generating namespaces
-}}
-<!DOCTYPE html>
-<html lang="en-US">
- <head>
- <meta charset="utf-8"/>
- <title>{{NamespaceTitle}}</title>
- </head>
- <h1>{{NamespaceTitle}}</h1>
- {{#NamespaceComments}}
- <div>
- {{>Comments}}
- </div>
- {{/NamespaceComments}}
- {{#Namespace}}
- <h2 id="Namespace">Namespace</h2>
- <ul>
- {{#Links}}
- <li>
- <a href="{{Link}}">{{Name}}</a>
- </li>
- {{/Links}}
- </ul>
- {{/Namespace}}
- {{#Record}}
- <h2 id="Class">Class</h2>
- <ul>
- {{#Links}}
- <li>
- <a href="{{Link}}">{{Name}}</a>
- </li>
- {{/Links}}
- </ul>
- {{/Record}}
- {{#Function}}
- <h2 id="Function">Function</h2>
- <div>
- {{#Obj}}
-
- {{/Obj}}
- </div>
- {{/Function}}
- {{#Enums}}
- <h2 id="Enums">Enums</h2>
- <div>
- {{#Obj}}
- {{/Obj}}
- </div>
- {{/Enums}}
-</html>
\ No newline at end of file
diff --git a/clang-tools-extra/clang-doc/tool/CMakeLists.txt b/clang-tools-extra/clang-doc/tool/CMakeLists.txt
index 8eb067dbe6de87..eccbc99a7ecc4b 100644
--- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt
@@ -21,7 +21,13 @@ target_link_libraries(clang-doc
set(assets
index.js
- template.mustache
+ mustache-index.js
+ class-template.mustache
+ comments-template.mustache
+ enum-template.mustache
+ function-template.mustache
+ namespace-template.mustache
+ clang-doc-mustache.css
clang-doc-default-stylesheet.css
)
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index eee8dbf093cc53..256e29c3c9f894 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -163,6 +163,15 @@ llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) {
return llvm::Error::success();
}
+llvm::SmallString<128> appendPathNative(llvm::SmallString<128> Path,
+ llvm::StringRef Asset) {
+ llvm::SmallString<128> Default;
+ llvm::sys::path::native(Path, Default);
+ llvm::sys::path::append(Default, Asset);
+ return Default;
+}
+
+
llvm::Error getDefaultAssetFiles(const char *Argv0,
clang::doc::ClangDocContext &CDCtx) {
void *MainAddr = (void *)(intptr_t)getExecutablePath;
@@ -173,13 +182,10 @@ llvm::Error getDefaultAssetFiles(const char *Argv0,
llvm::SmallString<128> AssetsPath;
AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath);
llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc");
- llvm::SmallString<128> DefaultStylesheet;
- llvm::sys::path::native(AssetsPath, DefaultStylesheet);
- llvm::sys::path::append(DefaultStylesheet,
- "clang-doc-default-stylesheet.css");
- llvm::SmallString<128> IndexJS;
- llvm::sys::path::native(AssetsPath, IndexJS);
- llvm::sys::path::append(IndexJS, "index.js");
+ llvm::SmallString<128> DefaultStylesheet =
+ appendPathNative(AssetsPath, "clang-doc-default-stylesheet.css");
+ llvm::SmallString<128> IndexJS =
+ appendPathNative(AssetsPath, "index.js");
if (!llvm::sys::fs::is_regular_file(IndexJS))
return llvm::createStringError(llvm::inconvertibleErrorCode(),
@@ -210,6 +216,7 @@ llvm::Error getHtmlAssetFiles(const char *Argv0,
return getDefaultAssetFiles(Argv0, CDCtx);
}
+
llvm::Error getMustacheHtmlFiles(const char *Argv0,
clang::doc::ClangDocContext &CDCtx) {
if (!UserAssetPath.empty() &&
@@ -228,10 +235,34 @@ llvm::Error getMustacheHtmlFiles(const char *Argv0,
AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath);
llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc");
- llvm::SmallString<128> MustacheTemplate;
- llvm::sys::path::native(AssetsPath, MustacheTemplate);
- llvm::sys::path::append(MustacheTemplate, "template.mustache");
- CDCtx.MustacheTemplates.insert({"template", MustacheTemplate.c_str()});
+ llvm::SmallString<128> DefaultStylesheet
+ = appendPathNative(AssetsPath, "clang-doc-mustache.css");
+ llvm::SmallString<128> NamespaceTemplate
+ = appendPathNative(AssetsPath, "namespace-template.mustache");
+ llvm::SmallString<128> ClassTemplate
+ = appendPathNative(AssetsPath, "class-template.mustache");
+ llvm::SmallString<128> EnumTemplate
+ = appendPathNative(AssetsPath, "enum-template.mustache");
+ llvm::SmallString<128> FunctionTemplate
+ = appendPathNative(AssetsPath, "function-template.mustache");
+ llvm::SmallString<128> CommentTemplate
+ = appendPathNative(AssetsPath, "comments-template.mustache");
+ llvm::SmallString<128> IndexJS
+ = appendPathNative(AssetsPath, "mustache-index.js");
+
+ CDCtx.JsScripts.insert(CDCtx.JsScripts.begin(), IndexJS.c_str());
+ CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(),
+ std::string(DefaultStylesheet));
+ CDCtx.MustacheTemplates.insert({"namespace-template",
+ NamespaceTemplate.c_str()});
+ CDCtx.MustacheTemplates.insert({"class-template",
+ ClassTemplate.c_str()});
+ CDCtx.MustacheTemplates.insert({"enum-template",
+ EnumTemplate.c_str()});
+ CDCtx.MustacheTemplates.insert({"function-template",
+ FunctionTemplate.c_str()});
+ CDCtx.MustacheTemplates.insert({"comments-template",
+ CommentTemplate.c_str()});
return llvm::Error::success();
}
More information about the cfe-commits
mailing list