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 = ArrayRef<StringRef>;
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, MustacheContext &Ctx) {
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.
44 if (Str == ".") {
45 // "." is a special accessor that refers to the current context.
46 // It's a literal, so it doesn't need to be saved.
47 Tokens.push_back(".");
48 } else {
49 while (!Str.empty()) {
50 StringRef Part;
51 std::tie(Part, Str) = Str.split('.');
52 // Each part of the accessor needs to be saved to the arena
53 // to ensure it has a stable address.
54 Tokens.push_back(Ctx.Saver.save(Part.trim()));
55 }
56 }
57 // Now, allocate memory for the array of StringRefs in the arena.
58 StringRef *ArenaTokens = Ctx.Allocator.Allocate<StringRef>(Tokens.size());
59 // Copy the StringRefs from the stack vector to the arena.
60 std::copy(Tokens.begin(), Tokens.end(), ArenaTokens);
61 // Return an ArrayRef pointing to the stable arena memory.
62 return ArrayRef<StringRef>(ArenaTokens, Tokens.size());
63}
64} // namespace
65
66namespace llvm::mustache {
67
69public:
71 ~MustacheOutputStream() override = default;
72
73 virtual void suspendIndentation() {}
74 virtual void resumeIndentation() {}
75
76private:
77 void anchor() override;
78};
79
80void MustacheOutputStream::anchor() {}
81
83public:
85
86private:
87 raw_ostream &OS;
88
89 void write_impl(const char *Ptr, size_t Size) override {
90 OS.write(Ptr, Size);
91 }
92 uint64_t current_pos() const override { return OS.tell(); }
93};
94
95class Token {
96public:
108
112
114 MustacheContext &Ctx)
116 TokenType = getTokenType(Identifier);
118 return;
119 StringRef AccessorStr(this->TokenBody);
121 AccessorStr = AccessorStr.substr(1);
122 AccessorValue = splitMustacheString(StringRef(AccessorStr).trim(), Ctx);
123 }
124
126
127 Type getType() const { return TokenType; }
128
129 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }
130
131 size_t getIndentation() const { return Indentation; }
132
133 static Type getTokenType(char Identifier) {
134 switch (Identifier) {
135 case '#':
136 return Type::SectionOpen;
137 case '/':
138 return Type::SectionClose;
139 case '^':
141 case '!':
142 return Type::Comment;
143 case '>':
144 return Type::Partial;
145 case '&':
147 case '=':
148 return Type::SetDelimiter;
149 default:
150 return Type::Variable;
151 }
152 }
153
155 // RawBody is the original string that was tokenized.
157 // TokenBody is the original string with the identifier removed.
161};
162
164
165class ASTNode : public ilist_node<ASTNode> {
166public:
176
178 : Ctx(Ctx), Ty(Type::Root), Parent(nullptr), ParentContext(nullptr) {}
179
181 : Ctx(Ctx), Ty(Type::Text), Body(Body), Parent(Parent),
182 ParentContext(nullptr) {}
183
184 // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes
186 ASTNode *Parent)
187 : Ctx(Ctx), Ty(Ty), Parent(Parent), AccessorValue(Accessor),
188 ParentContext(nullptr) {}
189
190 void addChild(AstPtr Child) { Children.push_back(Child); };
191
192 void setRawBody(StringRef NewBody) { RawBody = NewBody; };
193
194 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
195
197
198private:
199 void renderLambdas(const llvm::json::Value &Contexts,
201
202 void renderSectionLambdas(const llvm::json::Value &Contexts,
204
205 void renderPartial(const llvm::json::Value &Contexts,
207
208 void renderChild(const llvm::json::Value &Context, MustacheOutputStream &OS);
209
210 const llvm::json::Value *findContext();
211
212 void renderRoot(const json::Value &CurrentCtx, MustacheOutputStream &OS);
213 void renderText(MustacheOutputStream &OS);
214 void renderPartial(const json::Value &CurrentCtx, MustacheOutputStream &OS);
215 void renderVariable(const json::Value &CurrentCtx, MustacheOutputStream &OS);
216 void renderUnescapeVariable(const json::Value &CurrentCtx,
218 void renderSection(const json::Value &CurrentCtx, MustacheOutputStream &OS);
219 void renderInvertSection(const json::Value &CurrentCtx,
221
222 MustacheContext &Ctx;
223 Type Ty;
224 size_t Indentation = 0;
225 StringRef RawBody;
226 StringRef Body;
227 ASTNode *Parent;
228 ASTNodeList Children;
229 const ArrayRef<StringRef> AccessorValue;
230 const llvm::json::Value *ParentContext;
231};
232
233// A wrapper for arena allocator for ASTNodes
235 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx);
236}
237
239 ArrayRef<StringRef> A, ASTNode *Parent) {
240 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, T, A, Parent);
241}
242
244 ASTNode *Parent) {
245 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, Body, Parent);
246}
247
248// Function to check if there is meaningful text behind.
249// We determine if a token has meaningful text behind
250// if the right of previous token contains anything that is
251// not a newline.
252// For example:
253// "Stuff {{#Section}}" (returns true)
254// vs
255// "{{#Section}} \n" (returns false)
256// We make an exception for when previous token is empty
257// and the current token is the second token.
258// For example:
259// "{{#Section}}"
260static bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) {
261 if (Idx == 0)
262 return true;
263
264 size_t PrevIdx = Idx - 1;
265 if (Tokens[PrevIdx].getType() != Token::Type::Text)
266 return true;
267
268 const Token &PrevToken = Tokens[PrevIdx];
269 StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v");
270 return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1);
271}
272
273// Function to check if there's no meaningful text ahead.
274// We determine if a token has text ahead if the left of previous
275// token does not start with a newline.
276static bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) {
277 if (Idx >= Tokens.size() - 1)
278 return true;
279
280 size_t NextIdx = Idx + 1;
281 if (Tokens[NextIdx].getType() != Token::Type::Text)
282 return true;
283
284 const Token &NextToken = Tokens[NextIdx];
285 StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" ");
286 return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n");
287}
288
290 // We must clean up all the tokens that could contain child nodes.
294}
295
296// Adjust next token body if there is no text ahead.
297// For example:
298// The template string
299// "{{! Comment }} \nLine 2"
300// would be considered as no text ahead and should be rendered as
301// " Line 2"
302static void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {
303 Token &NextToken = Tokens[Idx + 1];
304 StringRef NextTokenBody = NextToken.TokenBody;
305 // Cut off the leading newline which could be \n or \r\n.
306 if (NextTokenBody.starts_with("\r\n"))
307 NextToken.TokenBody = NextTokenBody.substr(2);
308 else if (NextTokenBody.starts_with("\n"))
309 NextToken.TokenBody = NextTokenBody.substr(1);
310}
311
312// Adjust previous token body if there no text behind.
313// For example:
314// The template string
315// " \t{{#section}}A{{/section}}"
316// would be considered as having no text ahead and would be render as:
317// "A"
319 Token &CurrentToken, Token::Type CurrentType) {
320 Token &PrevToken = Tokens[Idx - 1];
321 StringRef PrevTokenBody = PrevToken.TokenBody;
322 StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v");
323 size_t Indentation = PrevTokenBody.size() - Unindented.size();
324 PrevToken.TokenBody = Unindented;
325 CurrentToken.setIndentation(Indentation);
326}
327
328struct Tag {
329 enum class Kind {
331 Normal, // {{...}}
332 Triple, // {{{...}}}
333 };
334
336 StringRef Content; // The content between the delimiters.
337 StringRef FullMatch; // The entire tag, including delimiters.
339};
340
341[[maybe_unused]] static const char *tagKindToString(Tag::Kind K) {
342 switch (K) {
343 case Tag::Kind::None:
344 return "None";
346 return "Normal";
348 return "Triple";
349 }
350 llvm_unreachable("Unknown Tag::Kind");
351}
352
353[[maybe_unused]] static const char *jsonKindToString(json::Value::Kind K) {
354 switch (K) {
356 return "JSON_KIND_NULL";
358 return "JSON_KIND_BOOLEAN";
360 return "JSON_KIND_NUMBER";
362 return "JSON_KIND_STRING";
364 return "JSON_KIND_ARRAY";
366 return "JSON_KIND_OBJECT";
367 }
368 llvm_unreachable("Unknown json::Value::Kind");
369}
370
371static Tag findNextTag(StringRef Template, size_t StartPos, StringRef Open,
372 StringRef Close) {
373 const StringLiteral TripleOpen("{{{");
374 const StringLiteral TripleClose("}}}");
375
376 size_t NormalOpenPos = Template.find(Open, StartPos);
377 size_t TripleOpenPos = Template.find(TripleOpen, StartPos);
378
379 Tag Result;
380
381 // Determine which tag comes first.
382 if (TripleOpenPos != StringRef::npos &&
383 (NormalOpenPos == StringRef::npos || TripleOpenPos <= NormalOpenPos)) {
384 // Found a triple mustache tag.
385 size_t EndPos =
386 Template.find(TripleClose, TripleOpenPos + TripleOpen.size());
387 if (EndPos == StringRef::npos)
388 return Result; // No closing tag found.
389
390 Result.TagKind = Tag::Kind::Triple;
391 Result.StartPosition = TripleOpenPos;
392 size_t ContentStart = TripleOpenPos + TripleOpen.size();
393 Result.Content = Template.substr(ContentStart, EndPos - ContentStart);
394 Result.FullMatch = Template.substr(
395 TripleOpenPos, (EndPos + TripleClose.size()) - TripleOpenPos);
396 } else if (NormalOpenPos != StringRef::npos) {
397 // Found a normal mustache tag.
398 size_t EndPos = Template.find(Close, NormalOpenPos + Open.size());
399 if (EndPos == StringRef::npos)
400 return Result; // No closing tag found.
401
402 Result.TagKind = Tag::Kind::Normal;
403 Result.StartPosition = NormalOpenPos;
404 size_t ContentStart = NormalOpenPos + Open.size();
405 Result.Content = Template.substr(ContentStart, EndPos - ContentStart);
406 Result.FullMatch =
407 Template.substr(NormalOpenPos, (EndPos + Close.size()) - NormalOpenPos);
408 }
409
410 return Result;
411}
412
413static std::optional<std::pair<StringRef, StringRef>>
415 LLVM_DEBUG(dbgs() << "[Tag] " << T.FullMatch << ", Content: " << T.Content
416 << ", Kind: " << tagKindToString(T.TagKind) << "\n");
417 if (T.TagKind == Tag::Kind::Triple) {
418 Tokens.emplace_back(T.FullMatch, Ctx.Saver.save("&" + T.Content), '&', Ctx);
419 return std::nullopt;
420 }
421 StringRef Interpolated = T.Content;
422 if (!Interpolated.trim().starts_with("=")) {
423 char Front = Interpolated.empty() ? ' ' : Interpolated.trim().front();
424 Tokens.emplace_back(T.FullMatch, Interpolated, Front, Ctx);
425 return std::nullopt;
426 }
427 Tokens.emplace_back(T.FullMatch, Interpolated, '=', Ctx);
428 StringRef DelimSpec = Interpolated.trim();
429 DelimSpec = DelimSpec.drop_front(1);
430 DelimSpec = DelimSpec.take_until([](char C) { return C == '='; });
431 DelimSpec = DelimSpec.trim();
432
433 std::pair<StringRef, StringRef> Ret = DelimSpec.split(' ');
434 LLVM_DEBUG(dbgs() << "[Set Delimiter] NewOpen: " << Ret.first
435 << ", NewClose: " << Ret.second << "\n");
436 return Ret;
437}
438
439// Simple tokenizer that splits the template into tokens.
440// The mustache spec allows {{{ }}} to unescape variables,
441// but we don't support that here. An unescape variable
442// is represented only by {{& variable}}.
444 LLVM_DEBUG(dbgs() << "[Tokenize Template] \"" << Template << "\"\n");
445 SmallVector<Token> Tokens;
446 SmallString<8> Open("{{");
447 SmallString<8> Close("}}");
448 size_t Start = 0;
449
450 while (Start < Template.size()) {
451 LLVM_DEBUG(dbgs() << "[Tokenize Loop] Start:" << Start << ", Open:'" << Open
452 << "', Close:'" << Close << "'\n");
453 Tag T = findNextTag(Template, Start, Open, Close);
454
455 if (T.TagKind == Tag::Kind::None) {
456 // No more tags, the rest is text.
457 Tokens.emplace_back(Template.substr(Start));
458 break;
459 }
460
461 // Add the text before the tag.
462 if (T.StartPosition > Start) {
463 StringRef Text = Template.substr(Start, T.StartPosition - Start);
464 Tokens.emplace_back(Text);
465 }
466
467 if (auto NewDelims = processTag(T, Tokens, Ctx)) {
468 std::tie(Open, Close) = *NewDelims;
469 }
470
471 // Move past the tag.
472 Start = T.StartPosition + T.FullMatch.size();
473 }
474
475 // Fix up white spaces for:
476 // - open sections
477 // - inverted sections
478 // - close sections
479 // - comments
480 //
481 // This loop attempts to find standalone tokens and tries to trim out
482 // the surrounding whitespace.
483 // For example:
484 // if you have the template string
485 // {{#section}} \n Example \n{{/section}}
486 // The output should would be
487 // For example:
488 // \n Example \n
489 size_t LastIdx = Tokens.size() - 1;
490 for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {
491 Token &CurrentToken = Tokens[Idx];
492 Token::Type CurrentType = CurrentToken.getType();
493 // Check if token type requires cleanup.
494 bool RequiresCleanUp = requiresCleanUp(CurrentType);
495
496 if (!RequiresCleanUp)
497 continue;
498
499 // We adjust the token body if there's no text behind or ahead.
500 // A token is considered to have no text ahead if the right of the previous
501 // token is a newline followed by spaces.
502 // A token is considered to have no text behind if the left of the next
503 // token is spaces followed by a newline.
504 // eg.
505 // "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3"
506 bool HasTextBehind = hasTextBehind(Idx, Tokens);
507 bool HasTextAhead = hasTextAhead(Idx, Tokens);
508
509 if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0))
510 stripTokenAhead(Tokens, Idx);
511
512 if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx))
513 stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);
514 }
515 return Tokens;
516}
517
518// Custom stream to escape strings.
520public:
521 explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
522 EscapeMap &Escape)
523 : Escape(Escape), EscapeChars(Escape.keys().begin(), Escape.keys().end()),
524 WrappedStream(WrappedStream) {
526 }
527
528protected:
529 void write_impl(const char *Ptr, size_t Size) override {
531 size_t Start = 0;
532 while (Start < Size) {
533 // Find the next character that needs to be escaped.
534 size_t Next = Data.find_first_of(EscapeChars.str(), Start);
535
536 // If no escapable characters are found, write the rest of the string.
537 if (Next == StringRef::npos) {
538 WrappedStream << Data.substr(Start);
539 return;
540 }
541
542 // Write the chunk of text before the escapable character.
543 if (Next > Start)
544 WrappedStream << Data.substr(Start, Next - Start);
545
546 // Look up and write the escaped version of the character.
547 WrappedStream << Escape[Data[Next]];
548 Start = Next + 1;
549 }
550 }
551
552 uint64_t current_pos() const override { return WrappedStream.tell(); }
553
554private:
555 EscapeMap &Escape;
556 SmallString<8> EscapeChars;
557 llvm::raw_ostream &WrappedStream;
558};
559
560// Custom stream to add indentation used to for rendering partials.
562public:
564 size_t Indentation)
565 : Indentation(Indentation), WrappedStream(WrappedStream),
566 NeedsIndent(true), IsSuspended(false) {
568 }
569
570 void suspendIndentation() override { IsSuspended = true; }
571 void resumeIndentation() override { IsSuspended = false; }
572
573protected:
574 void write_impl(const char *Ptr, size_t Size) override {
576 SmallString<0> Indent;
577 Indent.resize(Indentation, ' ');
578
579 for (char C : Data) {
580 LLVM_DEBUG(dbgs() << "[Indentation Stream] NeedsIndent:" << NeedsIndent
581 << ", C:'" << C << "', Indentation:" << Indentation
582 << "\n");
583 if (NeedsIndent && C != '\n') {
584 WrappedStream << Indent;
585 NeedsIndent = false;
586 }
587 WrappedStream << C;
588 if (C == '\n' && !IsSuspended)
589 NeedsIndent = true;
590 }
591 }
592
593 uint64_t current_pos() const override { return WrappedStream.tell(); }
594
595private:
596 size_t Indentation;
597 raw_ostream &WrappedStream;
598 bool NeedsIndent;
599 bool IsSuspended;
600};
601
602class Parser {
603public:
605 : Ctx(Ctx), TemplateStr(TemplateStr) {}
606
607 AstPtr parse();
608
609private:
610 void parseMustache(ASTNode *Parent);
611 void parseSection(ASTNode *Parent, ASTNode::Type Ty, const Accessor &A);
612
613 MustacheContext &Ctx;
614 SmallVector<Token> Tokens;
615 size_t CurrentPtr;
616 StringRef TemplateStr;
617};
618
619void Parser::parseSection(ASTNode *Parent, ASTNode::Type Ty,
620 const Accessor &A) {
621 AstPtr CurrentNode = createNode(Ctx, Ty, A, Parent);
622 size_t Start = CurrentPtr;
623 parseMustache(CurrentNode);
624 const size_t End = CurrentPtr - 1;
625 SmallString<128> RawBody;
626 for (std::size_t I = Start; I < End; I++)
627 RawBody += Tokens[I].RawBody;
628 CurrentNode->setRawBody(Ctx.Saver.save(StringRef(RawBody)));
629 Parent->addChild(CurrentNode);
630}
631
633 Tokens = tokenize(TemplateStr, Ctx);
634 CurrentPtr = 0;
635 AstPtr RootNode = createRootNode(Ctx);
636 parseMustache(RootNode);
637 return RootNode;
638}
639
640void Parser::parseMustache(ASTNode *Parent) {
641
642 while (CurrentPtr < Tokens.size()) {
643 Token CurrentToken = Tokens[CurrentPtr];
644 CurrentPtr++;
645 ArrayRef<StringRef> A = CurrentToken.getAccessor();
646 AstPtr CurrentNode;
647
648 switch (CurrentToken.getType()) {
649 case Token::Type::Text: {
650 CurrentNode = createTextNode(Ctx, CurrentToken.TokenBody, Parent);
651 Parent->addChild(CurrentNode);
652 break;
653 }
655 CurrentNode = createNode(Ctx, ASTNode::Variable, A, Parent);
656 Parent->addChild(CurrentNode);
657 break;
658 }
660 CurrentNode = createNode(Ctx, ASTNode::UnescapeVariable, A, Parent);
661 Parent->addChild(CurrentNode);
662 break;
663 }
665 CurrentNode = createNode(Ctx, ASTNode::Partial, A, Parent);
666 CurrentNode->setIndentation(CurrentToken.getIndentation());
667 Parent->addChild(CurrentNode);
668 break;
669 }
671 parseSection(Parent, ASTNode::Section, A);
672 break;
673 }
675 parseSection(Parent, ASTNode::InvertSection, A);
676 break;
677 }
680 break;
682 return;
683 }
684 }
685}
687 LLVM_DEBUG(dbgs() << "[To Mustache String] Kind: "
688 << jsonKindToString(Data.kind()) << ", Data: " << Data
689 << "\n");
690 switch (Data.kind()) {
692 return;
693 case json::Value::Number: {
694 auto Num = *Data.getAsNumber();
695 std::ostringstream SS;
696 SS << Num;
697 OS << SS.str();
698 return;
699 }
700 case json::Value::String: {
701 OS << *Data.getAsString();
702 return;
703 }
704
705 case json::Value::Array: {
706 auto Arr = *Data.getAsArray();
707 if (Arr.empty())
708 return;
709 [[fallthrough]];
710 }
713 llvm::json::OStream JOS(OS, 2);
714 JOS.value(Data);
715 break;
716 }
717 }
718}
719
720void ASTNode::renderRoot(const json::Value &CurrentCtx,
722 renderChild(CurrentCtx, OS);
723}
724
725void ASTNode::renderText(MustacheOutputStream &OS) { OS << Body; }
726
727void ASTNode::renderPartial(const json::Value &CurrentCtx,
729 LLVM_DEBUG(dbgs() << "[Render Partial] Accessor:" << AccessorValue[0]
730 << ", Indentation:" << Indentation << "\n");
731 auto Partial = Ctx.Partials.find(AccessorValue[0]);
732 if (Partial != Ctx.Partials.end())
733 renderPartial(CurrentCtx, OS, Partial->getValue());
734}
735
736void ASTNode::renderVariable(const json::Value &CurrentCtx,
738 auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);
739 if (Lambda != Ctx.Lambdas.end()) {
740 renderLambdas(CurrentCtx, OS, Lambda->getValue());
741 } else if (const json::Value *ContextPtr = findContext()) {
742 EscapeStringStream ES(OS, Ctx.Escapes);
743 toMustacheString(*ContextPtr, ES);
744 }
745}
746
747void ASTNode::renderUnescapeVariable(const json::Value &CurrentCtx,
749 LLVM_DEBUG(dbgs() << "[Render UnescapeVariable] Accessor:" << AccessorValue[0]
750 << "\n");
751 auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);
752 if (Lambda != Ctx.Lambdas.end()) {
753 renderLambdas(CurrentCtx, OS, Lambda->getValue());
754 } else if (const json::Value *ContextPtr = findContext()) {
756 toMustacheString(*ContextPtr, OS);
758 }
759}
760
761void ASTNode::renderSection(const json::Value &CurrentCtx,
763 auto SectionLambda = Ctx.SectionLambdas.find(AccessorValue[0]);
764 if (SectionLambda != Ctx.SectionLambdas.end()) {
765 renderSectionLambdas(CurrentCtx, OS, SectionLambda->getValue());
766 return;
767 }
768
769 const json::Value *ContextPtr = findContext();
770 if (isContextFalsey(ContextPtr))
771 return;
772
773 if (const json::Array *Arr = ContextPtr->getAsArray()) {
774 for (const json::Value &V : *Arr)
775 renderChild(V, OS);
776 return;
777 }
778 renderChild(*ContextPtr, OS);
779}
780
781void ASTNode::renderInvertSection(const json::Value &CurrentCtx,
783 bool IsLambda = Ctx.SectionLambdas.contains(AccessorValue[0]);
784 const json::Value *ContextPtr = findContext();
785 if (isContextFalsey(ContextPtr) && !IsLambda) {
786 renderChild(CurrentCtx, OS);
787 }
788}
789
791 if (Ty != Root && Ty != Text && AccessorValue.empty())
792 return;
793 // Set the parent context to the incoming context so that we
794 // can walk up the context tree correctly in findContext().
795 ParentContext = &Data;
796
797 switch (Ty) {
798 case Root:
799 renderRoot(Data, OS);
800 return;
801 case Text:
802 renderText(OS);
803 return;
804 case Partial:
805 renderPartial(Data, OS);
806 return;
807 case Variable:
808 renderVariable(Data, OS);
809 return;
810 case UnescapeVariable:
811 renderUnescapeVariable(Data, OS);
812 return;
813 case Section:
814 renderSection(Data, OS);
815 return;
816 case InvertSection:
817 renderInvertSection(Data, OS);
818 return;
819 }
820 llvm_unreachable("Invalid ASTNode type");
821}
822
823const json::Value *ASTNode::findContext() {
824 // The mustache spec allows for dot notation to access nested values
825 // a single dot refers to the current context.
826 // We attempt to find the JSON context in the current node, if it is not
827 // found, then we traverse the parent nodes to find the context until we
828 // reach the root node or the context is found.
829 if (AccessorValue.empty())
830 return nullptr;
831 if (AccessorValue[0] == ".")
832 return ParentContext;
833
834 const json::Object *CurrentContext = ParentContext->getAsObject();
835 StringRef CurrentAccessor = AccessorValue[0];
836 ASTNode *CurrentParent = Parent;
837
838 while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
839 if (CurrentParent->Ty != Root) {
840 CurrentContext = CurrentParent->ParentContext->getAsObject();
841 CurrentParent = CurrentParent->Parent;
842 continue;
843 }
844 return nullptr;
845 }
846 const json::Value *Context = nullptr;
847 for (auto [Idx, Acc] : enumerate(AccessorValue)) {
848 const json::Value *CurrentValue = CurrentContext->get(Acc);
849 if (!CurrentValue)
850 return nullptr;
851 if (Idx < AccessorValue.size() - 1) {
852 CurrentContext = CurrentValue->getAsObject();
853 if (!CurrentContext)
854 return nullptr;
855 } else {
856 Context = CurrentValue;
857 }
858 }
859 return Context;
860}
861
862void ASTNode::renderChild(const json::Value &Contexts,
864 for (ASTNode &Child : Children)
865 Child.render(Contexts, OS);
866}
867
868void ASTNode::renderPartial(const json::Value &Contexts,
869 MustacheOutputStream &OS, ASTNode *Partial) {
870 LLVM_DEBUG(dbgs() << "[Render Partial Indentation] Indentation: " << Indentation << "\n");
871 AddIndentationStringStream IS(OS, Indentation);
872 Partial->render(Contexts, IS);
873}
874
875void ASTNode::renderLambdas(const llvm::json::Value &Contexts,
877 json::Value LambdaResult = L();
878 std::string LambdaStr;
879 raw_string_ostream Output(LambdaStr);
880 toMustacheString(LambdaResult, Output);
881 Parser P(LambdaStr, Ctx);
882 AstPtr LambdaNode = P.parse();
883
884 EscapeStringStream ES(OS, Ctx.Escapes);
885 if (Ty == Variable) {
886 LambdaNode->render(Contexts, ES);
887 return;
888 }
889 LambdaNode->render(Contexts, OS);
890}
891
892void ASTNode::renderSectionLambdas(const llvm::json::Value &Contexts,
894 json::Value Return = L(RawBody.str());
895 if (isFalsey(Return))
896 return;
897 std::string LambdaStr;
898 raw_string_ostream Output(LambdaStr);
899 toMustacheString(Return, Output);
900 Parser P(LambdaStr, Ctx);
901 AstPtr LambdaNode = P.parse();
902 LambdaNode->render(Contexts, OS);
903}
904
907 Tree->render(Data, MOS);
908}
909
910void Template::registerPartial(std::string Name, std::string Partial) {
911 StringRef SavedPartial = Ctx.Saver.save(Partial);
912 Parser P(SavedPartial, Ctx);
913 AstPtr PartialTree = P.parse();
914 Ctx.Partials.insert(std::make_pair(Name, PartialTree));
915}
916
917void Template::registerLambda(std::string Name, Lambda L) {
918 Ctx.Lambdas[Name] = L;
919}
920
921void Template::registerLambda(std::string Name, SectionLambda L) {
922 Ctx.SectionLambdas[Name] = L;
923}
924
926 Ctx.Escapes = std::move(E);
927}
928
929Template::Template(StringRef TemplateStr, MustacheContext &Ctx) : Ctx(Ctx) {
930 Parser P(TemplateStr, Ctx);
931 Tree = P.parse();
932 // The default behavior is to escape html entities.
933 const EscapeMap HtmlEntities = {{'&', "&amp;"},
934 {'<', "&lt;"},
935 {'>', "&gt;"},
936 {'"', "&quot;"},
937 {'\'', "&#39;"}};
938 overrideEscapeCharacters(HtmlEntities);
939}
940
942 : Ctx(Other.Ctx), Tree(Other.Tree) {
943 Other.Tree = nullptr;
944}
945
946Template::~Template() = default;
947
948} // namespace llvm::mustache
949
950#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)
void push_back(const T &Elt)
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
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, Type Ty, ArrayRef< StringRef > Accessor, ASTNode *Parent)
Definition Mustache.cpp:185
ASTNode(MustacheContext &Ctx)
Definition Mustache.cpp:177
void setIndentation(size_t NewIndentation)
Definition Mustache.cpp:194
ASTNode(MustacheContext &Ctx, StringRef Body, ASTNode *Parent)
Definition Mustache.cpp:180
void setRawBody(StringRef NewBody)
Definition Mustache.cpp:192
void render(const llvm::json::Value &Data, MustacheOutputStream &OS)
Definition Mustache.cpp:790
void addChild(AstPtr Child)
Definition Mustache.cpp:190
AddIndentationStringStream(raw_ostream &WrappedStream, size_t Indentation)
Definition Mustache.cpp:563
uint64_t current_pos() const override
Return the current position within the stream, not counting the bytes currently in the buffer.
Definition Mustache.cpp:593
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:574
uint64_t current_pos() const override
Return the current position within the stream, not counting the bytes currently in the buffer.
Definition Mustache.cpp:552
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:529
EscapeStringStream(llvm::raw_ostream &WrappedStream, EscapeMap &Escape)
Definition Mustache.cpp:521
~MustacheOutputStream() override=default
Parser(StringRef TemplateStr, MustacheContext &Ctx)
Definition Mustache.cpp:604
LLVM_ABI void registerPartial(std::string Name, std::string Partial)
Definition Mustache.cpp:910
LLVM_ABI void registerLambda(std::string Name, Lambda Lambda)
Definition Mustache.cpp:917
LLVM_ABI Template(StringRef TemplateStr, MustacheContext &Ctx)
Definition Mustache.cpp:929
LLVM_ABI void render(const llvm::json::Value &Data, llvm::raw_ostream &OS)
Definition Mustache.cpp:905
LLVM_ABI void overrideEscapeCharacters(DenseMap< char, std::string > Escapes)
Definition Mustache.cpp:925
Type getType() const
Definition Mustache.cpp:127
size_t getIndentation() const
Definition Mustache.cpp:131
Token(StringRef Str)
Definition Mustache.cpp:109
Token(StringRef RawBody, StringRef TokenBody, char Identifier, MustacheContext &Ctx)
Definition Mustache.cpp:113
void setIndentation(size_t NewIndentation)
Definition Mustache.cpp:129
static Type getTokenType(char Identifier)
Definition Mustache.cpp:133
ArrayRef< StringRef > AccessorValue
Definition Mustache.cpp:159
ArrayRef< StringRef > getAccessor() const
Definition Mustache.cpp:125
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:276
static AstPtr createRootNode(MustacheContext &Ctx)
Definition Mustache.cpp:234
static const char * tagKindToString(Tag::Kind K)
Definition Mustache.cpp:341
void stripTokenBefore(SmallVectorImpl< Token > &Tokens, size_t Idx, Token &CurrentToken, Token::Type CurrentType)
Definition Mustache.cpp:318
static std::optional< std::pair< StringRef, StringRef > > processTag(const Tag &T, SmallVectorImpl< Token > &Tokens, MustacheContext &Ctx)
Definition Mustache.cpp:414
iplist< ASTNode > ASTNodeList
Definition Mustache.h:91
static AstPtr createTextNode(MustacheContext &Ctx, StringRef Body, ASTNode *Parent)
Definition Mustache.cpp:243
static const char * jsonKindToString(json::Value::Kind K)
Definition Mustache.cpp:353
std::function< llvm::json::Value(std::string)> SectionLambda
Definition Mustache.h:86
static AstPtr createNode(MustacheContext &Ctx, ASTNode::Type T, ArrayRef< StringRef > A, ASTNode *Parent)
Definition Mustache.cpp:238
static Tag findNextTag(StringRef Template, size_t StartPos, StringRef Open, StringRef Close)
Definition Mustache.cpp:371
static void stripTokenAhead(SmallVectorImpl< Token > &Tokens, size_t Idx)
Definition Mustache.cpp:302
std::function< llvm::json::Value()> Lambda
Definition Mustache.h:85
static bool hasTextBehind(size_t Idx, const ArrayRef< Token > &Tokens)
Definition Mustache.cpp:260
ASTNode * AstPtr
Definition Mustache.h:89
static bool requiresCleanUp(Token::Type T)
Definition Mustache.cpp:289
static SmallVector< Token > tokenize(StringRef Template, MustacheContext &Ctx)
Definition Mustache.cpp:443
DenseMap< char, std::string > EscapeMap
Definition Mustache.h:90
static void toMustacheString(const json::Value &Data, raw_ostream &OS)
Definition Mustache.cpp:686
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:2474
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
ArrayRef(const T &OneElt) -> ArrayRef< T >