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//===----------------------------------------------------------------------===//
8#include "llvm/Support/Mustache.h"
9#include "llvm/ADT/SmallVector.h"
10#include "llvm/Support/Debug.h"
11#include "llvm/Support/raw_ostream.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: *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(C: '.', From: 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(Start: CurrentPos, End: DelimiterPos);
48
49 // Manually trim the part without creating a new string object.
50 size_t Start = Part.find_first_not_of(Chars: " \t\r\n");
51 if (Start != StringRef::npos) {
52 size_t End = Part.find_last_not_of(Chars: " \t\r\n");
53 Tokens.push_back(Elt: Part.slice(Start, End: 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.
67 SmallVector<StringRef> Tokens;
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(Elt: ".");
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>(Num: Tokens.size());
77 // Copy the StringRefs from the stack vector to the arena.
78 llvm::copy(Range&: Tokens, Out: 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
86class MustacheOutputStream : public raw_ostream {
87public:
88 MustacheOutputStream() = default;
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
100class RawMustacheOutputStream : public MustacheOutputStream {
101public:
102 RawMustacheOutputStream(raw_ostream &OS) : OS(OS) { SetUnbuffered(); }
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:
115 enum class Type {
116 Text,
117 Variable,
118 Partial,
119 SectionOpen,
120 SectionClose,
121 InvertSectionOpen,
122 UnescapeVariable,
123 Comment,
124 SetDelimiter,
125 };
126
127 Token(StringRef Str)
128 : TokenType(Type::Text), RawBody(Str), TokenBody(RawBody),
129 AccessorValue({}), Indentation(0) {};
130
131 Token(StringRef RawBody, StringRef TokenBody, char Identifier,
132 MustacheContext &Ctx)
133 : RawBody(RawBody), TokenBody(TokenBody), Indentation(0) {
134 TokenType = getTokenType(Identifier);
135 if (TokenType == Type::Comment)
136 return;
137 StringRef AccessorStr(this->TokenBody);
138 if (TokenType != Type::Variable)
139 AccessorStr = AccessorStr.substr(Start: 1);
140 AccessorValue = splitMustacheString(Str: StringRef(AccessorStr).trim(), Ctx);
141 }
142
143 ArrayRef<StringRef> getAccessor() const { return AccessorValue; }
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 '^':
158 return Type::InvertSectionOpen;
159 case '!':
160 return Type::Comment;
161 case '>':
162 return Type::Partial;
163 case '&':
164 return Type::UnescapeVariable;
165 case '=':
166 return Type::SetDelimiter;
167 default:
168 return Type::Variable;
169 }
170 }
171
172 Type TokenType;
173 // RawBody is the original string that was tokenized.
174 StringRef RawBody;
175 // TokenBody is the original string with the identifier removed.
176 StringRef TokenBody;
177 ArrayRef<StringRef> AccessorValue;
178 size_t Indentation;
179};
180
181using EscapeMap = DenseMap<char, std::string>;
182
183class ASTNode : public ilist_node<ASTNode> {
184public:
185 enum Type {
186 Root,
187 Text,
188 Partial,
189 Variable,
190 UnescapeVariable,
191 Section,
192 InvertSection,
193 };
194
195 ASTNode(MustacheContext &Ctx)
196 : Ctx(Ctx), Ty(Type::Root), Parent(nullptr), ParentContext(nullptr) {}
197
198 ASTNode(MustacheContext &Ctx, StringRef Body, ASTNode *Parent)
199 : Ctx(Ctx), Ty(Type::Text), Body(Body), Parent(Parent),
200 ParentContext(nullptr) {}
201
202 // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes
203 ASTNode(MustacheContext &Ctx, Type Ty, ArrayRef<StringRef> Accessor,
204 ASTNode *Parent)
205 : Ctx(Ctx), Ty(Ty), Parent(Parent), AccessorValue(Accessor),
206 ParentContext(nullptr) {}
207
208 void addChild(AstPtr Child) { Children.push_back(val: Child); };
209
210 void setRawBody(StringRef NewBody) { RawBody = NewBody; };
211
212 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
213
214 void render(const llvm::json::Value &Data, MustacheOutputStream &OS);
215
216private:
217 void renderLambdas(const llvm::json::Value &Contexts,
218 MustacheOutputStream &OS, Lambda &L);
219
220 void renderSectionLambdas(const llvm::json::Value &Contexts,
221 MustacheOutputStream &OS, SectionLambda &L);
222
223 void renderPartial(const llvm::json::Value &Contexts,
224 MustacheOutputStream &OS, ASTNode *Partial);
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,
235 MustacheOutputStream &OS);
236 void renderSection(const json::Value &CurrentCtx, MustacheOutputStream &OS);
237 void renderInvertSection(const json::Value &CurrentCtx,
238 MustacheOutputStream &OS);
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
252static AstPtr createRootNode(MustacheContext &Ctx) {
253 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx);
254}
255
256static AstPtr createNode(MustacheContext &Ctx, ASTNode::Type T,
257 ArrayRef<StringRef> A, ASTNode *Parent) {
258 return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, T, A, Parent);
259}
260
261static AstPtr createTextNode(MustacheContext &Ctx, StringRef Body,
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(Chars: " \r\t\v");
288 return !TokenBody.ends_with(Suffix: "\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(Chars: " ");
304 return !TokenBody.starts_with(Prefix: "\r\n") && !TokenBody.starts_with(Prefix: "\n");
305}
306
307static bool requiresCleanUp(Token::Type T) {
308 // We must clean up all the tokens that could contain child nodes.
309 return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen ||
310 T == Token::Type::SectionClose || T == Token::Type::Comment ||
311 T == Token::Type::Partial || T == Token::Type::SetDelimiter;
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(Prefix: "\r\n"))
325 NextToken.TokenBody = NextTokenBody.substr(Start: 2);
326 else if (NextTokenBody.starts_with(Prefix: "\n"))
327 NextToken.TokenBody = NextTokenBody.substr(Start: 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"
336void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx,
337 Token &CurrentToken, Token::Type CurrentType) {
338 Token &PrevToken = Tokens[Idx - 1];
339 StringRef PrevTokenBody = PrevToken.TokenBody;
340 StringRef Unindented = PrevTokenBody.rtrim(Chars: " \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 {
348 None,
349 Normal, // {{...}}
350 Triple, // {{{...}}}
351 };
352
353 Kind TagKind = Kind::None;
354 StringRef Content; // The content between the delimiters.
355 StringRef FullMatch; // The entire tag, including delimiters.
356 size_t StartPosition = StringRef::npos;
357};
358
359[[maybe_unused]] static const char *tagKindToString(Tag::Kind K) {
360 switch (K) {
361 case Tag::Kind::None:
362 return "None";
363 case Tag::Kind::Normal:
364 return "Normal";
365 case Tag::Kind::Triple:
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) {
373 case json::Value::Kind::Null:
374 return "JSON_KIND_NULL";
375 case json::Value::Kind::Boolean:
376 return "JSON_KIND_BOOLEAN";
377 case json::Value::Kind::Number:
378 return "JSON_KIND_NUMBER";
379 case json::Value::Kind::String:
380 return "JSON_KIND_STRING";
381 case json::Value::Kind::Array:
382 return "JSON_KIND_ARRAY";
383 case json::Value::Kind::Object:
384 return "JSON_KIND_OBJECT";
385 }
386 llvm_unreachable("Unknown json::Value::Kind");
387}
388
389// Simple tokenizer that splits the template into tokens.
390static SmallVector<Token> tokenize(StringRef Template, MustacheContext &Ctx) {
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(Start: Cursor);
403 StringRef TagOpen, TagClose;
404 Tag::Kind Kind;
405
406 // Determine which tag we've encountered.
407 if (TemplateSuffix.starts_with(Prefix: TripleOpen)) {
408 Kind = Tag::Kind::Triple;
409 TagOpen = TripleOpen;
410 TagClose = TripleClose;
411 } else if (TemplateSuffix.starts_with(Prefix: 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(Args: Template.slice(Start: TextStart, End: Cursor));
424
425 // Find the closing tag.
426 size_t EndPos = Template.find(Str: TagClose, From: Cursor + TagOpen.size());
427 if (EndPos == StringRef::npos) {
428 // No closing tag, the rest is text.
429 Tokens.emplace_back(Args: Template.substr(Start: 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(Start: ContentStart, N: EndPos - ContentStart);
437 StringRef FullMatch =
438 Template.substr(Start: Cursor, N: (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(Args&: FullMatch, Args: Ctx.Saver.save(S: "&" + Content), Args: '&', Args&: Ctx);
445 } else { // Normal Tag
446 StringRef Interpolated = Content;
447 if (!Interpolated.trim().starts_with(Prefix: "=")) {
448 char Front = Interpolated.empty() ? ' ' : Interpolated.trim().front();
449 Tokens.emplace_back(Args&: FullMatch, Args&: Interpolated, Args&: Front, Args&: Ctx);
450 } else { // Set Delimiter
451 Tokens.emplace_back(Args&: FullMatch, Args&: Interpolated, Args: '=', Args&: Ctx);
452 StringRef DelimSpec = Interpolated.trim();
453 DelimSpec = DelimSpec.drop_front(N: 1);
454 DelimSpec = DelimSpec.take_until(F: [](char C) { return C == '='; });
455 DelimSpec = DelimSpec.trim();
456
457 auto [NewOpen, NewClose] = DelimSpec.split(Separator: ' ');
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(Args: Template.substr(Start: 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(T: 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.
495class EscapeStringStream : public MustacheOutputStream {
496public:
497 explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
498 EscapeMap &Escape)
499 : Escape(Escape), EscapeChars(Escape.keys().begin(), Escape.keys().end()),
500 WrappedStream(WrappedStream) {
501 SetUnbuffered();
502 }
503
504protected:
505 void write_impl(const char *Ptr, size_t Size) override {
506 StringRef Data(Ptr, Size);
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(Chars: EscapeChars.str(), From: 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, N: 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.
537class AddIndentationStringStream : public MustacheOutputStream {
538public:
539 explicit AddIndentationStringStream(raw_ostream &WrappedStream,
540 size_t Indentation)
541 : Indentation(Indentation), WrappedStream(WrappedStream),
542 NeedsIndent(true), IsSuspended(false) {
543 SetUnbuffered();
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 {
551 llvm::StringRef Data(Ptr, Size);
552 SmallString<0> Indent;
553 Indent.resize(N: Indentation, NV: ' ');
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:
580 Parser(StringRef TemplateStr, MustacheContext &Ctx)
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, T: Ty, A, Parent);
598 size_t Start = CurrentPtr;
599 parseMustache(Parent: 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(N: RawBodySize);
608 for (std::size_t I = Start; I < End; ++I)
609 RawBody += Tokens[I].RawBody;
610
611 CurrentNode->setRawBody(Ctx.Saver.save(S: StringRef(RawBody)));
612 Parent->addChild(Child: CurrentNode);
613}
614
615AstPtr Parser::parse() {
616 Tokens = tokenize(Template: TemplateStr, Ctx);
617 CurrentPtr = 0;
618 AstPtr RootNode = createRootNode(Ctx);
619 parseMustache(Parent: 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, Body: CurrentToken.TokenBody, Parent);
634 Parent->addChild(Child: CurrentNode);
635 break;
636 }
637 case Token::Type::Variable: {
638 CurrentNode = createNode(Ctx, T: ASTNode::Variable, A, Parent);
639 Parent->addChild(Child: CurrentNode);
640 break;
641 }
642 case Token::Type::UnescapeVariable: {
643 CurrentNode = createNode(Ctx, T: ASTNode::UnescapeVariable, A, Parent);
644 Parent->addChild(Child: CurrentNode);
645 break;
646 }
647 case Token::Type::Partial: {
648 CurrentNode = createNode(Ctx, T: ASTNode::Partial, A, Parent);
649 CurrentNode->setIndentation(CurrentToken.getIndentation());
650 Parent->addChild(Child: CurrentNode);
651 break;
652 }
653 case Token::Type::SectionOpen: {
654 parseSection(Parent, Ty: ASTNode::Section, A);
655 break;
656 }
657 case Token::Type::InvertSectionOpen: {
658 parseSection(Parent, Ty: ASTNode::InvertSection, A);
659 break;
660 }
661 case Token::Type::Comment:
662 case Token::Type::SetDelimiter:
663 break;
664 case Token::Type::SectionClose:
665 return;
666 }
667 }
668}
669static void toMustacheString(const json::Value &Data, raw_ostream &OS) {
670 LLVM_DEBUG(dbgs() << "[To Mustache String] Kind: "
671 << jsonKindToString(Data.kind()) << ", Data: " << Data
672 << "\n");
673 switch (Data.kind()) {
674 case json::Value::Null:
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 }
694 case json::Value::Object:
695 case json::Value::Boolean: {
696 llvm::json::OStream JOS(OS, 2);
697 JOS.value(V: Data);
698 break;
699 }
700 }
701}
702
703void ASTNode::renderRoot(const json::Value &CurrentCtx,
704 MustacheOutputStream &OS) {
705 renderChild(Context: CurrentCtx, OS);
706}
707
708void ASTNode::renderText(MustacheOutputStream &OS) { OS << Body; }
709
710void ASTNode::renderPartial(const json::Value &CurrentCtx,
711 MustacheOutputStream &OS) {
712 LLVM_DEBUG(dbgs() << "[Render Partial] Accessor:" << AccessorValue[0]
713 << ", Indentation:" << Indentation << "\n");
714 auto Partial = Ctx.Partials.find(Key: AccessorValue[0]);
715 if (Partial != Ctx.Partials.end())
716 renderPartial(Contexts: CurrentCtx, OS, Partial: Partial->getValue());
717}
718
719void ASTNode::renderVariable(const json::Value &CurrentCtx,
720 MustacheOutputStream &OS) {
721 auto Lambda = Ctx.Lambdas.find(Key: AccessorValue[0]);
722 if (Lambda != Ctx.Lambdas.end()) {
723 renderLambdas(Contexts: CurrentCtx, OS, L&: Lambda->getValue());
724 } else if (const json::Value *ContextPtr = findContext()) {
725 EscapeStringStream ES(OS, Ctx.Escapes);
726 toMustacheString(Data: *ContextPtr, OS&: ES);
727 }
728}
729
730void ASTNode::renderUnescapeVariable(const json::Value &CurrentCtx,
731 MustacheOutputStream &OS) {
732 LLVM_DEBUG(dbgs() << "[Render UnescapeVariable] Accessor:" << AccessorValue[0]
733 << "\n");
734 auto Lambda = Ctx.Lambdas.find(Key: AccessorValue[0]);
735 if (Lambda != Ctx.Lambdas.end()) {
736 renderLambdas(Contexts: CurrentCtx, OS, L&: Lambda->getValue());
737 } else if (const json::Value *ContextPtr = findContext()) {
738 OS.suspendIndentation();
739 toMustacheString(Data: *ContextPtr, OS);
740 OS.resumeIndentation();
741 }
742}
743
744void ASTNode::renderSection(const json::Value &CurrentCtx,
745 MustacheOutputStream &OS) {
746 auto SectionLambda = Ctx.SectionLambdas.find(Key: AccessorValue[0]);
747 if (SectionLambda != Ctx.SectionLambdas.end()) {
748 renderSectionLambdas(Contexts: CurrentCtx, OS, L&: SectionLambda->getValue());
749 return;
750 }
751
752 const json::Value *ContextPtr = findContext();
753 if (isContextFalsey(V: ContextPtr))
754 return;
755
756 if (const json::Array *Arr = ContextPtr->getAsArray()) {
757 for (const json::Value &V : *Arr)
758 renderChild(Context: V, OS);
759 return;
760 }
761 renderChild(Context: *ContextPtr, OS);
762}
763
764void ASTNode::renderInvertSection(const json::Value &CurrentCtx,
765 MustacheOutputStream &OS) {
766 bool IsLambda = Ctx.SectionLambdas.contains(Key: AccessorValue[0]);
767 const json::Value *ContextPtr = findContext();
768 if (isContextFalsey(V: ContextPtr) && !IsLambda) {
769 renderChild(Context: CurrentCtx, OS);
770 }
771}
772
773void ASTNode::render(const llvm::json::Value &Data, MustacheOutputStream &OS) {
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(CurrentCtx: Data, OS);
783 return;
784 case Text:
785 renderText(OS);
786 return;
787 case Partial:
788 renderPartial(CurrentCtx: Data, OS);
789 return;
790 case Variable:
791 renderVariable(CurrentCtx: Data, OS);
792 return;
793 case UnescapeVariable:
794 renderUnescapeVariable(CurrentCtx: Data, OS);
795 return;
796 case Section:
797 renderSection(CurrentCtx: Data, OS);
798 return;
799 case InvertSection:
800 renderInvertSection(CurrentCtx: 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(K: 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(First: AccessorValue)) {
831 const json::Value *CurrentValue = CurrentContext->get(K: 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,
846 MustacheOutputStream &OS) {
847 for (ASTNode &Child : Children)
848 Child.render(Data: 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(Data: Contexts, OS&: IS);
856}
857
858void ASTNode::renderLambdas(const llvm::json::Value &Contexts,
859 MustacheOutputStream &OS, Lambda &L) {
860 json::Value LambdaResult = L();
861 std::string LambdaStr;
862 raw_string_ostream Output(LambdaStr);
863 toMustacheString(Data: LambdaResult, OS&: 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(Data: Contexts, OS&: ES);
870 return;
871 }
872 LambdaNode->render(Data: Contexts, OS);
873}
874
875void ASTNode::renderSectionLambdas(const llvm::json::Value &Contexts,
876 MustacheOutputStream &OS, SectionLambda &L) {
877 json::Value Return = L(RawBody.str());
878 if (isFalsey(V: Return))
879 return;
880 std::string LambdaStr;
881 raw_string_ostream Output(LambdaStr);
882 toMustacheString(Data: Return, OS&: Output);
883 Parser P(LambdaStr, Ctx);
884 AstPtr LambdaNode = P.parse();
885 LambdaNode->render(Data: Contexts, OS);
886}
887
888void Template::render(const llvm::json::Value &Data, llvm::raw_ostream &OS) {
889 RawMustacheOutputStream MOS(OS);
890 Tree->render(Data, OS&: MOS);
891}
892
893void Template::registerPartial(std::string Name, std::string Partial) {
894 StringRef SavedPartial = Ctx.Saver.save(S: Partial);
895 Parser P(SavedPartial, Ctx);
896 AstPtr PartialTree = P.parse();
897 Ctx.Partials.insert(KV: std::make_pair(x&: Name, y&: PartialTree));
898}
899
900void Template::registerLambda(std::string Name, Lambda L) {
901 Ctx.Lambdas[Name] = std::move(L);
902}
903
904void Template::registerLambda(std::string Name, SectionLambda L) {
905 Ctx.SectionLambdas[Name] = std::move(L);
906}
907
908void Template::overrideEscapeCharacters(EscapeMap E) {
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(E: HtmlEntities);
922}
923
924Template::Template(Template &&Other) noexcept
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
934