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