blob: 2560619538f9ac0d97d2693a894362994c91d9f4 [file] [log] [blame]
Paul Kirthece59a82025-03-24 17:23:25 -07001//===-- 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/Error.h"
11#include "llvm/Support/raw_ostream.h"
12#include <sstream>
13
14using namespace llvm;
15using namespace llvm::mustache;
16
17namespace {
18
19using Accessor = SmallVector<std::string>;
20
21static bool isFalsey(const json::Value &V) {
22 return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||
23 (V.getAsArray() && V.getAsArray()->empty());
24}
25
26static Accessor splitMustacheString(StringRef Str) {
27 // We split the mustache string into an accessor.
28 // For example:
29 // "a.b.c" would be split into {"a", "b", "c"}
30 // We make an exception for a single dot which
31 // refers to the current context.
32 Accessor Tokens;
33 if (Str == ".") {
34 Tokens.emplace_back(Str);
35 return Tokens;
36 }
37 while (!Str.empty()) {
38 StringRef Part;
39 std::tie(Part, Str) = Str.split(".");
40 Tokens.emplace_back(Part.trim());
41 }
42 return Tokens;
43}
44} // namespace
45
46namespace llvm::mustache {
47
48class Token {
49public:
50 enum class Type {
51 Text,
52 Variable,
53 Partial,
54 SectionOpen,
55 SectionClose,
56 InvertSectionOpen,
57 UnescapeVariable,
58 Comment,
59 };
60
61 Token(std::string Str)
62 : TokenType(Type::Text), RawBody(std::move(Str)), TokenBody(RawBody),
63 AccessorValue({}), Indentation(0) {};
64
65 Token(std::string RawBody, std::string TokenBody, char Identifier)
66 : RawBody(std::move(RawBody)), TokenBody(std::move(TokenBody)),
67 Indentation(0) {
68 TokenType = getTokenType(Identifier);
69 if (TokenType == Type::Comment)
70 return;
71 StringRef AccessorStr(this->TokenBody);
72 if (TokenType != Type::Variable)
73 AccessorStr = AccessorStr.substr(1);
74 AccessorValue = splitMustacheString(StringRef(AccessorStr).trim());
75 }
76
77 Accessor getAccessor() const { return AccessorValue; }
78
79 Type getType() const { return TokenType; }
80
81 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }
82
83 size_t getIndentation() const { return Indentation; }
84
85 static Type getTokenType(char Identifier) {
86 switch (Identifier) {
87 case '#':
88 return Type::SectionOpen;
89 case '/':
90 return Type::SectionClose;
91 case '^':
92 return Type::InvertSectionOpen;
93 case '!':
94 return Type::Comment;
95 case '>':
96 return Type::Partial;
97 case '&':
98 return Type::UnescapeVariable;
99 default:
100 return Type::Variable;
101 }
102 }
103
104 Type TokenType;
105 // RawBody is the original string that was tokenized.
106 std::string RawBody;
107 // TokenBody is the original string with the identifier removed.
108 std::string TokenBody;
109 Accessor AccessorValue;
110 size_t Indentation;
111};
112
113class ASTNode {
114public:
115 enum Type {
116 Root,
117 Text,
118 Partial,
119 Variable,
120 UnescapeVariable,
121 Section,
122 InvertSection,
123 };
124
125 ASTNode(llvm::StringMap<AstPtr> &Partials, llvm::StringMap<Lambda> &Lambdas,
126 llvm::StringMap<SectionLambda> &SectionLambdas,
127 llvm::DenseMap<char, std::string> &Escapes)
128 : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas),
129 Escapes(Escapes), Ty(Type::Root), Parent(nullptr),
130 ParentContext(nullptr) {}
131
132 ASTNode(std::string Body, ASTNode *Parent, llvm::StringMap<AstPtr> &Partials,
133 llvm::StringMap<Lambda> &Lambdas,
134 llvm::StringMap<SectionLambda> &SectionLambdas,
135 llvm::DenseMap<char, std::string> &Escapes)
136 : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas),
137 Escapes(Escapes), Ty(Type::Text), Body(std::move(Body)), Parent(Parent),
138 ParentContext(nullptr) {}
139
140 // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes
141 ASTNode(Type Ty, Accessor Accessor, ASTNode *Parent,
142 llvm::StringMap<AstPtr> &Partials, llvm::StringMap<Lambda> &Lambdas,
143 llvm::StringMap<SectionLambda> &SectionLambdas,
144 llvm::DenseMap<char, std::string> &Escapes)
145 : Partials(Partials), Lambdas(Lambdas), SectionLambdas(SectionLambdas),
146 Escapes(Escapes), Ty(Ty), Parent(Parent),
147 AccessorValue(std::move(Accessor)), ParentContext(nullptr) {}
148
149 void addChild(AstPtr Child) { Children.emplace_back(std::move(Child)); };
150
151 void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); };
152
153 void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
154
155 void render(const llvm::json::Value &Data, llvm::raw_ostream &OS);
156
157private:
158 void renderLambdas(const llvm::json::Value &Contexts, llvm::raw_ostream &OS,
159 Lambda &L);
160
161 void renderSectionLambdas(const llvm::json::Value &Contexts,
162 llvm::raw_ostream &OS, SectionLambda &L);
163
164 void renderPartial(const llvm::json::Value &Contexts, llvm::raw_ostream &OS,
165 ASTNode *Partial);
166
167 void renderChild(const llvm::json::Value &Context, llvm::raw_ostream &OS);
168
169 const llvm::json::Value *findContext();
170
171 StringMap<AstPtr> &Partials;
172 StringMap<Lambda> &Lambdas;
173 StringMap<SectionLambda> &SectionLambdas;
174 DenseMap<char, std::string> &Escapes;
175 Type Ty;
176 size_t Indentation = 0;
177 std::string RawBody;
178 std::string Body;
179 ASTNode *Parent;
180 // TODO: switch implementation to SmallVector<T>
181 std::vector<AstPtr> Children;
182 const Accessor AccessorValue;
183 const llvm::json::Value *ParentContext;
184};
185
186// A wrapper for arena allocator for ASTNodes
187AstPtr createRootNode(llvm::StringMap<AstPtr> &Partials,
188 llvm::StringMap<Lambda> &Lambdas,
189 llvm::StringMap<SectionLambda> &SectionLambdas,
190 llvm::DenseMap<char, std::string> &Escapes) {
191 return std::make_unique<ASTNode>(Partials, Lambdas, SectionLambdas, Escapes);
192}
193
194AstPtr createNode(ASTNode::Type T, Accessor A, ASTNode *Parent,
195 llvm::StringMap<AstPtr> &Partials,
196 llvm::StringMap<Lambda> &Lambdas,
197 llvm::StringMap<SectionLambda> &SectionLambdas,
198 llvm::DenseMap<char, std::string> &Escapes) {
199 return std::make_unique<ASTNode>(T, std::move(A), Parent, Partials, Lambdas,
200 SectionLambdas, Escapes);
201}
202
203AstPtr createTextNode(std::string Body, ASTNode *Parent,
204 llvm::StringMap<AstPtr> &Partials,
205 llvm::StringMap<Lambda> &Lambdas,
206 llvm::StringMap<SectionLambda> &SectionLambdas,
207 llvm::DenseMap<char, std::string> &Escapes) {
208 return std::make_unique<ASTNode>(std::move(Body), Parent, Partials, Lambdas,
209 SectionLambdas, Escapes);
210}
211
212// Function to check if there is meaningful text behind.
213// We determine if a token has meaningful text behind
214// if the right of previous token contains anything that is
215// not a newline.
216// For example:
217// "Stuff {{#Section}}" (returns true)
218// vs
219// "{{#Section}} \n" (returns false)
220// We make an exception for when previous token is empty
221// and the current token is the second token.
222// For example:
223// "{{#Section}}"
224bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) {
225 if (Idx == 0)
226 return true;
227
228 size_t PrevIdx = Idx - 1;
229 if (Tokens[PrevIdx].getType() != Token::Type::Text)
230 return true;
231
232 const Token &PrevToken = Tokens[PrevIdx];
233 StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v");
234 return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1);
235}
236
237// Function to check if there's no meaningful text ahead.
238// We determine if a token has text ahead if the left of previous
239// token does not start with a newline.
240bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) {
241 if (Idx >= Tokens.size() - 1)
242 return true;
243
244 size_t NextIdx = Idx + 1;
245 if (Tokens[NextIdx].getType() != Token::Type::Text)
246 return true;
247
248 const Token &NextToken = Tokens[NextIdx];
249 StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" ");
250 return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n");
251}
252
253bool requiresCleanUp(Token::Type T) {
254 // We must clean up all the tokens that could contain child nodes.
255 return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen ||
256 T == Token::Type::SectionClose || T == Token::Type::Comment ||
257 T == Token::Type::Partial;
258}
259
260// Adjust next token body if there is no text ahead.
261// For example:
262// The template string
263// "{{! Comment }} \nLine 2"
264// would be considered as no text ahead and should be rendered as
265// " Line 2"
266void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {
267 Token &NextToken = Tokens[Idx + 1];
268 StringRef NextTokenBody = NextToken.TokenBody;
269 // Cut off the leading newline which could be \n or \r\n.
270 if (NextTokenBody.starts_with("\r\n"))
271 NextToken.TokenBody = NextTokenBody.substr(2).str();
272 else if (NextTokenBody.starts_with("\n"))
273 NextToken.TokenBody = NextTokenBody.substr(1).str();
274}
275
276// Adjust previous token body if there no text behind.
277// For example:
278// The template string
279// " \t{{#section}}A{{/section}}"
280// would be considered as having no text ahead and would be render as
281// "A"
282// The exception for this is partial tag which requires us to
283// keep track of the indentation once it's rendered.
284void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx,
285 Token &CurrentToken, Token::Type CurrentType) {
286 Token &PrevToken = Tokens[Idx - 1];
287 StringRef PrevTokenBody = PrevToken.TokenBody;
288 StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v");
289 size_t Indentation = PrevTokenBody.size() - Unindented.size();
290 if (CurrentType != Token::Type::Partial)
291 PrevToken.TokenBody = Unindented.str();
292 CurrentToken.setIndentation(Indentation);
293}
294
295// Simple tokenizer that splits the template into tokens.
296// The mustache spec allows {{{ }}} to unescape variables,
297// but we don't support that here. An unescape variable
298// is represented only by {{& variable}}.
299SmallVector<Token> tokenize(StringRef Template) {
300 SmallVector<Token> Tokens;
301 StringLiteral Open("{{");
302 StringLiteral Close("}}");
303 size_t Start = 0;
304 size_t DelimiterStart = Template.find(Open);
305 if (DelimiterStart == StringRef::npos) {
306 Tokens.emplace_back(Template.str());
307 return Tokens;
308 }
309 while (DelimiterStart != StringRef::npos) {
310 if (DelimiterStart != Start)
311 Tokens.emplace_back(Template.substr(Start, DelimiterStart - Start).str());
312 size_t DelimiterEnd = Template.find(Close, DelimiterStart);
313 if (DelimiterEnd == StringRef::npos)
314 break;
315
316 // Extract the Interpolated variable without delimiters.
317 size_t InterpolatedStart = DelimiterStart + Open.size();
318 size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size();
319 std::string Interpolated =
320 Template.substr(InterpolatedStart, InterpolatedEnd).str();
321 std::string RawBody = Open.str() + Interpolated + Close.str();
322 Tokens.emplace_back(RawBody, Interpolated, Interpolated[0]);
323 Start = DelimiterEnd + Close.size();
324 DelimiterStart = Template.find(Open, Start);
325 }
326
327 if (Start < Template.size())
328 Tokens.emplace_back(Template.substr(Start).str());
329
330 // Fix up white spaces for:
331 // - open sections
332 // - inverted sections
333 // - close sections
334 // - comments
335 //
336 // This loop attempts to find standalone tokens and tries to trim out
337 // the surrounding whitespace.
338 // For example:
339 // if you have the template string
340 // {{#section}} \n Example \n{{/section}}
341 // The output should would be
342 // For example:
343 // \n Example \n
344 size_t LastIdx = Tokens.size() - 1;
345 for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {
346 Token &CurrentToken = Tokens[Idx];
347 Token::Type CurrentType = CurrentToken.getType();
348 // Check if token type requires cleanup.
349 bool RequiresCleanUp = requiresCleanUp(CurrentType);
350
351 if (!RequiresCleanUp)
352 continue;
353
354 // We adjust the token body if there's no text behind or ahead.
355 // A token is considered to have no text ahead if the right of the previous
356 // token is a newline followed by spaces.
357 // A token is considered to have no text behind if the left of the next
358 // token is spaces followed by a newline.
359 // eg.
360 // "Line 1\n {{#section}} \n Line 2 \n {{/section}} \n Line 3"
361 bool HasTextBehind = hasTextBehind(Idx, Tokens);
362 bool HasTextAhead = hasTextAhead(Idx, Tokens);
363
364 if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0))
365 stripTokenAhead(Tokens, Idx);
366
367 if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx))
368 stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);
369 }
370 return Tokens;
371}
372
373// Custom stream to escape strings.
374class EscapeStringStream : public raw_ostream {
375public:
376 explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
377 DenseMap<char, std::string> &Escape)
378 : Escape(Escape), WrappedStream(WrappedStream) {
379 SetUnbuffered();
380 }
381
382protected:
383 void write_impl(const char *Ptr, size_t Size) override {
384 llvm::StringRef Data(Ptr, Size);
385 for (char C : Data) {
386 auto It = Escape.find(C);
387 if (It != Escape.end())
388 WrappedStream << It->getSecond();
389 else
390 WrappedStream << C;
391 }
392 }
393
394 uint64_t current_pos() const override { return WrappedStream.tell(); }
395
396private:
397 DenseMap<char, std::string> &Escape;
398 llvm::raw_ostream &WrappedStream;
399};
400
401// Custom stream to add indentation used to for rendering partials.
402class AddIndentationStringStream : public raw_ostream {
403public:
404 explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream,
405 size_t Indentation)
406 : Indentation(Indentation), WrappedStream(WrappedStream) {
407 SetUnbuffered();
408 }
409
410protected:
411 void write_impl(const char *Ptr, size_t Size) override {
412 llvm::StringRef Data(Ptr, Size);
413 SmallString<0> Indent;
414 Indent.resize(Indentation, ' ');
415 for (char C : Data) {
416 WrappedStream << C;
417 if (C == '\n')
418 WrappedStream << Indent;
419 }
420 }
421
422 uint64_t current_pos() const override { return WrappedStream.tell(); }
423
424private:
425 size_t Indentation;
426 llvm::raw_ostream &WrappedStream;
427};
428
429class Parser {
430public:
431 Parser(StringRef TemplateStr) : TemplateStr(TemplateStr) {}
432
433 AstPtr parse(llvm::StringMap<AstPtr> &Partials,
434 llvm::StringMap<Lambda> &Lambdas,
435 llvm::StringMap<SectionLambda> &SectionLambdas,
436 llvm::DenseMap<char, std::string> &Escapes);
437
438private:
439 void parseMustache(ASTNode *Parent, llvm::StringMap<AstPtr> &Partials,
440 llvm::StringMap<Lambda> &Lambdas,
441 llvm::StringMap<SectionLambda> &SectionLambdas,
442 llvm::DenseMap<char, std::string> &Escapes);
443
444 SmallVector<Token> Tokens;
445 size_t CurrentPtr;
446 StringRef TemplateStr;
447};
448
449AstPtr Parser::parse(llvm::StringMap<AstPtr> &Partials,
450 llvm::StringMap<Lambda> &Lambdas,
451 llvm::StringMap<SectionLambda> &SectionLambdas,
452 llvm::DenseMap<char, std::string> &Escapes) {
453 Tokens = tokenize(TemplateStr);
454 CurrentPtr = 0;
455 AstPtr RootNode = createRootNode(Partials, Lambdas, SectionLambdas, Escapes);
456 parseMustache(RootNode.get(), Partials, Lambdas, SectionLambdas, Escapes);
457 return RootNode;
458}
459
460void Parser::parseMustache(ASTNode *Parent, llvm::StringMap<AstPtr> &Partials,
461 llvm::StringMap<Lambda> &Lambdas,
462 llvm::StringMap<SectionLambda> &SectionLambdas,
463 llvm::DenseMap<char, std::string> &Escapes) {
464
465 while (CurrentPtr < Tokens.size()) {
466 Token CurrentToken = Tokens[CurrentPtr];
467 CurrentPtr++;
468 Accessor A = CurrentToken.getAccessor();
469 AstPtr CurrentNode;
470
471 switch (CurrentToken.getType()) {
472 case Token::Type::Text: {
473 CurrentNode = createTextNode(std::move(CurrentToken.TokenBody), Parent,
474 Partials, Lambdas, SectionLambdas, Escapes);
475 Parent->addChild(std::move(CurrentNode));
476 break;
477 }
478 case Token::Type::Variable: {
479 CurrentNode = createNode(ASTNode::Variable, std::move(A), Parent,
480 Partials, Lambdas, SectionLambdas, Escapes);
481 Parent->addChild(std::move(CurrentNode));
482 break;
483 }
484 case Token::Type::UnescapeVariable: {
485 CurrentNode = createNode(ASTNode::UnescapeVariable, std::move(A), Parent,
486 Partials, Lambdas, SectionLambdas, Escapes);
487 Parent->addChild(std::move(CurrentNode));
488 break;
489 }
490 case Token::Type::Partial: {
491 CurrentNode = createNode(ASTNode::Partial, std::move(A), Parent, Partials,
492 Lambdas, SectionLambdas, Escapes);
493 CurrentNode->setIndentation(CurrentToken.getIndentation());
494 Parent->addChild(std::move(CurrentNode));
495 break;
496 }
497 case Token::Type::SectionOpen: {
498 CurrentNode = createNode(ASTNode::Section, A, Parent, Partials, Lambdas,
499 SectionLambdas, Escapes);
500 size_t Start = CurrentPtr;
501 parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas,
502 Escapes);
503 const size_t End = CurrentPtr - 1;
504 std::string RawBody;
505 for (std::size_t I = Start; I < End; I++)
506 RawBody += Tokens[I].RawBody;
507 CurrentNode->setRawBody(std::move(RawBody));
508 Parent->addChild(std::move(CurrentNode));
509 break;
510 }
511 case Token::Type::InvertSectionOpen: {
512 CurrentNode = createNode(ASTNode::InvertSection, A, Parent, Partials,
513 Lambdas, SectionLambdas, Escapes);
514 size_t Start = CurrentPtr;
515 parseMustache(CurrentNode.get(), Partials, Lambdas, SectionLambdas,
516 Escapes);
517 const size_t End = CurrentPtr - 1;
518 std::string RawBody;
519 for (size_t Idx = Start; Idx < End; Idx++)
520 RawBody += Tokens[Idx].RawBody;
521 CurrentNode->setRawBody(std::move(RawBody));
522 Parent->addChild(std::move(CurrentNode));
523 break;
524 }
525 case Token::Type::Comment:
526 break;
527 case Token::Type::SectionClose:
528 return;
529 }
530 }
531}
532void toMustacheString(const json::Value &Data, raw_ostream &OS) {
533 switch (Data.kind()) {
534 case json::Value::Null:
535 return;
536 case json::Value::Number: {
537 auto Num = *Data.getAsNumber();
538 std::ostringstream SS;
539 SS << Num;
540 OS << SS.str();
541 return;
542 }
543 case json::Value::String: {
544 auto Str = *Data.getAsString();
545 OS << Str.str();
546 return;
547 }
548
549 case json::Value::Array: {
550 auto Arr = *Data.getAsArray();
551 if (Arr.empty())
552 return;
553 [[fallthrough]];
554 }
555 case json::Value::Object:
556 case json::Value::Boolean: {
557 llvm::json::OStream JOS(OS, 2);
558 JOS.value(Data);
559 break;
560 }
561 }
562}
563
564void ASTNode::render(const json::Value &Data, raw_ostream &OS) {
565 ParentContext = &Data;
566 const json::Value *ContextPtr = Ty == Root ? ParentContext : findContext();
567 const json::Value &Context = ContextPtr ? *ContextPtr : nullptr;
568
569 switch (Ty) {
570 case Root:
571 renderChild(Data, OS);
572 return;
573 case Text:
574 OS << Body;
575 return;
576 case Partial: {
577 auto Partial = Partials.find(AccessorValue[0]);
578 if (Partial != Partials.end())
579 renderPartial(Data, OS, Partial->getValue().get());
580 return;
581 }
582 case Variable: {
583 auto Lambda = Lambdas.find(AccessorValue[0]);
584 if (Lambda != Lambdas.end())
585 renderLambdas(Data, OS, Lambda->getValue());
586 else {
587 EscapeStringStream ES(OS, Escapes);
588 toMustacheString(Context, ES);
589 }
590 return;
591 }
592 case UnescapeVariable: {
593 auto Lambda = Lambdas.find(AccessorValue[0]);
594 if (Lambda != Lambdas.end())
595 renderLambdas(Data, OS, Lambda->getValue());
596 else
597 toMustacheString(Context, OS);
598 return;
599 }
600 case Section: {
601 // Sections are not rendered if the context is falsey.
602 auto SectionLambda = SectionLambdas.find(AccessorValue[0]);
603 bool IsLambda = SectionLambda != SectionLambdas.end();
604 if (isFalsey(Context) && !IsLambda)
605 return;
606
607 if (IsLambda) {
608 renderSectionLambdas(Data, OS, SectionLambda->getValue());
609 return;
610 }
611
612 if (Context.getAsArray()) {
613 const json::Array *Arr = Context.getAsArray();
614 for (const json::Value &V : *Arr)
615 renderChild(V, OS);
616 return;
617 }
618 renderChild(Context, OS);
619 return;
620 }
621 case InvertSection: {
622 bool IsLambda =
623 SectionLambdas.find(AccessorValue[0]) != SectionLambdas.end();
624 if (!isFalsey(Context) || IsLambda)
625 return;
626 renderChild(Context, OS);
627 return;
628 }
629 }
630 llvm_unreachable("Invalid ASTNode type");
631}
632
633const json::Value *ASTNode::findContext() {
634 // The mustache spec allows for dot notation to access nested values
635 // a single dot refers to the current context.
636 // We attempt to find the JSON context in the current node, if it is not
637 // found, then we traverse the parent nodes to find the context until we
638 // reach the root node or the context is found.
639 if (AccessorValue.empty())
640 return nullptr;
641 if (AccessorValue[0] == ".")
642 return ParentContext;
643
644 const json::Object *CurrentContext = ParentContext->getAsObject();
645 StringRef CurrentAccessor = AccessorValue[0];
646 ASTNode *CurrentParent = Parent;
647
648 while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
649 if (CurrentParent->Ty != Root) {
650 CurrentContext = CurrentParent->ParentContext->getAsObject();
651 CurrentParent = CurrentParent->Parent;
652 continue;
653 }
654 return nullptr;
655 }
656 const json::Value *Context = nullptr;
657 for (auto [Idx, Acc] : enumerate(AccessorValue)) {
658 const json::Value *CurrentValue = CurrentContext->get(Acc);
659 if (!CurrentValue)
660 return nullptr;
661 if (Idx < AccessorValue.size() - 1) {
662 CurrentContext = CurrentValue->getAsObject();
663 if (!CurrentContext)
664 return nullptr;
665 } else
666 Context = CurrentValue;
667 }
668 return Context;
669}
670
671void ASTNode::renderChild(const json::Value &Contexts, llvm::raw_ostream &OS) {
672 for (AstPtr &Child : Children)
673 Child->render(Contexts, OS);
674}
675
676void ASTNode::renderPartial(const json::Value &Contexts, llvm::raw_ostream &OS,
677 ASTNode *Partial) {
678 AddIndentationStringStream IS(OS, Indentation);
679 Partial->render(Contexts, IS);
680}
681
682void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS,
683 Lambda &L) {
684 json::Value LambdaResult = L();
685 std::string LambdaStr;
686 raw_string_ostream Output(LambdaStr);
687 toMustacheString(LambdaResult, Output);
688 Parser P = Parser(LambdaStr);
689 AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes);
690
691 EscapeStringStream ES(OS, Escapes);
692 if (Ty == Variable) {
693 LambdaNode->render(Contexts, ES);
694 return;
695 }
696 LambdaNode->render(Contexts, OS);
697}
698
699void ASTNode::renderSectionLambdas(const json::Value &Contexts,
700 llvm::raw_ostream &OS, SectionLambda &L) {
701 json::Value Return = L(RawBody);
702 if (isFalsey(Return))
703 return;
704 std::string LambdaStr;
705 raw_string_ostream Output(LambdaStr);
706 toMustacheString(Return, Output);
707 Parser P = Parser(LambdaStr);
708 AstPtr LambdaNode = P.parse(Partials, Lambdas, SectionLambdas, Escapes);
709 LambdaNode->render(Contexts, OS);
710 return;
711}
712
713void Template::render(const json::Value &Data, llvm::raw_ostream &OS) {
714 Tree->render(Data, OS);
715}
716
717void Template::registerPartial(std::string Name, std::string Partial) {
718 Parser P = Parser(Partial);
719 AstPtr PartialTree = P.parse(Partials, Lambdas, SectionLambdas, Escapes);
720 Partials.insert(std::make_pair(Name, std::move(PartialTree)));
721}
722
723void Template::registerLambda(std::string Name, Lambda L) { Lambdas[Name] = L; }
724
725void Template::registerLambda(std::string Name, SectionLambda L) {
726 SectionLambdas[Name] = L;
727}
728
729void Template::overrideEscapeCharacters(DenseMap<char, std::string> E) {
730 Escapes = std::move(E);
731}
732
733Template::Template(StringRef TemplateStr) {
734 Parser P = Parser(TemplateStr);
735 Tree = P.parse(Partials, Lambdas, SectionLambdas, Escapes);
736 // The default behavior is to escape html entities.
737 DenseMap<char, std::string> HtmlEntities = {{'&', "&amp;"},
738 {'<', "&lt;"},
739 {'>', "&gt;"},
740 {'"', "&quot;"},
741 {'\'', "&#39;"}};
742 overrideEscapeCharacters(HtmlEntities);
743}
744
745Template::Template(Template &&Other) noexcept
746 : Partials(std::move(Other.Partials)), Lambdas(std::move(Other.Lambdas)),
747 SectionLambdas(std::move(Other.SectionLambdas)),
748 Escapes(std::move(Other.Escapes)), Tree(std::move(Other.Tree)) {}
749
750Template::~Template() = default;
751
752Template &Template::operator=(Template &&Other) noexcept {
753 if (this != &Other) {
754 Partials = std::move(Other.Partials);
755 Lambdas = std::move(Other.Lambdas);
756 SectionLambdas = std::move(Other.SectionLambdas);
757 Escapes = std::move(Other.Escapes);
758 Tree = std::move(Other.Tree);
759 Other.Tree = nullptr;
760 }
761 return *this;
762}
763} // namespace llvm::mustache