[clang-tools-extra] [llvm] [llvm] add support for mustache templating language (PR #105893)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 23 14:43:42 PDT 2024
https://github.com/PeterChou1 created https://github.com/llvm/llvm-project/pull/105893
Work In Progress
>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/10] [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/10] [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/10] [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/10] [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/10] [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/10] [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/10] [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/10] [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/10] [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/10] [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);
+}
More information about the llvm-commits
mailing list