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