LLVM 22.0.0git
Mustache.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
10#include "llvm/Support/Debug.h"
12#include <cctype>
13#include <optional>
14#include <sstream>
15
16#define DEBUG_TYPE "mustache"
17
18using namespace llvm;
19using namespace llvm::mustache;
20
21namespace {
22
23using Accessor = SmallVector<std::string>;
24
25static bool isFalsey(const json::Value &V) {
26 return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||
27 (V.getAsArray() && V.getAsArray()->empty());
28}
29
30static bool isContextFalsey(const json::Value *V) {
31 // A missing context (represented by a nullptr) is defined as falsey.
32 if (!V)
33 return true;
34 return isFalsey(*V);
35}
36
37static Accessor splitMustacheString(StringRef Str) {
38 // We split the mustache string into an accessor.
39 // For example:
40 // "a.b.c" would be split into {"a", "b", "c"}
41 // We make an exception for a single dot which
42 // refers to the current context.
43 Accessor Tokens;
44 if (Str == ".") {
45 Tokens.emplace_back(Str);
46 return Tokens;
47 }
48 while (!Str.empty()) {
49 StringRef Part;
50 std::tie(Part, Str) = Str.split(".");
51 Tokens.emplace_back(Part.trim());
52 }
53 return Tokens;
54}
55} // namespace
56
57namespace llvm::mustache {
58
60public:
62 ~MustacheOutputStream() override = default;
63
64 virtual void suspendIndentation() {}
65 virtual void resumeIndentation() {}
66
67private:
68 void anchor() override;
69};
70
71void MustacheOutputStream::anchor() {}
72
74public:
76
77private:
78 raw_ostream &OS;
79
80 void write_impl(const char *Ptr, size_t Size) override {
81 OS.write(Ptr, Size);
82 }
83 uint64_t current_pos() const override { return OS.tell(); }
84};
85
86class Token {
87public:
99
100 Token(std::string Str)
102 AccessorValue({}), Indentation(0) {};
103
104 Token(std::string RawBody, std::string TokenBody, char Identifier)
106 Indentation(0) {
107 TokenType = getTokenType(Identifier);
109 return;
110 StringRef AccessorStr(this->TokenBody);
112 AccessorStr = AccessorStr.substr(1);
113 AccessorValue = splitMustacheString(StringRef(AccessorStr).trim());
114 }
115
116 Accessor getAccessor() const { return AccessorValue; }
117
118 Type getType() const { return TokenType; }
119
120 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }
121
122 size_t getIndentation() const { return Indentation; }
123
124 static Type getTokenType(char Identifier) {
125 switch (Identifier) {
126 case '#':
127 return Type::SectionOpen;
128 case '/':
129 return Type::SectionClose;
130 case '^':
132 case '!':
133 return Type::Comment;
134 case '>':
135 return Type::Partial;
136 case '&':
138 case '=':
139 return Type::SetDelimiter;
140 default:
141 return Type::Variable;
142 }
143 }
144
146 // RawBody is the original string that was tokenized.
147 std::string RawBody;
148 // TokenBody is the original string with the identifier removed.
149 std::string TokenBody;
152};
153
155
156class ASTNode {
157public:
167
169 : Ctx(Ctx), Ty(Type::Root), Parent(nullptr), ParentContext(nullptr) {}
170
171 ASTNode(MustacheContext &Ctx, std::string Body, ASTNode *Parent)
172 : Ctx(Ctx), Ty(Type::Text), Body(std::move(Body)), Parent(Parent),
173 ParentContext(nullptr) {}
174
175 // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes
176 ASTNode(MustacheContext &Ctx, Type Ty, Accessor Accessor, ASTNode *Parent)
177 : Ctx(Ctx), Ty(Ty), Parent(Parent), AccessorValue(std::move(Accessor)),
178 ParentContext(nullptr) {}
179
180 void addChild(AstPtr Child) { Children.emplace_back(std::move(Child)); };
181
182 void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); };
183
184 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
185
187
188private:
189 void renderLambdas(const llvm::json::Value &Contexts,
191
192 void renderSectionLambdas(const llvm::json::Value &Contexts,
194
195 void renderPartial(const llvm::json::Value &Contexts,
197
198 void renderChild(const llvm::json::Value &Context, MustacheOutputStream &OS);
199
200 const llvm::json::Value *findContext();
201
202 void renderRoot(const json::Value &CurrentCtx, MustacheOutputStream &OS);
203 void renderText(MustacheOutputStream &OS);
204 void renderPartial(const json::Value &CurrentCtx, MustacheOutputStream &OS);
205 void renderVariable(const json::Value &CurrentCtx, MustacheOutputStream &OS);
206 void renderUnescapeVariable(const json::Value &CurrentCtx,
208 void renderSection(const json::Value &CurrentCtx, MustacheOutputStream &OS);
209 void renderInvertSection(const json::Value &CurrentCtx,
211
212 MustacheContext &Ctx;
213 Type Ty;
214 size_t Indentation = 0;
215 std::string RawBody;
216 std::string Body;
217 ASTNode *Parent;
218 // TODO: switch implementation to SmallVector<T>
219 std::vector<AstPtr> Children;
220 const Accessor AccessorValue;
221 const llvm::json::Value *ParentContext;
222};
223
224// A wrapper for arena allocator for ASTNodes
226 return std::make_unique<ASTNode>(Ctx);
227}
228
230 ASTNode *Parent) {
231 return std::make_unique<ASTNode>(Ctx, T, std::move(A), Parent);
232}
233
234static AstPtr createTextNode(MustacheContext &Ctx, std::string Body,
235 ASTNode *Parent) {
236 return std::make_unique<ASTNode>(Ctx, std::move(Body), Parent);
237}
238
239// Function to check if there is meaningful text behind.
240// We determine if a token has meaningful text behind
241// if the right of previous token contains anything that is
242// not a newline.
243// For example:
244// "Stuff {{#Section}}" (returns true)
245// vs
246// "{{#Section}} \n" (returns false)
247// We make an exception for when previous token is empty
248// and the current token is the second token.
249// For example:
250// "{{#Section}}"
251static bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) {
252 if (Idx == 0)
253 return true;
254
255 size_t PrevIdx = Idx - 1;
256 if (Tokens[PrevIdx].getType() != Token::Type::Text)
257 return true;
258
259 const Token &PrevToken = Tokens[PrevIdx];
260 StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v");
261 return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1);
262}
263
264// Function to check if there's no meaningful text ahead.
265// We determine if a token has text ahead if the left of previous
266// token does not start with a newline.
267static bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) {
268 if (Idx >= Tokens.size() - 1)
269 return true;
270
271 size_t NextIdx = Idx + 1;
272 if (Tokens[NextIdx].getType() != Token::Type::Text)
273 return true;
274
275 const Token &NextToken = Tokens[NextIdx];
276 StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" ");
277 return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n");
278}
279
281 // We must clean up all the tokens that could contain child nodes.
285}
286
287// Adjust next token body if there is no text ahead.
288// For example:
289// The template string
290// "{{! Comment }} \nLine 2"
291// would be considered as no text ahead and should be rendered as
292// " Line 2"
293static void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {
294 Token &NextToken = Tokens[Idx + 1];
295 StringRef NextTokenBody = NextToken.TokenBody;
296 // Cut off the leading newline which could be \n or \r\n.
297 if (NextTokenBody.starts_with("\r\n"))
298 NextToken.TokenBody = NextTokenBody.substr(2).str();
299 else if (NextTokenBody.starts_with("\n"))
300 NextToken.TokenBody = NextTokenBody.substr(1).str();
301}
302
303// Adjust previous token body if there no text behind.
304// For example:
305// The template string
306// " \t{{#section}}A{{/section}}"
307// would be considered as having no text ahead and would be render as:
308// "A"
310 Token &CurrentToken, Token::Type CurrentType) {
311 Token &PrevToken = Tokens[Idx - 1];
312 StringRef PrevTokenBody = PrevToken.TokenBody;
313 StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v");
314 size_t Indentation = PrevTokenBody.size() - Unindented.size();
315 PrevToken.TokenBody = Unindented.str();
316 CurrentToken.setIndentation(Indentation);
317}
318
319struct Tag {
320 enum class Kind {
322 Normal, // {{...}}
323 Triple, // {{{...}}}
324 };
325
327 StringRef Content; // The content between the delimiters.
328 StringRef FullMatch; // The entire tag, including delimiters.
330};
331
332[[maybe_unused]] static const char *tagKindToString(Tag::Kind K) {
333 switch (K) {
334 case Tag::Kind::None:
335 return "None";
337 return "Normal";
339 return "Triple";
340 }
341 llvm_unreachable("Unknown Tag::Kind");
342}
343
344[[maybe_unused]] static const char *jsonKindToString(json::Value::Kind K) {
345 switch (K) {
347 return "JSON_KIND_NULL";
349 return "JSON_KIND_BOOLEAN";
351 return "JSON_KIND_NUMBER";
353 return "JSON_KIND_STRING";
355 return "JSON_KIND_ARRAY";
357 return "JSON_KIND_OBJECT";
358 }
359 llvm_unreachable("Unknown json::Value::Kind");
360}
361
362static Tag findNextTag(StringRef Template, size_t StartPos, StringRef Open,
363 StringRef Close) {
364 const StringLiteral TripleOpen("{{{");
365 const StringLiteral TripleClose("}}}");
366
367 size_t NormalOpenPos = Template.find(Open, StartPos);
368 size_t TripleOpenPos = Template.find(TripleOpen, StartPos);
369
370 Tag Result;
371
372 // Determine which tag comes first.
373 if (TripleOpenPos != StringRef::npos &&
374 (NormalOpenPos == StringRef::npos || TripleOpenPos <= NormalOpenPos)) {
375 // Found a triple mustache tag.
376 size_t EndPos =
377 Template.find(TripleClose, TripleOpenPos + TripleOpen.size());
378 if (EndPos == StringRef::npos)
379 return Result; // No closing tag found.
380
381 Result.TagKind = Tag::Kind::Triple;
382 Result.StartPosition = TripleOpenPos;
383 size_t ContentStart = TripleOpenPos + TripleOpen.size();
384 Result.Content = Template.substr(ContentStart, EndPos - ContentStart);
385 Result.FullMatch = Template.substr(
386 TripleOpenPos, (EndPos + TripleClose.size()) - TripleOpenPos);
387 } else if (NormalOpenPos != StringRef::npos) {
388 // Found a normal mustache tag.
389 size_t EndPos = Template.find(Close, NormalOpenPos + Open.size());
390 if (EndPos == StringRef::npos)
391 return Result; // No closing tag found.
392
393 Result.TagKind = Tag::Kind::Normal;
394 Result.StartPosition = NormalOpenPos;
395 size_t ContentStart = NormalOpenPos + Open.size();
396 Result.Content = Template.substr(ContentStart, EndPos - ContentStart);
397 Result.FullMatch =
398 Template.substr(NormalOpenPos, (EndPos + Close.size()) - NormalOpenPos);
399 }
400
401 return Result;
402}
403
404static std::optional<std::pair<StringRef, StringRef>>
406 LLVM_DEBUG(dbgs() << "[Tag] " << T.FullMatch << ", Content: " << T.Content
407 << ", Kind: " << tagKindToString(T.TagKind) << "\n");
408 if (T.TagKind == Tag::Kind::Triple) {
409 Tokens.emplace_back(T.FullMatch.str(), "&" + T.Content.str(), '&');
410 return std::nullopt;
411 }
412 StringRef Interpolated = T.Content;
413 std::string RawBody = T.FullMatch.str();
414 if (!Interpolated.trim().starts_with("=")) {
415 char Front = Interpolated.empty() ? ' ' : Interpolated.trim().front();
416 Tokens.emplace_back(RawBody, Interpolated.str(), Front);
417 return std::nullopt;
418 }
419 Tokens.emplace_back(RawBody, Interpolated.str(), '=');
420 StringRef DelimSpec = Interpolated.trim();
421 DelimSpec = DelimSpec.drop_front(1);
422 DelimSpec = DelimSpec.take_until([](char C) { return C == '='; });
423 DelimSpec = DelimSpec.trim();
424
425 std::pair<StringRef, StringRef> Ret = DelimSpec.split(' ');
426 LLVM_DEBUG(dbgs() << "[Set Delimiter] NewOpen: " << Ret.first
427 << ", NewClose: " << Ret.second << "\n");
428 return Ret;
429}
430
431// Simple tokenizer that splits the template into tokens.
432// The mustache spec allows {{{ }}} to unescape variables,
433// but we don't support that here. An unescape variable
434// is represented only by {{& variable}}.
436 LLVM_DEBUG(dbgs() << "[Tokenize Template] \"" << Template << "\"\n");
437 SmallVector<Token> Tokens;
438 SmallString<8> Open("{{");
439 SmallString<8> Close("}}");
440 size_t Start = 0;
441
442 while (Start < Template.size()) {
443 LLVM_DEBUG(dbgs() << "[Tokenize Loop] Start:" << Start << ", Open:'" << Open
444 << "', Close:'" << Close << "'\n");
445 Tag T = findNextTag(Template, Start, Open, Close);
446
447 if (T.TagKind == Tag::Kind::None) {
448 // No more tags, the rest is text.
449 Tokens.emplace_back(Template.substr(Start).str());
450 LLVM_DEBUG(dbgs() << " No more tags. Created final Text token: \""
451 << Template.substr(Start) << "\"\n");
452 break;
453 }
454
455 // Add the text before the tag.
456 if (T.StartPosition > Start) {
457 StringRef Text = Template.substr(Start, T.StartPosition - Start);
458 Tokens.emplace_back(Text.str());
459 }
460
461 if (auto NewDelims = processTag(T, Tokens)) {
462 std::tie(Open, Close) = *NewDelims;
463 }
464
465 // Move past the tag.
466 Start = T.StartPosition + T.FullMatch.size();
467 }
468
469 // Fix up white spaces for:
470 // - open sections
471 // - inverted sections
472 // - close sections
473 // - comments
474 //
475 // This loop attempts to find standalone tokens and tries to trim out
476 // the surrounding whitespace.
477 // For example:
478 // if you have the template string
479 // {{#section}} \n Example \n{{/section}}
480 // The output should would be
481 // For example:
482 // \n Example \n
483 size_t LastIdx = Tokens.size() - 1;
484 for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {
485 Token &CurrentToken = Tokens[Idx];
486 Token::Type CurrentType = CurrentToken.getType();
487 // Check if token type requires cleanup.
488 bool RequiresCleanUp = requiresCleanUp(CurrentType);
489
490 if (!RequiresCleanUp)
491 continue;
492
493 // We adjust the token body if there's no text behind or ahead.
494 // A token is considered to have no text ahead if the right of the previous
495 // token is a newline followed by spaces.
496 // A token is considered to have no text behind if the left of the next
497 // token is spaces followed by a newline.
498 // eg.
499 // "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3"
500 bool HasTextBehind = hasTextBehind(Idx, Tokens);
501 bool HasTextAhead = hasTextAhead(Idx, Tokens);
502
503 if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0))
504 stripTokenAhead(Tokens, Idx);
505
506 if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx))
507 stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);
508 }
509 return Tokens;
510}
511
512// Custom stream to escape strings.
514public:
515 explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
516 EscapeMap &Escape)
517 : Escape(Escape), EscapeChars(Escape.keys().begin(), Escape.keys().end()),
518 WrappedStream(WrappedStream) {
520 }
521
522protected:
523 void write_impl(const char *Ptr, size_t Size) override {
525 size_t Start = 0;
526 while (Start < Size) {
527 // Find the next character that needs to be escaped.
528 size_t Next = Data.find_first_of(EscapeChars.str(), Start);
529
530 // If no escapable characters are found, write the rest of the string.
531 if (Next == StringRef::npos) {
532 WrappedStream << Data.substr(Start);
533 return;
534 }
535
536 // Write the chunk of text before the escapable character.
537 if (Next > Start)
538 WrappedStream << Data.substr(Start, Next - Start);
539
540 // Look up and write the escaped version of the character.
541 WrappedStream << Escape[Data[Next]];
542 Start = Next + 1;
543 }
544 }
545
546 uint64_t current_pos() const override { return WrappedStream.tell(); }
547
548private:
549 EscapeMap &Escape;
550 SmallString<8> EscapeChars;
551 llvm::raw_ostream &WrappedStream;
552};
553
554// Custom stream to add indentation used to for rendering partials.
556public:
558 size_t Indentation)
559 : Indentation(Indentation), WrappedStream(WrappedStream),
560 NeedsIndent(true), IsSuspended(false) {
562 }
563
564 void suspendIndentation() override { IsSuspended = true; }
565 void resumeIndentation() override { IsSuspended = false; }
566
567protected:
568 void write_impl(const char *Ptr, size_t Size) override {
570 SmallString<0> Indent;
571 Indent.resize(Indentation, ' ');
572
573 for (char C : Data) {
574 LLVM_DEBUG(dbgs() << "[Indentation Stream] NeedsIndent:" << NeedsIndent
575 << ", C:'" << C << "', Indentation:" << Indentation
576 << "\n");
577 if (NeedsIndent && C != '\n') {
578 WrappedStream << Indent;
579 NeedsIndent = false;
580 }
581 WrappedStream << C;
582 if (C == '\n' && !IsSuspended)
583 NeedsIndent = true;
584 }
585 }
586
587 uint64_t current_pos() const override { return WrappedStream.tell(); }
588
589private:
590 size_t Indentation;
591 raw_ostream &WrappedStream;
592 bool NeedsIndent;
593 bool IsSuspended;
594};
595
596class Parser {
597public:
599 : Ctx(Ctx), TemplateStr(TemplateStr) {}
600
601 AstPtr parse();
602
603private:
604 void parseMustache(ASTNode *Parent);
605 void parseSection(ASTNode *Parent, ASTNode::Type Ty, const Accessor &A);
606
607 MustacheContext &Ctx;
608 SmallVector<Token> Tokens;
609 size_t CurrentPtr;
610 StringRef TemplateStr;
611};
612
613void Parser::parseSection(ASTNode *Parent, ASTNode::Type Ty,
614 const Accessor &A) {
615 AstPtr CurrentNode = createNode(Ctx, Ty, A, Parent);
616 size_t Start = CurrentPtr;
617 parseMustache(CurrentNode.get());
618 const size_t End = CurrentPtr - 1;
619 std::string RawBody;
620 for (std::size_t I = Start; I < End; I++)
621 RawBody += Tokens[I].RawBody;
622 CurrentNode->setRawBody(std::move(RawBody));
623 Parent->addChild(std::move(CurrentNode));
624}
625
627 Tokens = tokenize(TemplateStr);
628 CurrentPtr = 0;
629 AstPtr RootNode = createRootNode(Ctx);
630 parseMustache(RootNode.get());
631 return RootNode;
632}
633
634void Parser::parseMustache(ASTNode *Parent) {
635
636 while (CurrentPtr < Tokens.size()) {
637 Token CurrentToken = Tokens[CurrentPtr];
638 CurrentPtr++;
639 Accessor A = CurrentToken.getAccessor();
640 AstPtr CurrentNode;
641
642 switch (CurrentToken.getType()) {
643 case Token::Type::Text: {
644 CurrentNode =
645 createTextNode(Ctx, std::move(CurrentToken.TokenBody), Parent);
646 Parent->addChild(std::move(CurrentNode));
647 break;
648 }
650 CurrentNode = createNode(Ctx, ASTNode::Variable, std::move(A), Parent);
651 Parent->addChild(std::move(CurrentNode));
652 break;
653 }
655 CurrentNode =
656 createNode(Ctx, ASTNode::UnescapeVariable, std::move(A), Parent);
657 Parent->addChild(std::move(CurrentNode));
658 break;
659 }
661 CurrentNode = createNode(Ctx, ASTNode::Partial, std::move(A), Parent);
662 CurrentNode->setIndentation(CurrentToken.getIndentation());
663 Parent->addChild(std::move(CurrentNode));
664 break;
665 }
667 parseSection(Parent, ASTNode::Section, A);
668 break;
669 }
671 parseSection(Parent, ASTNode::InvertSection, A);
672 break;
673 }
676 break;
678 return;
679 }
680 }
681}
683 LLVM_DEBUG(dbgs() << "[To Mustache String] Kind: "
684 << jsonKindToString(Data.kind()) << ", Data: " << Data
685 << "\n");
686 switch (Data.kind()) {
688 return;
689 case json::Value::Number: {
690 auto Num = *Data.getAsNumber();
691 std::ostringstream SS;
692 SS << Num;
693 OS << SS.str();
694 return;
695 }
696 case json::Value::String: {
697 auto Str = *Data.getAsString();
698 OS << Str.str();
699 return;
700 }
701
702 case json::Value::Array: {
703 auto Arr = *Data.getAsArray();
704 if (Arr.empty())
705 return;
706 [[fallthrough]];
707 }
710 llvm::json::OStream JOS(OS, 2);
711 JOS.value(Data);
712 break;
713 }
714 }
715}
716
717void ASTNode::renderRoot(const json::Value &CurrentCtx,
719 renderChild(CurrentCtx, OS);
720}
721
722void ASTNode::renderText(MustacheOutputStream &OS) { OS << Body; }
723
724void ASTNode::renderPartial(const json::Value &CurrentCtx,
726 LLVM_DEBUG(dbgs() << "[Render Partial] Accessor:" << AccessorValue[0]
727 << ", Indentation:" << Indentation << "\n");
728 auto Partial = Ctx.Partials.find(AccessorValue[0]);
729 if (Partial != Ctx.Partials.end())
730 renderPartial(CurrentCtx, OS, Partial->getValue().get());
731}
732
733void ASTNode::renderVariable(const json::Value &CurrentCtx,
735 auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);
736 if (Lambda != Ctx.Lambdas.end()) {
737 renderLambdas(CurrentCtx, OS, Lambda->getValue());
738 } else if (const json::Value *ContextPtr = findContext()) {
739 EscapeStringStream ES(OS, Ctx.Escapes);
740 toMustacheString(*ContextPtr, ES);
741 }
742}
743
744void ASTNode::renderUnescapeVariable(const json::Value &CurrentCtx,
746 LLVM_DEBUG(dbgs() << "[Render UnescapeVariable] Accessor:" << AccessorValue[0]
747 << "\n");
748 auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);
749 if (Lambda != Ctx.Lambdas.end()) {
750 renderLambdas(CurrentCtx, OS, Lambda->getValue());
751 } else if (const json::Value *ContextPtr = findContext()) {
753 toMustacheString(*ContextPtr, OS);
755 }
756}
757
758void ASTNode::renderSection(const json::Value &CurrentCtx,
760 auto SectionLambda = Ctx.SectionLambdas.find(AccessorValue[0]);
761 if (SectionLambda != Ctx.SectionLambdas.end()) {
762 renderSectionLambdas(CurrentCtx, OS, SectionLambda->getValue());
763 return;
764 }
765
766 const json::Value *ContextPtr = findContext();
767 if (isContextFalsey(ContextPtr))
768 return;
769
770 if (const json::Array *Arr = ContextPtr->getAsArray()) {
771 for (const json::Value &V : *Arr)
772 renderChild(V, OS);
773 return;
774 }
775 renderChild(*ContextPtr, OS);
776}
777
778void ASTNode::renderInvertSection(const json::Value &CurrentCtx,
780 bool IsLambda = Ctx.SectionLambdas.contains(AccessorValue[0]);
781 const json::Value *ContextPtr = findContext();
782 if (isContextFalsey(ContextPtr) && !IsLambda) {
783 renderChild(CurrentCtx, OS);
784 }
785}
786
788 if (Ty != Root && Ty != Text && AccessorValue.empty())
789 return;
790 // Set the parent context to the incoming context so that we
791 // can walk up the context tree correctly in findContext().
792 ParentContext = &Data;
793
794 switch (Ty) {
795 case Root:
796 renderRoot(Data, OS);
797 return;
798 case Text:
799 renderText(OS);
800 return;
801 case Partial:
802 renderPartial(Data, OS);
803 return;
804 case Variable:
805 renderVariable(Data, OS);
806 return;
807 case UnescapeVariable:
808 renderUnescapeVariable(Data, OS);
809 return;
810 case Section:
811 renderSection(Data, OS);
812 return;
813 case InvertSection:
814 renderInvertSection(Data, OS);
815 return;
816 }
817 llvm_unreachable("Invalid ASTNode type");
818}
819
820const json::Value *ASTNode::findContext() {
821 // The mustache spec allows for dot notation to access nested values
822 // a single dot refers to the current context.
823 // We attempt to find the JSON context in the current node, if it is not
824 // found, then we traverse the parent nodes to find the context until we
825 // reach the root node or the context is found.
826 if (AccessorValue.empty())
827 return nullptr;
828 if (AccessorValue[0] == ".")
829 return ParentContext;
830
831 const json::Object *CurrentContext = ParentContext->getAsObject();
832 StringRef CurrentAccessor = AccessorValue[0];
833 ASTNode *CurrentParent = Parent;
834
835 while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
836 if (CurrentParent->Ty != Root) {
837 CurrentContext = CurrentParent->ParentContext->getAsObject();
838 CurrentParent = CurrentParent->Parent;
839 continue;
840 }
841 return nullptr;
842 }
843 const json::Value *Context = nullptr;
844 for (auto [Idx, Acc] : enumerate(AccessorValue)) {
845 const json::Value *CurrentValue = CurrentContext->get(Acc);
846 if (!CurrentValue)
847 return nullptr;
848 if (Idx < AccessorValue.size() - 1) {
849 CurrentContext = CurrentValue->getAsObject();
850 if (!CurrentContext)
851 return nullptr;
852 } else {
853 Context = CurrentValue;
854 }
855 }
856 return Context;
857}
858
859void ASTNode::renderChild(const json::Value &Contexts,
861 for (AstPtr &Child : Children)
862 Child->render(Contexts, OS);
863}
864
865void ASTNode::renderPartial(const json::Value &Contexts,
866 MustacheOutputStream &OS, ASTNode *Partial) {
867 LLVM_DEBUG(dbgs() << "[Render Partial Indentation] Indentation: " << Indentation << "\n");
868 AddIndentationStringStream IS(OS, Indentation);
869 Partial->render(Contexts, IS);
870}
871
872void ASTNode::renderLambdas(const json::Value &Contexts,
874 json::Value LambdaResult = L();
875 std::string LambdaStr;
876 raw_string_ostream Output(LambdaStr);
877 toMustacheString(LambdaResult, Output);
878 Parser P(LambdaStr, Ctx);
879 AstPtr LambdaNode = P.parse();
880
881 EscapeStringStream ES(OS, Ctx.Escapes);
882 if (Ty == Variable) {
883 LambdaNode->render(Contexts, ES);
884 return;
885 }
886 LambdaNode->render(Contexts, OS);
887}
888
889void ASTNode::renderSectionLambdas(const json::Value &Contexts,
891 json::Value Return = L(RawBody);
892 if (isFalsey(Return))
893 return;
894 std::string LambdaStr;
895 raw_string_ostream Output(LambdaStr);
896 toMustacheString(Return, Output);
897 Parser P(LambdaStr, Ctx);
898 AstPtr LambdaNode = P.parse();
899 LambdaNode->render(Contexts, OS);
900}
901
904 Tree->render(Data, MOS);
905}
906
907void Template::registerPartial(std::string Name, std::string Partial) {
908 Parser P(Partial, Ctx);
909 AstPtr PartialTree = P.parse();
910 Ctx.Partials.insert(std::make_pair(Name, std::move(PartialTree)));
911}
912
913void Template::registerLambda(std::string Name, Lambda L) {
914 Ctx.Lambdas[Name] = L;
915}
916
917void Template::registerLambda(std::string Name, SectionLambda L) {
918 Ctx.SectionLambdas[Name] = L;
919}
920
922 Ctx.Escapes = std::move(E);
923}
924
926 Parser P(TemplateStr, Ctx);
927 Tree = P.parse();
928 // The default behavior is to escape html entities.
929 const EscapeMap HtmlEntities = {{'&', "&amp;"},
930 {'<', "&lt;"},
931 {'>', "&gt;"},
932 {'"', "&quot;"},
933 {'\'', "&#39;"}};
934 overrideEscapeCharacters(HtmlEntities);
935}
936
938 : Ctx(std::move(Other.Ctx)), Tree(std::move(Other.Tree)) {}
939
940Template::~Template() = default;
941
943 if (this != &Other) {
944 Ctx = std::move(Other.Ctx);
945 Tree = std::move(Other.Tree);
946 Other.Tree = nullptr;
947 }
948 return *this;
949}
950} // namespace llvm::mustache
951
952#undef DEBUG_TYPE
static GCRegistry::Add< ErlangGC > A("erlang", "erlang-compatible garbage collector")
#define I(x, y, z)
Definition MD5.cpp:58
#define T
#define P(N)
This file defines the SmallVector class.
#define LLVM_DEBUG(...)
Definition Debug.h:114
static SymbolRef::Type getType(const Symbol *Sym)
Definition TapiFile.cpp:39
ArrayRef - Represent a constant reference to an array (0 or more elements consecutively in memory),...
Definition ArrayRef.h:41
size_t size() const
size - Get the array size.
Definition ArrayRef.h:147
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
Definition SmallString.h:26
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
reference emplace_back(ArgTypes &&... Args)
void resize(size_type N)
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
A wrapper around a string literal that serves as a proxy for constructing global tables of StringRefs...
Definition StringRef.h:854
StringRef - Represent a constant reference to a string, i.e.
Definition StringRef.h:55
std::pair< StringRef, StringRef > split(char Separator) const
Split into two substrings around the first occurrence of a separator character.
Definition StringRef.h:702
std::string str() const
str - Get the contents as an std::string.
Definition StringRef.h:225
constexpr StringRef substr(size_t Start, size_t N=npos) const
Return a reference to the substring from [Start, Start + N).
Definition StringRef.h:573
bool starts_with(StringRef Prefix) const
Check if this string starts with the given Prefix.
Definition StringRef.h:261
constexpr bool empty() const
empty - Check if the string is empty.
Definition StringRef.h:143
StringRef drop_front(size_t N=1) const
Return a StringRef equal to 'this' but with the first N elements dropped.
Definition StringRef.h:611
constexpr size_t size() const
size - Get the string size.
Definition StringRef.h:146
char front() const
front - Get the first character in the string.
Definition StringRef.h:149
StringRef ltrim(char Char) const
Return string with consecutive Char characters starting from the the left removed.
Definition StringRef.h:792
StringRef rtrim(char Char) const
Return string with consecutive Char characters starting from the right removed.
Definition StringRef.h:804
StringRef take_until(function_ref< bool(char)> F) const
Return the longest prefix of 'this' such that no character in the prefix satisfies the given predicat...
Definition StringRef.h:605
StringRef trim(char Char) const
Return string with consecutive Char characters starting from the left and right removed.
Definition StringRef.h:816
bool ends_with(StringRef Suffix) const
Check if this string ends with the given Suffix.
Definition StringRef.h:273
static constexpr size_t npos
Definition StringRef.h:57
The instances of the Type class are immutable: once they are created, they are never changed.
Definition Type.h:45
json::OStream allows writing well-formed JSON without materializing all structures as json::Value ahe...
Definition JSON.h:998
LLVM_ABI void value(const Value &V)
Emit a self-contained value (number, string, vector<string> etc).
Definition JSON.cpp:747
An Object is a JSON object, which maps strings to heterogenous JSON values.
Definition JSON.h:98
LLVM_ABI Value * get(StringRef K)
Definition JSON.cpp:30
A Value is an JSON value of unknown type.
Definition JSON.h:290
friend class Object
Definition JSON.h:493
@ Number
Number values can store both int64s and doubles at full precision, depending on what they were constr...
Definition JSON.h:297
friend class Array
Definition JSON.h:492
const json::Object * getAsObject() const
Definition JSON.h:464
const json::Array * getAsArray() const
Definition JSON.h:470
ASTNode(MustacheContext &Ctx, std::string Body, ASTNode *Parent)
Definition Mustache.cpp:171
ASTNode(MustacheContext &Ctx)
Definition Mustache.cpp:168
void setIndentation(size_t NewIndentation)
Definition Mustache.cpp:184
ASTNode(MustacheContext &Ctx, Type Ty, Accessor Accessor, ASTNode *Parent)
Definition Mustache.cpp:176
void render(const llvm::json::Value &Data, MustacheOutputStream &OS)
Definition Mustache.cpp:787
void addChild(AstPtr Child)
Definition Mustache.cpp:180
void setRawBody(std::string NewBody)
Definition Mustache.cpp:182
AddIndentationStringStream(raw_ostream &WrappedStream, size_t Indentation)
Definition Mustache.cpp:557
uint64_t current_pos() const override
Return the current position within the stream, not counting the bytes currently in the buffer.
Definition Mustache.cpp:587
void write_impl(const char *Ptr, size_t Size) override
The is the piece of the class that is implemented by subclasses.
Definition Mustache.cpp:568
uint64_t current_pos() const override
Return the current position within the stream, not counting the bytes currently in the buffer.
Definition Mustache.cpp:546
void write_impl(const char *Ptr, size_t Size) override
The is the piece of the class that is implemented by subclasses.
Definition Mustache.cpp:523
EscapeStringStream(llvm::raw_ostream &WrappedStream, EscapeMap &Escape)
Definition Mustache.cpp:515
~MustacheOutputStream() override=default
Parser(StringRef TemplateStr, MustacheContext &Ctx)
Definition Mustache.cpp:598
LLVM_ABI void registerPartial(std::string Name, std::string Partial)
Definition Mustache.cpp:907
Template & operator=(const Template &)=delete
LLVM_ABI void registerLambda(std::string Name, Lambda Lambda)
Definition Mustache.cpp:913
LLVM_ABI Template(StringRef TemplateStr)
Definition Mustache.cpp:925
LLVM_ABI void render(const llvm::json::Value &Data, llvm::raw_ostream &OS)
Definition Mustache.cpp:902
LLVM_ABI void overrideEscapeCharacters(DenseMap< char, std::string > Escapes)
Definition Mustache.cpp:921
Token(std::string Str)
Definition Mustache.cpp:100
Type getType() const
Definition Mustache.cpp:118
size_t getIndentation() const
Definition Mustache.cpp:122
void setIndentation(size_t NewIndentation)
Definition Mustache.cpp:120
static Type getTokenType(char Identifier)
Definition Mustache.cpp:124
Token(std::string RawBody, std::string TokenBody, char Identifier)
Definition Mustache.cpp:104
Accessor getAccessor() const
Definition Mustache.cpp:116
std::string TokenBody
Definition Mustache.cpp:149
This class implements an extremely fast bulk output stream that can only output to a stream.
Definition raw_ostream.h:53
raw_ostream(bool unbuffered=false, OStreamKind K=OStreamKind::OK_OStream)
uint64_t tell() const
tell - Return the current offset with the file.
raw_ostream & write(unsigned char C)
void SetUnbuffered()
Set the stream to be unbuffered.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
@ C
The default llvm calling convention, compatible with C.
Definition CallingConv.h:34
static bool hasTextAhead(size_t Idx, const ArrayRef< Token > &Tokens)
Definition Mustache.cpp:267
static AstPtr createTextNode(MustacheContext &Ctx, std::string Body, ASTNode *Parent)
Definition Mustache.cpp:234
static AstPtr createRootNode(MustacheContext &Ctx)
Definition Mustache.cpp:225
static const char * tagKindToString(Tag::Kind K)
Definition Mustache.cpp:332
std::unique_ptr< ASTNode > AstPtr
Definition Mustache.h:87
static SmallVector< Token > tokenize(StringRef Template)
Definition Mustache.cpp:435
void stripTokenBefore(SmallVectorImpl< Token > &Tokens, size_t Idx, Token &CurrentToken, Token::Type CurrentType)
Definition Mustache.cpp:309
static const char * jsonKindToString(json::Value::Kind K)
Definition Mustache.cpp:344
static AstPtr createNode(MustacheContext &Ctx, ASTNode::Type T, Accessor A, ASTNode *Parent)
Definition Mustache.cpp:229
std::function< llvm::json::Value(std::string)> SectionLambda
Definition Mustache.h:84
static Tag findNextTag(StringRef Template, size_t StartPos, StringRef Open, StringRef Close)
Definition Mustache.cpp:362
static void stripTokenAhead(SmallVectorImpl< Token > &Tokens, size_t Idx)
Definition Mustache.cpp:293
std::function< llvm::json::Value()> Lambda
Definition Mustache.h:83
static bool hasTextBehind(size_t Idx, const ArrayRef< Token > &Tokens)
Definition Mustache.cpp:251
static bool requiresCleanUp(Token::Type T)
Definition Mustache.cpp:280
DenseMap< char, std::string > EscapeMap
Definition Mustache.h:88
static std::optional< std::pair< StringRef, StringRef > > processTag(const Tag &T, SmallVectorImpl< Token > &Tokens)
Definition Mustache.cpp:405
static void toMustacheString(const json::Value &Data, raw_ostream &OS)
Definition Mustache.cpp:682
This is an optimization pass for GlobalISel generic memory operations.
auto enumerate(FirstRange &&First, RestRanges &&...Rest)
Given two or more input ranges, returns a new range whose values are tuples (A, B,...
Definition STLExtras.h:2452
LLVM_ABI raw_ostream & dbgs()
dbgs() - This returns a reference to a raw_ostream for debugging messages.
Definition Debug.cpp:207
@ Other
Any other memory.
Definition ModRef.h:68
FunctionAddr VTableAddr uintptr_t uintptr_t Data
Definition InstrProf.h:189
FunctionAddr VTableAddr Next
Definition InstrProf.h:141
OutputIt move(R &&Range, OutputIt Out)
Provide wrappers to std::move which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1847
Implement std::hash so that hash_code can be used in STL containers.
Definition BitVector.h:870