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