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