https://github.com/PeterChou1 updated https://github.com/llvm/llvm-project/pull/105893
>From ac1c7b5cbf3024bf8cd4021174a47b914d7f7dea Mon Sep 17 00:00:00 2001 From: PeterChou1 <peter.c...@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.c...@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.c...@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.c...@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.c...@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.c...@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.c...@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.c...@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.c...@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.c...@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); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits