77// ===----------------------------------------------------------------------===//
88#include " llvm/Support/Mustache.h"
99#include " llvm/ADT/SmallVector.h"
10+ #include " llvm/Support/Debug.h"
1011#include " llvm/Support/raw_ostream.h"
12+
13+ #include < cctype>
1114#include < sstream>
1215
16+ #define DEBUG_TYPE " mustache"
17+
1318using namespace llvm ;
1419using namespace llvm ::mustache;
1520
@@ -62,6 +67,7 @@ class Token {
6267 InvertSectionOpen,
6368 UnescapeVariable,
6469 Comment,
70+ SetDelimiter,
6571 };
6672
6773 Token (std::string Str)
@@ -102,6 +108,8 @@ class Token {
102108 return Type::Partial;
103109 case ' &' :
104110 return Type::UnescapeVariable;
111+ case ' =' :
112+ return Type::SetDelimiter;
105113 default :
106114 return Type::Variable;
107115 }
@@ -189,27 +197,27 @@ class ASTNode {
189197};
190198
191199// A wrapper for arena allocator for ASTNodes
192- AstPtr createRootNode (llvm::StringMap<AstPtr> &Partials,
193- llvm::StringMap<Lambda> &Lambdas,
194- llvm::StringMap<SectionLambda> &SectionLambdas,
195- EscapeMap &Escapes) {
200+ static AstPtr createRootNode (llvm::StringMap<AstPtr> &Partials,
201+ llvm::StringMap<Lambda> &Lambdas,
202+ llvm::StringMap<SectionLambda> &SectionLambdas,
203+ EscapeMap &Escapes) {
196204 return std::make_unique<ASTNode>(Partials, Lambdas, SectionLambdas, Escapes);
197205}
198206
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) {
207+ static AstPtr createNode (ASTNode::Type T, Accessor A, ASTNode *Parent,
208+ llvm::StringMap<AstPtr> &Partials,
209+ llvm::StringMap<Lambda> &Lambdas,
210+ llvm::StringMap<SectionLambda> &SectionLambdas,
211+ EscapeMap &Escapes) {
204212 return std::make_unique<ASTNode>(T, std::move (A), Parent, Partials, Lambdas,
205213 SectionLambdas, Escapes);
206214}
207215
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) {
216+ static AstPtr createTextNode (std::string Body, ASTNode *Parent,
217+ llvm::StringMap<AstPtr> &Partials,
218+ llvm::StringMap<Lambda> &Lambdas,
219+ llvm::StringMap<SectionLambda> &SectionLambdas,
220+ EscapeMap &Escapes) {
213221 return std::make_unique<ASTNode>(std::move (Body), Parent, Partials, Lambdas,
214222 SectionLambdas, Escapes);
215223}
@@ -226,7 +234,7 @@ AstPtr createTextNode(std::string Body, ASTNode *Parent,
226234// and the current token is the second token.
227235// For example:
228236// "{{#Section}}"
229- bool hasTextBehind (size_t Idx, const ArrayRef<Token> &Tokens) {
237+ static bool hasTextBehind (size_t Idx, const ArrayRef<Token> &Tokens) {
230238 if (Idx == 0 )
231239 return true ;
232240
@@ -242,7 +250,7 @@ bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) {
242250// Function to check if there's no meaningful text ahead.
243251// We determine if a token has text ahead if the left of previous
244252// token does not start with a newline.
245- bool hasTextAhead (size_t Idx, const ArrayRef<Token> &Tokens) {
253+ static bool hasTextAhead (size_t Idx, const ArrayRef<Token> &Tokens) {
246254 if (Idx >= Tokens.size () - 1 )
247255 return true ;
248256
@@ -255,11 +263,11 @@ bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) {
255263 return !TokenBody.starts_with (" \r\n " ) && !TokenBody.starts_with (" \n " );
256264}
257265
258- bool requiresCleanUp (Token::Type T) {
266+ static bool requiresCleanUp (Token::Type T) {
259267 // We must clean up all the tokens that could contain child nodes.
260268 return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen ||
261269 T == Token::Type::SectionClose || T == Token::Type::Comment ||
262- T == Token::Type::Partial;
270+ T == Token::Type::Partial || T == Token::Type::SetDelimiter ;
263271}
264272
265273// Adjust next token body if there is no text ahead.
@@ -268,7 +276,7 @@ bool requiresCleanUp(Token::Type T) {
268276// "{{! Comment }} \nLine 2"
269277// would be considered as no text ahead and should be rendered as
270278// " Line 2"
271- void stripTokenAhead (SmallVectorImpl<Token> &Tokens, size_t Idx) {
279+ static void stripTokenAhead (SmallVectorImpl<Token> &Tokens, size_t Idx) {
272280 Token &NextToken = Tokens[Idx + 1 ];
273281 StringRef NextTokenBody = NextToken.TokenBody ;
274282 // Cut off the leading newline which could be \n or \r\n.
@@ -286,8 +294,8 @@ void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {
286294// "A"
287295// The exception for this is partial tag which requires us to
288296// keep track of the indentation once it's rendered.
289- void stripTokenBefore (SmallVectorImpl<Token> &Tokens, size_t Idx,
290- Token &CurrentToken, Token::Type CurrentType) {
297+ static void stripTokenBefore (SmallVectorImpl<Token> &Tokens, size_t Idx,
298+ Token &CurrentToken, Token::Type CurrentType) {
291299 Token &PrevToken = Tokens[Idx - 1 ];
292300 StringRef PrevTokenBody = PrevToken.TokenBody ;
293301 StringRef Unindented = PrevTokenBody.rtrim (" \r\t\v " );
@@ -296,57 +304,128 @@ void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx,
296304 CurrentToken.setIndentation (Indentation);
297305}
298306
307+ struct Tag {
308+ enum class Kind {
309+ None,
310+ Normal, // {{...}}
311+ Triple, // {{{...}}}
312+ };
313+
314+ Kind TagKind = Kind::None;
315+ StringRef Content; // The content between the delimiters.
316+ StringRef FullMatch; // The entire tag, including delimiters.
317+ size_t StartPosition = StringRef::npos;
318+ };
319+
320+ static Tag findNextTag (StringRef Template, size_t StartPos, StringRef Open,
321+ StringRef Close) {
322+ const StringLiteral TripleOpen (" {{{" );
323+ const StringLiteral TripleClose (" }}}" );
324+
325+ size_t NormalOpenPos = Template.find (Open, StartPos);
326+ size_t TripleOpenPos = Template.find (TripleOpen, StartPos);
327+
328+ Tag Result;
329+
330+ // Determine which tag comes first.
331+ if (TripleOpenPos != StringRef::npos &&
332+ (NormalOpenPos == StringRef::npos || TripleOpenPos <= NormalOpenPos)) {
333+ // Found a triple mustache tag.
334+ size_t EndPos =
335+ Template.find (TripleClose, TripleOpenPos + TripleOpen.size ());
336+ if (EndPos == StringRef::npos)
337+ return Result; // No closing tag found.
338+
339+ Result.TagKind = Tag::Kind::Triple;
340+ Result.StartPosition = TripleOpenPos;
341+ size_t ContentStart = TripleOpenPos + TripleOpen.size ();
342+ Result.Content = Template.substr (ContentStart, EndPos - ContentStart);
343+ Result.FullMatch = Template.substr (
344+ TripleOpenPos, (EndPos + TripleClose.size ()) - TripleOpenPos);
345+ } else if (NormalOpenPos != StringRef::npos) {
346+ // Found a normal mustache tag.
347+ size_t EndPos = Template.find (Close, NormalOpenPos + Open.size ());
348+ if (EndPos == StringRef::npos)
349+ return Result; // No closing tag found.
350+
351+ Result.TagKind = Tag::Kind::Normal;
352+ Result.StartPosition = NormalOpenPos;
353+ size_t ContentStart = NormalOpenPos + Open.size ();
354+ Result.Content = Template.substr (ContentStart, EndPos - ContentStart);
355+ Result.FullMatch =
356+ Template.substr (NormalOpenPos, (EndPos + Close.size ()) - NormalOpenPos);
357+ }
358+
359+ return Result;
360+ }
361+
362+ static void processTag (const Tag &T, SmallVectorImpl<Token> &Tokens,
363+ SmallString<8 > &Open, SmallString<8 > &Close) {
364+ LLVM_DEBUG (dbgs () << " Found tag: \" " << T.FullMatch << " \" , Content: \" "
365+ << T.Content << " \"\n " );
366+ if (T.TagKind == Tag::Kind::Triple) {
367+ Tokens.emplace_back (T.FullMatch .str (), " &" + T.Content .str (), ' &' );
368+ LLVM_DEBUG (dbgs () << " Created UnescapeVariable token.\n " );
369+ return ;
370+ }
371+ StringRef Interpolated = T.Content ;
372+ std::string RawBody = T.FullMatch .str ();
373+ if (!Interpolated.trim ().starts_with (" =" )) {
374+ char Front = Interpolated.empty () ? ' ' : Interpolated.trim ().front ();
375+ Tokens.emplace_back (RawBody, Interpolated.str (), Front);
376+ LLVM_DEBUG (dbgs () << " Created tag token of type '" << Front << " '\n " );
377+ return ;
378+ }
379+ Tokens.emplace_back (RawBody, Interpolated.str (), ' =' );
380+ StringRef DelimSpec = Interpolated.trim ();
381+ DelimSpec = DelimSpec.drop_front (1 );
382+ DelimSpec = DelimSpec.take_until ([](char C) { return C == ' =' ; });
383+ DelimSpec = DelimSpec.trim ();
384+
385+ auto [NewOpen, NewClose] = DelimSpec.split (' ' );
386+ Open = NewOpen;
387+ Close = NewClose;
388+
389+ LLVM_DEBUG (dbgs () << " Found Set Delimiter tag. NewOpen='" << Open
390+ << " ', NewClose='" << Close << " '\n " );
391+ }
392+
299393// Simple tokenizer that splits the template into tokens.
300394// The mustache spec allows {{{ }}} to unescape variables,
301395// but we don't support that here. An unescape variable
302396// is represented only by {{& variable}}.
303- SmallVector<Token> tokenize (StringRef Template) {
397+ static SmallVector<Token> tokenize (StringRef Template) {
398+ LLVM_DEBUG (dbgs () << " Tokenizing template: \" " << Template << " \"\n " );
304399 SmallVector<Token> Tokens;
305- StringLiteral Open (" {{" );
306- StringLiteral Close (" }}" );
307- StringLiteral TripleOpen (" {{{" );
308- StringLiteral TripleClose (" }}}" );
400+ SmallString<8 > Open (" {{" );
401+ SmallString<8 > Close (" }}" );
309402 size_t Start = 0 ;
310- size_t DelimiterStart = Template.find (Open);
311- if (DelimiterStart == StringRef::npos) {
312- Tokens.emplace_back (Template.str ());
313- return Tokens;
314- }
315- while (DelimiterStart != StringRef::npos) {
316- if (DelimiterStart != Start)
317- Tokens.emplace_back (Template.substr (Start, DelimiterStart - Start).str ());
318-
319- if (Template.substr (DelimiterStart).starts_with (TripleOpen)) {
320- size_t DelimiterEnd = Template.find (TripleClose, DelimiterStart);
321- if (DelimiterEnd == StringRef::npos)
322- break ;
323- size_t BodyStart = DelimiterStart + TripleOpen.size ();
324- std::string Body =
325- Template.substr (BodyStart, DelimiterEnd - BodyStart).str ();
326- std::string RawBody =
327- Template.substr (DelimiterStart, DelimiterEnd - DelimiterStart + 3 )
328- .str ();
329- Tokens.emplace_back (RawBody, " &" + Body, ' &' );
330- Start = DelimiterEnd + TripleClose.size ();
331- } else {
332- size_t DelimiterEnd = Template.find (Close, DelimiterStart);
333- if (DelimiterEnd == StringRef::npos)
334- break ;
335-
336- // Extract the Interpolated variable without delimiters.
337- size_t InterpolatedStart = DelimiterStart + Open.size ();
338- size_t InterpolatedEnd = DelimiterEnd - DelimiterStart - Close.size ();
339- std::string Interpolated =
340- Template.substr (InterpolatedStart, InterpolatedEnd).str ();
341- std::string RawBody = Open.str () + Interpolated + Close.str ();
342- Tokens.emplace_back (RawBody, Interpolated, Interpolated[0 ]);
343- Start = DelimiterEnd + Close.size ();
403+
404+ while (Start < Template.size ()) {
405+ LLVM_DEBUG (dbgs () << " Loop start. Start=" << Start << " , Open='" << Open
406+ << " ', Close='" << Close << " '\n " );
407+ Tag T = findNextTag (Template, Start, Open, Close);
408+
409+ if (T.TagKind == Tag::Kind::None) {
410+ // No more tags, the rest is text.
411+ Tokens.emplace_back (Template.substr (Start).str ());
412+ LLVM_DEBUG (dbgs () << " No more tags. Created final Text token: \" "
413+ << Template.substr (Start) << " \"\n " );
414+ break ;
415+ }
416+
417+ // Add the text before the tag.
418+ if (T.StartPosition > Start) {
419+ StringRef Text = Template.substr (Start, T.StartPosition - Start);
420+ Tokens.emplace_back (Text.str ());
421+ LLVM_DEBUG (dbgs () << " Created Text token: \" " << Text << " \"\n " );
344422 }
345- DelimiterStart = Template.find (Open, Start);
346- }
347423
348- if (Start < Template.size ())
349- Tokens.emplace_back (Template.substr (Start).str ());
424+ processTag (T, Tokens, Open, Close);
425+
426+ // Move past the tag.
427+ Start = T.StartPosition + T.FullMatch .size ();
428+ }
350429
351430 // Fix up white spaces for:
352431 // - open sections
@@ -388,6 +467,7 @@ SmallVector<Token> tokenize(StringRef Template) {
388467 if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx))
389468 stripTokenBefore (Tokens, Idx, CurrentToken, CurrentType);
390469 }
470+ LLVM_DEBUG (dbgs () << " Tokenizing finished.\n " );
391471 return Tokens;
392472}
393473
@@ -551,13 +631,14 @@ void Parser::parseMustache(ASTNode *Parent, llvm::StringMap<AstPtr> &Partials,
551631 break ;
552632 }
553633 case Token::Type::Comment:
634+ case Token::Type::SetDelimiter:
554635 break ;
555636 case Token::Type::SectionClose:
556637 return ;
557638 }
558639 }
559640}
560- void toMustacheString (const json::Value &Data, raw_ostream &OS) {
641+ static void toMustacheString (const json::Value &Data, raw_ostream &OS) {
561642 switch (Data.kind ()) {
562643 case json::Value::Null:
563644 return ;
@@ -590,6 +671,8 @@ void toMustacheString(const json::Value &Data, raw_ostream &OS) {
590671}
591672
592673void ASTNode::render (const json::Value &CurrentCtx, raw_ostream &OS) {
674+ if (Ty != Root && Ty != Text && AccessorValue.empty ())
675+ return ;
593676 // Set the parent context to the incoming context so that we
594677 // can walk up the context tree correctly in findContext().
595678 ParentContext = &CurrentCtx;
@@ -789,3 +872,5 @@ Template &Template::operator=(Template &&Other) noexcept {
789872 return *this ;
790873}
791874} // namespace llvm::mustache
875+
876+ #undef DEBUG_TYPE
0 commit comments