934 lines · cpp
1//===-- Mustache.cpp ------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "llvm/Support/Mustache.h"9#include "llvm/ADT/SmallVector.h"10#include "llvm/Support/Debug.h"11#include "llvm/Support/raw_ostream.h"12#include <cctype>13#include <sstream>14 15#define DEBUG_TYPE "mustache"16 17using namespace llvm;18using namespace llvm::mustache;19 20namespace {21 22using Accessor = ArrayRef<StringRef>;23 24static bool isFalsey(const json::Value &V) {25 return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||26 (V.getAsArray() && V.getAsArray()->empty());27}28 29static bool isContextFalsey(const json::Value *V) {30 // A missing context (represented by a nullptr) is defined as falsey.31 if (!V)32 return true;33 return isFalsey(*V);34}35 36static void splitAndTrim(StringRef Str, SmallVectorImpl<StringRef> &Tokens) {37 size_t CurrentPos = 0;38 while (CurrentPos < Str.size()) {39 // Find the next delimiter.40 size_t DelimiterPos = Str.find('.', CurrentPos);41 42 // If no delimiter is found, process the rest of the string.43 if (DelimiterPos == StringRef::npos)44 DelimiterPos = Str.size();45 46 // Get the current part, which may have whitespace.47 StringRef Part = Str.slice(CurrentPos, DelimiterPos);48 49 // Manually trim the part without creating a new string object.50 size_t Start = Part.find_first_not_of(" \t\r\n");51 if (Start != StringRef::npos) {52 size_t End = Part.find_last_not_of(" \t\r\n");53 Tokens.push_back(Part.slice(Start, End + 1));54 }55 56 // Move past the delimiter for the next iteration.57 CurrentPos = DelimiterPos + 1;58 }59}60 61static Accessor splitMustacheString(StringRef Str, MustacheContext &Ctx) {62 // We split the mustache string into an accessor.63 // For example:64 // "a.b.c" would be split into {"a", "b", "c"}65 // We make an exception for a single dot which66 // refers to the current context.67 SmallVector<StringRef> Tokens;68 if (Str == ".") {69 // "." is a special accessor that refers to the current context.70 // It's a literal, so it doesn't need to be saved.71 Tokens.push_back(".");72 } else {73 splitAndTrim(Str, Tokens);74 }75 // Now, allocate memory for the array of StringRefs in the arena.76 StringRef *ArenaTokens = Ctx.Allocator.Allocate<StringRef>(Tokens.size());77 // Copy the StringRefs from the stack vector to the arena.78 llvm::copy(Tokens, ArenaTokens);79 // Return an ArrayRef pointing to the stable arena memory.80 return ArrayRef<StringRef>(ArenaTokens, Tokens.size());81}82} // namespace83 84namespace llvm::mustache {85 86class MustacheOutputStream : public raw_ostream {87public:88 MustacheOutputStream() = default;89 ~MustacheOutputStream() override = default;90 91 virtual void suspendIndentation() {}92 virtual void resumeIndentation() {}93 94private:95 void anchor() override;96};97 98void MustacheOutputStream::anchor() {}99 100class RawMustacheOutputStream : public MustacheOutputStream {101public:102 RawMustacheOutputStream(raw_ostream &OS) : OS(OS) { SetUnbuffered(); }103 104private:105 raw_ostream &OS;106 107 void write_impl(const char *Ptr, size_t Size) override {108 OS.write(Ptr, Size);109 }110 uint64_t current_pos() const override { return OS.tell(); }111};112 113class Token {114public:115 enum class Type {116 Text,117 Variable,118 Partial,119 SectionOpen,120 SectionClose,121 InvertSectionOpen,122 UnescapeVariable,123 Comment,124 SetDelimiter,125 };126 127 Token(StringRef Str)128 : TokenType(Type::Text), RawBody(Str), TokenBody(RawBody),129 AccessorValue({}), Indentation(0) {};130 131 Token(StringRef RawBody, StringRef TokenBody, char Identifier,132 MustacheContext &Ctx)133 : RawBody(RawBody), TokenBody(TokenBody), Indentation(0) {134 TokenType = getTokenType(Identifier);135 if (TokenType == Type::Comment)136 return;137 StringRef AccessorStr(this->TokenBody);138 if (TokenType != Type::Variable)139 AccessorStr = AccessorStr.substr(1);140 AccessorValue = splitMustacheString(StringRef(AccessorStr).trim(), Ctx);141 }142 143 ArrayRef<StringRef> getAccessor() const { return AccessorValue; }144 145 Type getType() const { return TokenType; }146 147 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }148 149 size_t getIndentation() const { return Indentation; }150 151 static Type getTokenType(char Identifier) {152 switch (Identifier) {153 case '#':154 return Type::SectionOpen;155 case '/':156 return Type::SectionClose;157 case '^':158 return Type::InvertSectionOpen;159 case '!':160 return Type::Comment;161 case '>':162 return Type::Partial;163 case '&':164 return Type::UnescapeVariable;165 case '=':166 return Type::SetDelimiter;167 default:168 return Type::Variable;169 }170 }171 172 Type TokenType;173 // RawBody is the original string that was tokenized.174 StringRef RawBody;175 // TokenBody is the original string with the identifier removed.176 StringRef TokenBody;177 ArrayRef<StringRef> AccessorValue;178 size_t Indentation;179};180 181using EscapeMap = DenseMap<char, std::string>;182 183class ASTNode : public ilist_node<ASTNode> {184public:185 enum Type {186 Root,187 Text,188 Partial,189 Variable,190 UnescapeVariable,191 Section,192 InvertSection,193 };194 195 ASTNode(MustacheContext &Ctx)196 : Ctx(Ctx), Ty(Type::Root), Parent(nullptr), ParentContext(nullptr) {}197 198 ASTNode(MustacheContext &Ctx, StringRef Body, ASTNode *Parent)199 : Ctx(Ctx), Ty(Type::Text), Body(Body), Parent(Parent),200 ParentContext(nullptr) {}201 202 // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes203 ASTNode(MustacheContext &Ctx, Type Ty, ArrayRef<StringRef> Accessor,204 ASTNode *Parent)205 : Ctx(Ctx), Ty(Ty), Parent(Parent), AccessorValue(Accessor),206 ParentContext(nullptr) {}207 208 void addChild(AstPtr Child) { Children.push_back(Child); };209 210 void setRawBody(StringRef NewBody) { RawBody = NewBody; };211 212 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };213 214 void render(const llvm::json::Value &Data, MustacheOutputStream &OS);215 216private:217 void renderLambdas(const llvm::json::Value &Contexts,218 MustacheOutputStream &OS, Lambda &L);219 220 void renderSectionLambdas(const llvm::json::Value &Contexts,221 MustacheOutputStream &OS, SectionLambda &L);222 223 void renderPartial(const llvm::json::Value &Contexts,224 MustacheOutputStream &OS, ASTNode *Partial);225 226 void renderChild(const llvm::json::Value &Context, MustacheOutputStream &OS);227 228 const llvm::json::Value *findContext();229 230 void renderRoot(const json::Value &CurrentCtx, MustacheOutputStream &OS);231 void renderText(MustacheOutputStream &OS);232 void renderPartial(const json::Value &CurrentCtx, MustacheOutputStream &OS);233 void renderVariable(const json::Value &CurrentCtx, MustacheOutputStream &OS);234 void renderUnescapeVariable(const json::Value &CurrentCtx,235 MustacheOutputStream &OS);236 void renderSection(const json::Value &CurrentCtx, MustacheOutputStream &OS);237 void renderInvertSection(const json::Value &CurrentCtx,238 MustacheOutputStream &OS);239 240 MustacheContext &Ctx;241 Type Ty;242 size_t Indentation = 0;243 StringRef RawBody;244 StringRef Body;245 ASTNode *Parent;246 ASTNodeList Children;247 const ArrayRef<StringRef> AccessorValue;248 const llvm::json::Value *ParentContext;249};250 251// A wrapper for arena allocator for ASTNodes252static AstPtr createRootNode(MustacheContext &Ctx) {253 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx);254}255 256static AstPtr createNode(MustacheContext &Ctx, ASTNode::Type T,257 ArrayRef<StringRef> A, ASTNode *Parent) {258 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, T, A, Parent);259}260 261static AstPtr createTextNode(MustacheContext &Ctx, StringRef Body,262 ASTNode *Parent) {263 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, Body, Parent);264}265 266// Function to check if there is meaningful text behind.267// We determine if a token has meaningful text behind268// if the right of previous token contains anything that is269// not a newline.270// For example:271// "Stuff {{#Section}}" (returns true)272// vs273// "{{#Section}} \n" (returns false)274// We make an exception for when previous token is empty275// and the current token is the second token.276// For example:277// "{{#Section}}"278static bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) {279 if (Idx == 0)280 return true;281 282 size_t PrevIdx = Idx - 1;283 if (Tokens[PrevIdx].getType() != Token::Type::Text)284 return true;285 286 const Token &PrevToken = Tokens[PrevIdx];287 StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v");288 return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1);289}290 291// Function to check if there's no meaningful text ahead.292// We determine if a token has text ahead if the left of previous293// token does not start with a newline.294static bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) {295 if (Idx >= Tokens.size() - 1)296 return true;297 298 size_t NextIdx = Idx + 1;299 if (Tokens[NextIdx].getType() != Token::Type::Text)300 return true;301 302 const Token &NextToken = Tokens[NextIdx];303 StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" ");304 return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n");305}306 307static bool requiresCleanUp(Token::Type T) {308 // We must clean up all the tokens that could contain child nodes.309 return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen ||310 T == Token::Type::SectionClose || T == Token::Type::Comment ||311 T == Token::Type::Partial || T == Token::Type::SetDelimiter;312}313 314// Adjust next token body if there is no text ahead.315// For example:316// The template string317// "{{! Comment }} \nLine 2"318// would be considered as no text ahead and should be rendered as319// " Line 2"320static void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {321 Token &NextToken = Tokens[Idx + 1];322 StringRef NextTokenBody = NextToken.TokenBody;323 // Cut off the leading newline which could be \n or \r\n.324 if (NextTokenBody.starts_with("\r\n"))325 NextToken.TokenBody = NextTokenBody.substr(2);326 else if (NextTokenBody.starts_with("\n"))327 NextToken.TokenBody = NextTokenBody.substr(1);328}329 330// Adjust previous token body if there no text behind.331// For example:332// The template string333// " \t{{#section}}A{{/section}}"334// would be considered as having no text ahead and would be render as:335// "A"336void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx,337 Token &CurrentToken, Token::Type CurrentType) {338 Token &PrevToken = Tokens[Idx - 1];339 StringRef PrevTokenBody = PrevToken.TokenBody;340 StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v");341 size_t Indentation = PrevTokenBody.size() - Unindented.size();342 PrevToken.TokenBody = Unindented;343 CurrentToken.setIndentation(Indentation);344}345 346struct Tag {347 enum class Kind {348 None,349 Normal, // {{...}}350 Triple, // {{{...}}}351 };352 353 Kind TagKind = Kind::None;354 StringRef Content; // The content between the delimiters.355 StringRef FullMatch; // The entire tag, including delimiters.356 size_t StartPosition = StringRef::npos;357};358 359[[maybe_unused]] static const char *tagKindToString(Tag::Kind K) {360 switch (K) {361 case Tag::Kind::None:362 return "None";363 case Tag::Kind::Normal:364 return "Normal";365 case Tag::Kind::Triple:366 return "Triple";367 }368 llvm_unreachable("Unknown Tag::Kind");369}370 371[[maybe_unused]] static const char *jsonKindToString(json::Value::Kind K) {372 switch (K) {373 case json::Value::Kind::Null:374 return "JSON_KIND_NULL";375 case json::Value::Kind::Boolean:376 return "JSON_KIND_BOOLEAN";377 case json::Value::Kind::Number:378 return "JSON_KIND_NUMBER";379 case json::Value::Kind::String:380 return "JSON_KIND_STRING";381 case json::Value::Kind::Array:382 return "JSON_KIND_ARRAY";383 case json::Value::Kind::Object:384 return "JSON_KIND_OBJECT";385 }386 llvm_unreachable("Unknown json::Value::Kind");387}388 389// Simple tokenizer that splits the template into tokens.390static SmallVector<Token> tokenize(StringRef Template, MustacheContext &Ctx) {391 LLVM_DEBUG(dbgs() << "[Tokenize Template] \"" << Template << "\"\n");392 SmallVector<Token> Tokens;393 SmallString<8> Open("{{");394 SmallString<8> Close("}}");395 size_t Cursor = 0;396 size_t TextStart = 0;397 398 const StringLiteral TripleOpen("{{{");399 const StringLiteral TripleClose("}}}");400 401 while (Cursor < Template.size()) {402 StringRef TemplateSuffix = Template.substr(Cursor);403 StringRef TagOpen, TagClose;404 Tag::Kind Kind;405 406 // Determine which tag we've encountered.407 if (TemplateSuffix.starts_with(TripleOpen)) {408 Kind = Tag::Kind::Triple;409 TagOpen = TripleOpen;410 TagClose = TripleClose;411 } else if (TemplateSuffix.starts_with(Open)) {412 Kind = Tag::Kind::Normal;413 TagOpen = Open;414 TagClose = Close;415 } else {416 // Not at a tag, continue scanning.417 ++Cursor;418 continue;419 }420 421 // Found a tag, first add the preceding text.422 if (Cursor > TextStart)423 Tokens.emplace_back(Template.slice(TextStart, Cursor));424 425 // Find the closing tag.426 size_t EndPos = Template.find(TagClose, Cursor + TagOpen.size());427 if (EndPos == StringRef::npos) {428 // No closing tag, the rest is text.429 Tokens.emplace_back(Template.substr(Cursor));430 TextStart = Cursor = Template.size();431 break;432 }433 434 // Extract tag content and full match.435 size_t ContentStart = Cursor + TagOpen.size();436 StringRef Content = Template.substr(ContentStart, EndPos - ContentStart);437 StringRef FullMatch =438 Template.substr(Cursor, (EndPos + TagClose.size()) - Cursor);439 440 // Process the tag (inlined logic from processTag).441 LLVM_DEBUG(dbgs() << "[Tag] " << FullMatch << ", Content: " << Content442 << ", Kind: " << tagKindToString(Kind) << "\n");443 if (Kind == Tag::Kind::Triple) {444 Tokens.emplace_back(FullMatch, Ctx.Saver.save("&" + Content), '&', Ctx);445 } else { // Normal Tag446 StringRef Interpolated = Content;447 if (!Interpolated.trim().starts_with("=")) {448 char Front = Interpolated.empty() ? ' ' : Interpolated.trim().front();449 Tokens.emplace_back(FullMatch, Interpolated, Front, Ctx);450 } else { // Set Delimiter451 Tokens.emplace_back(FullMatch, Interpolated, '=', Ctx);452 StringRef DelimSpec = Interpolated.trim();453 DelimSpec = DelimSpec.drop_front(1);454 DelimSpec = DelimSpec.take_until([](char C) { return C == '='; });455 DelimSpec = DelimSpec.trim();456 457 auto [NewOpen, NewClose] = DelimSpec.split(' ');458 LLVM_DEBUG(dbgs() << "[Set Delimiter] NewOpen: " << NewOpen459 << ", NewClose: " << NewClose << "\n");460 Open = NewOpen;461 Close = NewClose;462 }463 }464 465 // Move past the tag for the next iteration.466 Cursor += FullMatch.size();467 TextStart = Cursor;468 }469 470 // Add any remaining text after the last tag.471 if (TextStart < Template.size())472 Tokens.emplace_back(Template.substr(TextStart));473 474 // Fix up white spaces for standalone tags.475 size_t LastIdx = Tokens.size() - 1;476 for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {477 Token &CurrentToken = Tokens[Idx];478 Token::Type CurrentType = CurrentToken.getType();479 if (!requiresCleanUp(CurrentType))480 continue;481 482 bool HasTextBehind = hasTextBehind(Idx, Tokens);483 bool HasTextAhead = hasTextAhead(Idx, Tokens);484 485 if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0))486 stripTokenAhead(Tokens, Idx);487 488 if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx))489 stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);490 }491 return Tokens;492}493 494// Custom stream to escape strings.495class EscapeStringStream : public MustacheOutputStream {496public:497 explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,498 EscapeMap &Escape)499 : Escape(Escape), EscapeChars(Escape.keys().begin(), Escape.keys().end()),500 WrappedStream(WrappedStream) {501 SetUnbuffered();502 }503 504protected:505 void write_impl(const char *Ptr, size_t Size) override {506 StringRef Data(Ptr, Size);507 size_t Start = 0;508 while (Start < Size) {509 // Find the next character that needs to be escaped.510 size_t Next = Data.find_first_of(EscapeChars.str(), Start);511 512 // If no escapable characters are found, write the rest of the string.513 if (Next == StringRef::npos) {514 WrappedStream << Data.substr(Start);515 return;516 }517 518 // Write the chunk of text before the escapable character.519 if (Next > Start)520 WrappedStream << Data.substr(Start, Next - Start);521 522 // Look up and write the escaped version of the character.523 WrappedStream << Escape[Data[Next]];524 Start = Next + 1;525 }526 }527 528 uint64_t current_pos() const override { return WrappedStream.tell(); }529 530private:531 EscapeMap &Escape;532 SmallString<8> EscapeChars;533 llvm::raw_ostream &WrappedStream;534};535 536// Custom stream to add indentation used to for rendering partials.537class AddIndentationStringStream : public MustacheOutputStream {538public:539 explicit AddIndentationStringStream(raw_ostream &WrappedStream,540 size_t Indentation)541 : Indentation(Indentation), WrappedStream(WrappedStream),542 NeedsIndent(true), IsSuspended(false) {543 SetUnbuffered();544 }545 546 void suspendIndentation() override { IsSuspended = true; }547 void resumeIndentation() override { IsSuspended = false; }548 549protected:550 void write_impl(const char *Ptr, size_t Size) override {551 llvm::StringRef Data(Ptr, Size);552 SmallString<0> Indent;553 Indent.resize(Indentation, ' ');554 555 for (char C : Data) {556 LLVM_DEBUG(dbgs() << "[Indentation Stream] NeedsIndent:" << NeedsIndent557 << ", C:'" << C << "', Indentation:" << Indentation558 << "\n");559 if (NeedsIndent && C != '\n') {560 WrappedStream << Indent;561 NeedsIndent = false;562 }563 WrappedStream << C;564 if (C == '\n' && !IsSuspended)565 NeedsIndent = true;566 }567 }568 569 uint64_t current_pos() const override { return WrappedStream.tell(); }570 571private:572 size_t Indentation;573 raw_ostream &WrappedStream;574 bool NeedsIndent;575 bool IsSuspended;576};577 578class Parser {579public:580 Parser(StringRef TemplateStr, MustacheContext &Ctx)581 : Ctx(Ctx), TemplateStr(TemplateStr) {}582 583 AstPtr parse();584 585private:586 void parseMustache(ASTNode *Parent);587 void parseSection(ASTNode *Parent, ASTNode::Type Ty, const Accessor &A);588 589 MustacheContext &Ctx;590 SmallVector<Token> Tokens;591 size_t CurrentPtr;592 StringRef TemplateStr;593};594 595void Parser::parseSection(ASTNode *Parent, ASTNode::Type Ty,596 const Accessor &A) {597 AstPtr CurrentNode = createNode(Ctx, Ty, A, Parent);598 size_t Start = CurrentPtr;599 parseMustache(CurrentNode);600 const size_t End = CurrentPtr - 1;601 602 size_t RawBodySize = 0;603 for (size_t I = Start; I < End; ++I)604 RawBodySize += Tokens[I].RawBody.size();605 606 SmallString<128> RawBody;607 RawBody.reserve(RawBodySize);608 for (std::size_t I = Start; I < End; ++I)609 RawBody += Tokens[I].RawBody;610 611 CurrentNode->setRawBody(Ctx.Saver.save(StringRef(RawBody)));612 Parent->addChild(CurrentNode);613}614 615AstPtr Parser::parse() {616 Tokens = tokenize(TemplateStr, Ctx);617 CurrentPtr = 0;618 AstPtr RootNode = createRootNode(Ctx);619 parseMustache(RootNode);620 return RootNode;621}622 623void Parser::parseMustache(ASTNode *Parent) {624 625 while (CurrentPtr < Tokens.size()) {626 Token CurrentToken = Tokens[CurrentPtr];627 CurrentPtr++;628 ArrayRef<StringRef> A = CurrentToken.getAccessor();629 AstPtr CurrentNode;630 631 switch (CurrentToken.getType()) {632 case Token::Type::Text: {633 CurrentNode = createTextNode(Ctx, CurrentToken.TokenBody, Parent);634 Parent->addChild(CurrentNode);635 break;636 }637 case Token::Type::Variable: {638 CurrentNode = createNode(Ctx, ASTNode::Variable, A, Parent);639 Parent->addChild(CurrentNode);640 break;641 }642 case Token::Type::UnescapeVariable: {643 CurrentNode = createNode(Ctx, ASTNode::UnescapeVariable, A, Parent);644 Parent->addChild(CurrentNode);645 break;646 }647 case Token::Type::Partial: {648 CurrentNode = createNode(Ctx, ASTNode::Partial, A, Parent);649 CurrentNode->setIndentation(CurrentToken.getIndentation());650 Parent->addChild(CurrentNode);651 break;652 }653 case Token::Type::SectionOpen: {654 parseSection(Parent, ASTNode::Section, A);655 break;656 }657 case Token::Type::InvertSectionOpen: {658 parseSection(Parent, ASTNode::InvertSection, A);659 break;660 }661 case Token::Type::Comment:662 case Token::Type::SetDelimiter:663 break;664 case Token::Type::SectionClose:665 return;666 }667 }668}669static void toMustacheString(const json::Value &Data, raw_ostream &OS) {670 LLVM_DEBUG(dbgs() << "[To Mustache String] Kind: "671 << jsonKindToString(Data.kind()) << ", Data: " << Data672 << "\n");673 switch (Data.kind()) {674 case json::Value::Null:675 return;676 case json::Value::Number: {677 auto Num = *Data.getAsNumber();678 std::ostringstream SS;679 SS << Num;680 OS << SS.str();681 return;682 }683 case json::Value::String: {684 OS << *Data.getAsString();685 return;686 }687 688 case json::Value::Array: {689 auto Arr = *Data.getAsArray();690 if (Arr.empty())691 return;692 [[fallthrough]];693 }694 case json::Value::Object:695 case json::Value::Boolean: {696 llvm::json::OStream JOS(OS, 2);697 JOS.value(Data);698 break;699 }700 }701}702 703void ASTNode::renderRoot(const json::Value &CurrentCtx,704 MustacheOutputStream &OS) {705 renderChild(CurrentCtx, OS);706}707 708void ASTNode::renderText(MustacheOutputStream &OS) { OS << Body; }709 710void ASTNode::renderPartial(const json::Value &CurrentCtx,711 MustacheOutputStream &OS) {712 LLVM_DEBUG(dbgs() << "[Render Partial] Accessor:" << AccessorValue[0]713 << ", Indentation:" << Indentation << "\n");714 auto Partial = Ctx.Partials.find(AccessorValue[0]);715 if (Partial != Ctx.Partials.end())716 renderPartial(CurrentCtx, OS, Partial->getValue());717}718 719void ASTNode::renderVariable(const json::Value &CurrentCtx,720 MustacheOutputStream &OS) {721 auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);722 if (Lambda != Ctx.Lambdas.end()) {723 renderLambdas(CurrentCtx, OS, Lambda->getValue());724 } else if (const json::Value *ContextPtr = findContext()) {725 EscapeStringStream ES(OS, Ctx.Escapes);726 toMustacheString(*ContextPtr, ES);727 }728}729 730void ASTNode::renderUnescapeVariable(const json::Value &CurrentCtx,731 MustacheOutputStream &OS) {732 LLVM_DEBUG(dbgs() << "[Render UnescapeVariable] Accessor:" << AccessorValue[0]733 << "\n");734 auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);735 if (Lambda != Ctx.Lambdas.end()) {736 renderLambdas(CurrentCtx, OS, Lambda->getValue());737 } else if (const json::Value *ContextPtr = findContext()) {738 OS.suspendIndentation();739 toMustacheString(*ContextPtr, OS);740 OS.resumeIndentation();741 }742}743 744void ASTNode::renderSection(const json::Value &CurrentCtx,745 MustacheOutputStream &OS) {746 auto SectionLambda = Ctx.SectionLambdas.find(AccessorValue[0]);747 if (SectionLambda != Ctx.SectionLambdas.end()) {748 renderSectionLambdas(CurrentCtx, OS, SectionLambda->getValue());749 return;750 }751 752 const json::Value *ContextPtr = findContext();753 if (isContextFalsey(ContextPtr))754 return;755 756 if (const json::Array *Arr = ContextPtr->getAsArray()) {757 for (const json::Value &V : *Arr)758 renderChild(V, OS);759 return;760 }761 renderChild(*ContextPtr, OS);762}763 764void ASTNode::renderInvertSection(const json::Value &CurrentCtx,765 MustacheOutputStream &OS) {766 bool IsLambda = Ctx.SectionLambdas.contains(AccessorValue[0]);767 const json::Value *ContextPtr = findContext();768 if (isContextFalsey(ContextPtr) && !IsLambda) {769 renderChild(CurrentCtx, OS);770 }771}772 773void ASTNode::render(const llvm::json::Value &Data, MustacheOutputStream &OS) {774 if (Ty != Root && Ty != Text && AccessorValue.empty())775 return;776 // Set the parent context to the incoming context so that we777 // can walk up the context tree correctly in findContext().778 ParentContext = &Data;779 780 switch (Ty) {781 case Root:782 renderRoot(Data, OS);783 return;784 case Text:785 renderText(OS);786 return;787 case Partial:788 renderPartial(Data, OS);789 return;790 case Variable:791 renderVariable(Data, OS);792 return;793 case UnescapeVariable:794 renderUnescapeVariable(Data, OS);795 return;796 case Section:797 renderSection(Data, OS);798 return;799 case InvertSection:800 renderInvertSection(Data, OS);801 return;802 }803 llvm_unreachable("Invalid ASTNode type");804}805 806const json::Value *ASTNode::findContext() {807 // The mustache spec allows for dot notation to access nested values808 // a single dot refers to the current context.809 // We attempt to find the JSON context in the current node, if it is not810 // found, then we traverse the parent nodes to find the context until we811 // reach the root node or the context is found.812 if (AccessorValue.empty())813 return nullptr;814 if (AccessorValue[0] == ".")815 return ParentContext;816 817 const json::Object *CurrentContext = ParentContext->getAsObject();818 StringRef CurrentAccessor = AccessorValue[0];819 ASTNode *CurrentParent = Parent;820 821 while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {822 if (CurrentParent->Ty != Root) {823 CurrentContext = CurrentParent->ParentContext->getAsObject();824 CurrentParent = CurrentParent->Parent;825 continue;826 }827 return nullptr;828 }829 const json::Value *Context = nullptr;830 for (auto [Idx, Acc] : enumerate(AccessorValue)) {831 const json::Value *CurrentValue = CurrentContext->get(Acc);832 if (!CurrentValue)833 return nullptr;834 if (Idx < AccessorValue.size() - 1) {835 CurrentContext = CurrentValue->getAsObject();836 if (!CurrentContext)837 return nullptr;838 } else {839 Context = CurrentValue;840 }841 }842 return Context;843}844 845void ASTNode::renderChild(const json::Value &Contexts,846 MustacheOutputStream &OS) {847 for (ASTNode &Child : Children)848 Child.render(Contexts, OS);849}850 851void ASTNode::renderPartial(const json::Value &Contexts,852 MustacheOutputStream &OS, ASTNode *Partial) {853 LLVM_DEBUG(dbgs() << "[Render Partial Indentation] Indentation: " << Indentation << "\n");854 AddIndentationStringStream IS(OS, Indentation);855 Partial->render(Contexts, IS);856}857 858void ASTNode::renderLambdas(const llvm::json::Value &Contexts,859 MustacheOutputStream &OS, Lambda &L) {860 json::Value LambdaResult = L();861 std::string LambdaStr;862 raw_string_ostream Output(LambdaStr);863 toMustacheString(LambdaResult, Output);864 Parser P(LambdaStr, Ctx);865 AstPtr LambdaNode = P.parse();866 867 EscapeStringStream ES(OS, Ctx.Escapes);868 if (Ty == Variable) {869 LambdaNode->render(Contexts, ES);870 return;871 }872 LambdaNode->render(Contexts, OS);873}874 875void ASTNode::renderSectionLambdas(const llvm::json::Value &Contexts,876 MustacheOutputStream &OS, SectionLambda &L) {877 json::Value Return = L(RawBody.str());878 if (isFalsey(Return))879 return;880 std::string LambdaStr;881 raw_string_ostream Output(LambdaStr);882 toMustacheString(Return, Output);883 Parser P(LambdaStr, Ctx);884 AstPtr LambdaNode = P.parse();885 LambdaNode->render(Contexts, OS);886}887 888void Template::render(const llvm::json::Value &Data, llvm::raw_ostream &OS) {889 RawMustacheOutputStream MOS(OS);890 Tree->render(Data, MOS);891}892 893void Template::registerPartial(std::string Name, std::string Partial) {894 StringRef SavedPartial = Ctx.Saver.save(Partial);895 Parser P(SavedPartial, Ctx);896 AstPtr PartialTree = P.parse();897 Ctx.Partials.insert(std::make_pair(Name, PartialTree));898}899 900void Template::registerLambda(std::string Name, Lambda L) {901 Ctx.Lambdas[Name] = L;902}903 904void Template::registerLambda(std::string Name, SectionLambda L) {905 Ctx.SectionLambdas[Name] = L;906}907 908void Template::overrideEscapeCharacters(EscapeMap E) {909 Ctx.Escapes = std::move(E);910}911 912Template::Template(StringRef TemplateStr, MustacheContext &Ctx) : Ctx(Ctx) {913 Parser P(TemplateStr, Ctx);914 Tree = P.parse();915 // The default behavior is to escape html entities.916 const EscapeMap HtmlEntities = {{'&', "&"},917 {'<', "<"},918 {'>', ">"},919 {'"', """},920 {'\'', "'"}};921 overrideEscapeCharacters(HtmlEntities);922}923 924Template::Template(Template &&Other) noexcept925 : Ctx(Other.Ctx), Tree(Other.Tree) {926 Other.Tree = nullptr;927}928 929Template::~Template() = default;930 931} // namespace llvm::mustache932 933#undef DEBUG_TYPE934