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