brintos

brintos / llvm-project-archived public Read only

0
0
Text · 28.5 KiB · f27a647 Raw
934 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8#include "llvm/Support/Mustache.h"9#include "llvm/ADT/SmallVector.h"10#include "llvm/Support/Debug.h"11#include "llvm/Support/raw_ostream.h"12#include <cctype>13#include <sstream>14 15#define DEBUG_TYPE "mustache"16 17using namespace llvm;18using namespace llvm::mustache;19 20namespace {21 22using Accessor = ArrayRef<StringRef>;23 24static bool isFalsey(const json::Value &V) {25  return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||26         (V.getAsArray() && V.getAsArray()->empty());27}28 29static bool isContextFalsey(const json::Value *V) {30  // A missing context (represented by a nullptr) is defined as falsey.31  if (!V)32    return true;33  return isFalsey(*V);34}35 36static void splitAndTrim(StringRef Str, SmallVectorImpl<StringRef> &Tokens) {37  size_t CurrentPos = 0;38  while (CurrentPos < Str.size()) {39    // Find the next delimiter.40    size_t DelimiterPos = Str.find('.', CurrentPos);41 42    // If no delimiter is found, process the rest of the string.43    if (DelimiterPos == StringRef::npos)44      DelimiterPos = Str.size();45 46    // Get the current part, which may have whitespace.47    StringRef Part = Str.slice(CurrentPos, DelimiterPos);48 49    // Manually trim the part without creating a new string object.50    size_t Start = Part.find_first_not_of(" \t\r\n");51    if (Start != StringRef::npos) {52      size_t End = Part.find_last_not_of(" \t\r\n");53      Tokens.push_back(Part.slice(Start, End + 1));54    }55 56    // Move past the delimiter for the next iteration.57    CurrentPos = DelimiterPos + 1;58  }59}60 61static Accessor splitMustacheString(StringRef Str, MustacheContext &Ctx) {62  // We split the mustache string into an accessor.63  // For example:64  //    "a.b.c" would be split into {"a", "b", "c"}65  // We make an exception for a single dot which66  // refers to the current context.67  SmallVector<StringRef> Tokens;68  if (Str == ".") {69    // "." is a special accessor that refers to the current context.70    // It's a literal, so it doesn't need to be saved.71    Tokens.push_back(".");72  } else {73    splitAndTrim(Str, Tokens);74  }75  // Now, allocate memory for the array of StringRefs in the arena.76  StringRef *ArenaTokens = Ctx.Allocator.Allocate<StringRef>(Tokens.size());77  // Copy the StringRefs from the stack vector to the arena.78  llvm::copy(Tokens, ArenaTokens);79  // Return an ArrayRef pointing to the stable arena memory.80  return ArrayRef<StringRef>(ArenaTokens, Tokens.size());81}82} // namespace83 84namespace llvm::mustache {85 86class MustacheOutputStream : public raw_ostream {87public:88  MustacheOutputStream() = default;89  ~MustacheOutputStream() override = default;90 91  virtual void suspendIndentation() {}92  virtual void resumeIndentation() {}93 94private:95  void anchor() override;96};97 98void MustacheOutputStream::anchor() {}99 100class RawMustacheOutputStream : public MustacheOutputStream {101public:102  RawMustacheOutputStream(raw_ostream &OS) : OS(OS) { SetUnbuffered(); }103 104private:105  raw_ostream &OS;106 107  void write_impl(const char *Ptr, size_t Size) override {108    OS.write(Ptr, Size);109  }110  uint64_t current_pos() const override { return OS.tell(); }111};112 113class Token {114public:115  enum class Type {116    Text,117    Variable,118    Partial,119    SectionOpen,120    SectionClose,121    InvertSectionOpen,122    UnescapeVariable,123    Comment,124    SetDelimiter,125  };126 127  Token(StringRef Str)128      : TokenType(Type::Text), RawBody(Str), TokenBody(RawBody),129        AccessorValue({}), Indentation(0) {};130 131  Token(StringRef RawBody, StringRef TokenBody, char Identifier,132        MustacheContext &Ctx)133      : RawBody(RawBody), TokenBody(TokenBody), Indentation(0) {134    TokenType = getTokenType(Identifier);135    if (TokenType == Type::Comment)136      return;137    StringRef AccessorStr(this->TokenBody);138    if (TokenType != Type::Variable)139      AccessorStr = AccessorStr.substr(1);140    AccessorValue = splitMustacheString(StringRef(AccessorStr).trim(), Ctx);141  }142 143  ArrayRef<StringRef> getAccessor() const { return AccessorValue; }144 145  Type getType() const { return TokenType; }146 147  void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }148 149  size_t getIndentation() const { return Indentation; }150 151  static Type getTokenType(char Identifier) {152    switch (Identifier) {153    case '#':154      return Type::SectionOpen;155    case '/':156      return Type::SectionClose;157    case '^':158      return Type::InvertSectionOpen;159    case '!':160      return Type::Comment;161    case '>':162      return Type::Partial;163    case '&':164      return Type::UnescapeVariable;165    case '=':166      return Type::SetDelimiter;167    default:168      return Type::Variable;169    }170  }171 172  Type TokenType;173  // RawBody is the original string that was tokenized.174  StringRef RawBody;175  // TokenBody is the original string with the identifier removed.176  StringRef TokenBody;177  ArrayRef<StringRef> AccessorValue;178  size_t Indentation;179};180 181using EscapeMap = DenseMap<char, std::string>;182 183class ASTNode : public ilist_node<ASTNode> {184public:185  enum Type {186    Root,187    Text,188    Partial,189    Variable,190    UnescapeVariable,191    Section,192    InvertSection,193  };194 195  ASTNode(MustacheContext &Ctx)196      : Ctx(Ctx), Ty(Type::Root), Parent(nullptr), ParentContext(nullptr) {}197 198  ASTNode(MustacheContext &Ctx, StringRef Body, ASTNode *Parent)199      : Ctx(Ctx), Ty(Type::Text), Body(Body), Parent(Parent),200        ParentContext(nullptr) {}201 202  // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes203  ASTNode(MustacheContext &Ctx, Type Ty, ArrayRef<StringRef> Accessor,204          ASTNode *Parent)205      : Ctx(Ctx), Ty(Ty), Parent(Parent), AccessorValue(Accessor),206        ParentContext(nullptr) {}207 208  void addChild(AstPtr Child) { Children.push_back(Child); };209 210  void setRawBody(StringRef NewBody) { RawBody = NewBody; };211 212  void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };213 214  void render(const llvm::json::Value &Data, MustacheOutputStream &OS);215 216private:217  void renderLambdas(const llvm::json::Value &Contexts,218                     MustacheOutputStream &OS, Lambda &L);219 220  void renderSectionLambdas(const llvm::json::Value &Contexts,221                            MustacheOutputStream &OS, SectionLambda &L);222 223  void renderPartial(const llvm::json::Value &Contexts,224                     MustacheOutputStream &OS, ASTNode *Partial);225 226  void renderChild(const llvm::json::Value &Context, MustacheOutputStream &OS);227 228  const llvm::json::Value *findContext();229 230  void renderRoot(const json::Value &CurrentCtx, MustacheOutputStream &OS);231  void renderText(MustacheOutputStream &OS);232  void renderPartial(const json::Value &CurrentCtx, MustacheOutputStream &OS);233  void renderVariable(const json::Value &CurrentCtx, MustacheOutputStream &OS);234  void renderUnescapeVariable(const json::Value &CurrentCtx,235                              MustacheOutputStream &OS);236  void renderSection(const json::Value &CurrentCtx, MustacheOutputStream &OS);237  void renderInvertSection(const json::Value &CurrentCtx,238                           MustacheOutputStream &OS);239 240  MustacheContext &Ctx;241  Type Ty;242  size_t Indentation = 0;243  StringRef RawBody;244  StringRef Body;245  ASTNode *Parent;246  ASTNodeList Children;247  const ArrayRef<StringRef> AccessorValue;248  const llvm::json::Value *ParentContext;249};250 251// A wrapper for arena allocator for ASTNodes252static AstPtr createRootNode(MustacheContext &Ctx) {253  return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx);254}255 256static AstPtr createNode(MustacheContext &Ctx, ASTNode::Type T,257                         ArrayRef<StringRef> A, ASTNode *Parent) {258  return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, T, A, Parent);259}260 261static AstPtr createTextNode(MustacheContext &Ctx, StringRef Body,262                             ASTNode *Parent) {263  return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, Body, Parent);264}265 266// Function to check if there is meaningful text behind.267// We determine if a token has meaningful text behind268// if the right of previous token contains anything that is269// not a newline.270// For example:271//  "Stuff {{#Section}}" (returns true)272//   vs273//  "{{#Section}} \n" (returns false)274// We make an exception for when previous token is empty275// and the current token is the second token.276// For example:277//  "{{#Section}}"278static bool hasTextBehind(size_t Idx, const ArrayRef<Token> &Tokens) {279  if (Idx == 0)280    return true;281 282  size_t PrevIdx = Idx - 1;283  if (Tokens[PrevIdx].getType() != Token::Type::Text)284    return true;285 286  const Token &PrevToken = Tokens[PrevIdx];287  StringRef TokenBody = StringRef(PrevToken.RawBody).rtrim(" \r\t\v");288  return !TokenBody.ends_with("\n") && !(TokenBody.empty() && Idx == 1);289}290 291// Function to check if there's no meaningful text ahead.292// We determine if a token has text ahead if the left of previous293// token does not start with a newline.294static bool hasTextAhead(size_t Idx, const ArrayRef<Token> &Tokens) {295  if (Idx >= Tokens.size() - 1)296    return true;297 298  size_t NextIdx = Idx + 1;299  if (Tokens[NextIdx].getType() != Token::Type::Text)300    return true;301 302  const Token &NextToken = Tokens[NextIdx];303  StringRef TokenBody = StringRef(NextToken.RawBody).ltrim(" ");304  return !TokenBody.starts_with("\r\n") && !TokenBody.starts_with("\n");305}306 307static bool requiresCleanUp(Token::Type T) {308  // We must clean up all the tokens that could contain child nodes.309  return T == Token::Type::SectionOpen || T == Token::Type::InvertSectionOpen ||310         T == Token::Type::SectionClose || T == Token::Type::Comment ||311         T == Token::Type::Partial || T == Token::Type::SetDelimiter;312}313 314// Adjust next token body if there is no text ahead.315// For example:316// The template string317//  "{{! Comment }} \nLine 2"318// would be considered as no text ahead and should be rendered as319//  " Line 2"320static void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {321  Token &NextToken = Tokens[Idx + 1];322  StringRef NextTokenBody = NextToken.TokenBody;323  // Cut off the leading newline which could be \n or \r\n.324  if (NextTokenBody.starts_with("\r\n"))325    NextToken.TokenBody = NextTokenBody.substr(2);326  else if (NextTokenBody.starts_with("\n"))327    NextToken.TokenBody = NextTokenBody.substr(1);328}329 330// Adjust previous token body if there no text behind.331// For example:332//  The template string333//  " \t{{#section}}A{{/section}}"334// would be considered as having no text ahead and would be render as:335//  "A"336void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx,337                      Token &CurrentToken, Token::Type CurrentType) {338  Token &PrevToken = Tokens[Idx - 1];339  StringRef PrevTokenBody = PrevToken.TokenBody;340  StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v");341  size_t Indentation = PrevTokenBody.size() - Unindented.size();342  PrevToken.TokenBody = Unindented;343  CurrentToken.setIndentation(Indentation);344}345 346struct Tag {347  enum class Kind {348    None,349    Normal, // {{...}}350    Triple, // {{{...}}}351  };352 353  Kind TagKind = Kind::None;354  StringRef Content;   // The content between the delimiters.355  StringRef FullMatch; // The entire tag, including delimiters.356  size_t StartPosition = StringRef::npos;357};358 359[[maybe_unused]] static const char *tagKindToString(Tag::Kind K) {360  switch (K) {361  case Tag::Kind::None:362    return "None";363  case Tag::Kind::Normal:364    return "Normal";365  case Tag::Kind::Triple:366    return "Triple";367  }368  llvm_unreachable("Unknown Tag::Kind");369}370 371[[maybe_unused]] static const char *jsonKindToString(json::Value::Kind K) {372  switch (K) {373  case json::Value::Kind::Null:374    return "JSON_KIND_NULL";375  case json::Value::Kind::Boolean:376    return "JSON_KIND_BOOLEAN";377  case json::Value::Kind::Number:378    return "JSON_KIND_NUMBER";379  case json::Value::Kind::String:380    return "JSON_KIND_STRING";381  case json::Value::Kind::Array:382    return "JSON_KIND_ARRAY";383  case json::Value::Kind::Object:384    return "JSON_KIND_OBJECT";385  }386  llvm_unreachable("Unknown json::Value::Kind");387}388 389// Simple tokenizer that splits the template into tokens.390static SmallVector<Token> tokenize(StringRef Template, MustacheContext &Ctx) {391  LLVM_DEBUG(dbgs() << "[Tokenize Template] \"" << Template << "\"\n");392  SmallVector<Token> Tokens;393  SmallString<8> Open("{{");394  SmallString<8> Close("}}");395  size_t Cursor = 0;396  size_t TextStart = 0;397 398  const StringLiteral TripleOpen("{{{");399  const StringLiteral TripleClose("}}}");400 401  while (Cursor < Template.size()) {402    StringRef TemplateSuffix = Template.substr(Cursor);403    StringRef TagOpen, TagClose;404    Tag::Kind Kind;405 406    // Determine which tag we've encountered.407    if (TemplateSuffix.starts_with(TripleOpen)) {408      Kind = Tag::Kind::Triple;409      TagOpen = TripleOpen;410      TagClose = TripleClose;411    } else if (TemplateSuffix.starts_with(Open)) {412      Kind = Tag::Kind::Normal;413      TagOpen = Open;414      TagClose = Close;415    } else {416      // Not at a tag, continue scanning.417      ++Cursor;418      continue;419    }420 421    // Found a tag, first add the preceding text.422    if (Cursor > TextStart)423      Tokens.emplace_back(Template.slice(TextStart, Cursor));424 425    // Find the closing tag.426    size_t EndPos = Template.find(TagClose, Cursor + TagOpen.size());427    if (EndPos == StringRef::npos) {428      // No closing tag, the rest is text.429      Tokens.emplace_back(Template.substr(Cursor));430      TextStart = Cursor = Template.size();431      break;432    }433 434    // Extract tag content and full match.435    size_t ContentStart = Cursor + TagOpen.size();436    StringRef Content = Template.substr(ContentStart, EndPos - ContentStart);437    StringRef FullMatch =438        Template.substr(Cursor, (EndPos + TagClose.size()) - Cursor);439 440    // Process the tag (inlined logic from processTag).441    LLVM_DEBUG(dbgs() << "[Tag] " << FullMatch << ", Content: " << Content442                      << ", Kind: " << tagKindToString(Kind) << "\n");443    if (Kind == Tag::Kind::Triple) {444      Tokens.emplace_back(FullMatch, Ctx.Saver.save("&" + Content), '&', Ctx);445    } else { // Normal Tag446      StringRef Interpolated = Content;447      if (!Interpolated.trim().starts_with("=")) {448        char Front = Interpolated.empty() ? ' ' : Interpolated.trim().front();449        Tokens.emplace_back(FullMatch, Interpolated, Front, Ctx);450      } else { // Set Delimiter451        Tokens.emplace_back(FullMatch, Interpolated, '=', Ctx);452        StringRef DelimSpec = Interpolated.trim();453        DelimSpec = DelimSpec.drop_front(1);454        DelimSpec = DelimSpec.take_until([](char C) { return C == '='; });455        DelimSpec = DelimSpec.trim();456 457        auto [NewOpen, NewClose] = DelimSpec.split(' ');458        LLVM_DEBUG(dbgs() << "[Set Delimiter] NewOpen: " << NewOpen459                          << ", NewClose: " << NewClose << "\n");460        Open = NewOpen;461        Close = NewClose;462      }463    }464 465    // Move past the tag for the next iteration.466    Cursor += FullMatch.size();467    TextStart = Cursor;468  }469 470  // Add any remaining text after the last tag.471  if (TextStart < Template.size())472    Tokens.emplace_back(Template.substr(TextStart));473 474  // Fix up white spaces for standalone tags.475  size_t LastIdx = Tokens.size() - 1;476  for (size_t Idx = 0, End = Tokens.size(); Idx < End; ++Idx) {477    Token &CurrentToken = Tokens[Idx];478    Token::Type CurrentType = CurrentToken.getType();479    if (!requiresCleanUp(CurrentType))480      continue;481 482    bool HasTextBehind = hasTextBehind(Idx, Tokens);483    bool HasTextAhead = hasTextAhead(Idx, Tokens);484 485    if ((!HasTextAhead && !HasTextBehind) || (!HasTextAhead && Idx == 0))486      stripTokenAhead(Tokens, Idx);487 488    if ((!HasTextBehind && !HasTextAhead) || (!HasTextBehind && Idx == LastIdx))489      stripTokenBefore(Tokens, Idx, CurrentToken, CurrentType);490  }491  return Tokens;492}493 494// Custom stream to escape strings.495class EscapeStringStream : public MustacheOutputStream {496public:497  explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,498                              EscapeMap &Escape)499      : Escape(Escape), EscapeChars(Escape.keys().begin(), Escape.keys().end()),500        WrappedStream(WrappedStream) {501    SetUnbuffered();502  }503 504protected:505  void write_impl(const char *Ptr, size_t Size) override {506    StringRef Data(Ptr, Size);507    size_t Start = 0;508    while (Start < Size) {509      // Find the next character that needs to be escaped.510      size_t Next = Data.find_first_of(EscapeChars.str(), Start);511 512      // If no escapable characters are found, write the rest of the string.513      if (Next == StringRef::npos) {514        WrappedStream << Data.substr(Start);515        return;516      }517 518      // Write the chunk of text before the escapable character.519      if (Next > Start)520        WrappedStream << Data.substr(Start, Next - Start);521 522      // Look up and write the escaped version of the character.523      WrappedStream << Escape[Data[Next]];524      Start = Next + 1;525    }526  }527 528  uint64_t current_pos() const override { return WrappedStream.tell(); }529 530private:531  EscapeMap &Escape;532  SmallString<8> EscapeChars;533  llvm::raw_ostream &WrappedStream;534};535 536// Custom stream to add indentation used to for rendering partials.537class AddIndentationStringStream : public MustacheOutputStream {538public:539  explicit AddIndentationStringStream(raw_ostream &WrappedStream,540                                      size_t Indentation)541      : Indentation(Indentation), WrappedStream(WrappedStream),542        NeedsIndent(true), IsSuspended(false) {543    SetUnbuffered();544  }545 546  void suspendIndentation() override { IsSuspended = true; }547  void resumeIndentation() override { IsSuspended = false; }548 549protected:550  void write_impl(const char *Ptr, size_t Size) override {551    llvm::StringRef Data(Ptr, Size);552    SmallString<0> Indent;553    Indent.resize(Indentation, ' ');554 555    for (char C : Data) {556      LLVM_DEBUG(dbgs() << "[Indentation Stream] NeedsIndent:" << NeedsIndent557                        << ", C:'" << C << "', Indentation:" << Indentation558                        << "\n");559      if (NeedsIndent && C != '\n') {560        WrappedStream << Indent;561        NeedsIndent = false;562      }563      WrappedStream << C;564      if (C == '\n' && !IsSuspended)565        NeedsIndent = true;566    }567  }568 569  uint64_t current_pos() const override { return WrappedStream.tell(); }570 571private:572  size_t Indentation;573  raw_ostream &WrappedStream;574  bool NeedsIndent;575  bool IsSuspended;576};577 578class Parser {579public:580  Parser(StringRef TemplateStr, MustacheContext &Ctx)581      : Ctx(Ctx), TemplateStr(TemplateStr) {}582 583  AstPtr parse();584 585private:586  void parseMustache(ASTNode *Parent);587  void parseSection(ASTNode *Parent, ASTNode::Type Ty, const Accessor &A);588 589  MustacheContext &Ctx;590  SmallVector<Token> Tokens;591  size_t CurrentPtr;592  StringRef TemplateStr;593};594 595void Parser::parseSection(ASTNode *Parent, ASTNode::Type Ty,596                          const Accessor &A) {597  AstPtr CurrentNode = createNode(Ctx, Ty, A, Parent);598  size_t Start = CurrentPtr;599  parseMustache(CurrentNode);600  const size_t End = CurrentPtr - 1;601 602  size_t RawBodySize = 0;603  for (size_t I = Start; I < End; ++I)604    RawBodySize += Tokens[I].RawBody.size();605 606  SmallString<128> RawBody;607  RawBody.reserve(RawBodySize);608  for (std::size_t I = Start; I < End; ++I)609    RawBody += Tokens[I].RawBody;610 611  CurrentNode->setRawBody(Ctx.Saver.save(StringRef(RawBody)));612  Parent->addChild(CurrentNode);613}614 615AstPtr Parser::parse() {616  Tokens = tokenize(TemplateStr, Ctx);617  CurrentPtr = 0;618  AstPtr RootNode = createRootNode(Ctx);619  parseMustache(RootNode);620  return RootNode;621}622 623void Parser::parseMustache(ASTNode *Parent) {624 625  while (CurrentPtr < Tokens.size()) {626    Token CurrentToken = Tokens[CurrentPtr];627    CurrentPtr++;628    ArrayRef<StringRef> A = CurrentToken.getAccessor();629    AstPtr CurrentNode;630 631    switch (CurrentToken.getType()) {632    case Token::Type::Text: {633      CurrentNode = createTextNode(Ctx, CurrentToken.TokenBody, Parent);634      Parent->addChild(CurrentNode);635      break;636    }637    case Token::Type::Variable: {638      CurrentNode = createNode(Ctx, ASTNode::Variable, A, Parent);639      Parent->addChild(CurrentNode);640      break;641    }642    case Token::Type::UnescapeVariable: {643      CurrentNode = createNode(Ctx, ASTNode::UnescapeVariable, A, Parent);644      Parent->addChild(CurrentNode);645      break;646    }647    case Token::Type::Partial: {648      CurrentNode = createNode(Ctx, ASTNode::Partial, A, Parent);649      CurrentNode->setIndentation(CurrentToken.getIndentation());650      Parent->addChild(CurrentNode);651      break;652    }653    case Token::Type::SectionOpen: {654      parseSection(Parent, ASTNode::Section, A);655      break;656    }657    case Token::Type::InvertSectionOpen: {658      parseSection(Parent, ASTNode::InvertSection, A);659      break;660    }661    case Token::Type::Comment:662    case Token::Type::SetDelimiter:663      break;664    case Token::Type::SectionClose:665      return;666    }667  }668}669static void toMustacheString(const json::Value &Data, raw_ostream &OS) {670  LLVM_DEBUG(dbgs() << "[To Mustache String] Kind: "671                    << jsonKindToString(Data.kind()) << ", Data: " << Data672                    << "\n");673  switch (Data.kind()) {674  case json::Value::Null:675    return;676  case json::Value::Number: {677    auto Num = *Data.getAsNumber();678    std::ostringstream SS;679    SS << Num;680    OS << SS.str();681    return;682  }683  case json::Value::String: {684    OS << *Data.getAsString();685    return;686  }687 688  case json::Value::Array: {689    auto Arr = *Data.getAsArray();690    if (Arr.empty())691      return;692    [[fallthrough]];693  }694  case json::Value::Object:695  case json::Value::Boolean: {696    llvm::json::OStream JOS(OS, 2);697    JOS.value(Data);698    break;699  }700  }701}702 703void ASTNode::renderRoot(const json::Value &CurrentCtx,704                         MustacheOutputStream &OS) {705  renderChild(CurrentCtx, OS);706}707 708void ASTNode::renderText(MustacheOutputStream &OS) { OS << Body; }709 710void ASTNode::renderPartial(const json::Value &CurrentCtx,711                            MustacheOutputStream &OS) {712  LLVM_DEBUG(dbgs() << "[Render Partial] Accessor:" << AccessorValue[0]713                    << ", Indentation:" << Indentation << "\n");714  auto Partial = Ctx.Partials.find(AccessorValue[0]);715  if (Partial != Ctx.Partials.end())716    renderPartial(CurrentCtx, OS, Partial->getValue());717}718 719void ASTNode::renderVariable(const json::Value &CurrentCtx,720                             MustacheOutputStream &OS) {721  auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);722  if (Lambda != Ctx.Lambdas.end()) {723    renderLambdas(CurrentCtx, OS, Lambda->getValue());724  } else if (const json::Value *ContextPtr = findContext()) {725    EscapeStringStream ES(OS, Ctx.Escapes);726    toMustacheString(*ContextPtr, ES);727  }728}729 730void ASTNode::renderUnescapeVariable(const json::Value &CurrentCtx,731                                     MustacheOutputStream &OS) {732  LLVM_DEBUG(dbgs() << "[Render UnescapeVariable] Accessor:" << AccessorValue[0]733                    << "\n");734  auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);735  if (Lambda != Ctx.Lambdas.end()) {736    renderLambdas(CurrentCtx, OS, Lambda->getValue());737  } else if (const json::Value *ContextPtr = findContext()) {738    OS.suspendIndentation();739    toMustacheString(*ContextPtr, OS);740    OS.resumeIndentation();741  }742}743 744void ASTNode::renderSection(const json::Value &CurrentCtx,745                            MustacheOutputStream &OS) {746  auto SectionLambda = Ctx.SectionLambdas.find(AccessorValue[0]);747  if (SectionLambda != Ctx.SectionLambdas.end()) {748    renderSectionLambdas(CurrentCtx, OS, SectionLambda->getValue());749    return;750  }751 752  const json::Value *ContextPtr = findContext();753  if (isContextFalsey(ContextPtr))754    return;755 756  if (const json::Array *Arr = ContextPtr->getAsArray()) {757    for (const json::Value &V : *Arr)758      renderChild(V, OS);759    return;760  }761  renderChild(*ContextPtr, OS);762}763 764void ASTNode::renderInvertSection(const json::Value &CurrentCtx,765                                  MustacheOutputStream &OS) {766  bool IsLambda = Ctx.SectionLambdas.contains(AccessorValue[0]);767  const json::Value *ContextPtr = findContext();768  if (isContextFalsey(ContextPtr) && !IsLambda) {769    renderChild(CurrentCtx, OS);770  }771}772 773void ASTNode::render(const llvm::json::Value &Data, MustacheOutputStream &OS) {774  if (Ty != Root && Ty != Text && AccessorValue.empty())775    return;776  // Set the parent context to the incoming context so that we777  // can walk up the context tree correctly in findContext().778  ParentContext = &Data;779 780  switch (Ty) {781  case Root:782    renderRoot(Data, OS);783    return;784  case Text:785    renderText(OS);786    return;787  case Partial:788    renderPartial(Data, OS);789    return;790  case Variable:791    renderVariable(Data, OS);792    return;793  case UnescapeVariable:794    renderUnescapeVariable(Data, OS);795    return;796  case Section:797    renderSection(Data, OS);798    return;799  case InvertSection:800    renderInvertSection(Data, OS);801    return;802  }803  llvm_unreachable("Invalid ASTNode type");804}805 806const json::Value *ASTNode::findContext() {807  // The mustache spec allows for dot notation to access nested values808  // a single dot refers to the current context.809  // We attempt to find the JSON context in the current node, if it is not810  // found, then we traverse the parent nodes to find the context until we811  // reach the root node or the context is found.812  if (AccessorValue.empty())813    return nullptr;814  if (AccessorValue[0] == ".")815    return ParentContext;816 817  const json::Object *CurrentContext = ParentContext->getAsObject();818  StringRef CurrentAccessor = AccessorValue[0];819  ASTNode *CurrentParent = Parent;820 821  while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {822    if (CurrentParent->Ty != Root) {823      CurrentContext = CurrentParent->ParentContext->getAsObject();824      CurrentParent = CurrentParent->Parent;825      continue;826    }827    return nullptr;828  }829  const json::Value *Context = nullptr;830  for (auto [Idx, Acc] : enumerate(AccessorValue)) {831    const json::Value *CurrentValue = CurrentContext->get(Acc);832    if (!CurrentValue)833      return nullptr;834    if (Idx < AccessorValue.size() - 1) {835      CurrentContext = CurrentValue->getAsObject();836      if (!CurrentContext)837        return nullptr;838    } else {839      Context = CurrentValue;840    }841  }842  return Context;843}844 845void ASTNode::renderChild(const json::Value &Contexts,846                          MustacheOutputStream &OS) {847  for (ASTNode &Child : Children)848    Child.render(Contexts, OS);849}850 851void ASTNode::renderPartial(const json::Value &Contexts,852                            MustacheOutputStream &OS, ASTNode *Partial) {853  LLVM_DEBUG(dbgs() << "[Render Partial Indentation] Indentation: " << Indentation << "\n");854  AddIndentationStringStream IS(OS, Indentation);855  Partial->render(Contexts, IS);856}857 858void ASTNode::renderLambdas(const llvm::json::Value &Contexts,859                            MustacheOutputStream &OS, Lambda &L) {860  json::Value LambdaResult = L();861  std::string LambdaStr;862  raw_string_ostream Output(LambdaStr);863  toMustacheString(LambdaResult, Output);864  Parser P(LambdaStr, Ctx);865  AstPtr LambdaNode = P.parse();866 867  EscapeStringStream ES(OS, Ctx.Escapes);868  if (Ty == Variable) {869    LambdaNode->render(Contexts, ES);870    return;871  }872  LambdaNode->render(Contexts, OS);873}874 875void ASTNode::renderSectionLambdas(const llvm::json::Value &Contexts,876                                   MustacheOutputStream &OS, SectionLambda &L) {877  json::Value Return = L(RawBody.str());878  if (isFalsey(Return))879    return;880  std::string LambdaStr;881  raw_string_ostream Output(LambdaStr);882  toMustacheString(Return, Output);883  Parser P(LambdaStr, Ctx);884  AstPtr LambdaNode = P.parse();885  LambdaNode->render(Contexts, OS);886}887 888void Template::render(const llvm::json::Value &Data, llvm::raw_ostream &OS) {889  RawMustacheOutputStream MOS(OS);890  Tree->render(Data, MOS);891}892 893void Template::registerPartial(std::string Name, std::string Partial) {894  StringRef SavedPartial = Ctx.Saver.save(Partial);895  Parser P(SavedPartial, Ctx);896  AstPtr PartialTree = P.parse();897  Ctx.Partials.insert(std::make_pair(Name, PartialTree));898}899 900void Template::registerLambda(std::string Name, Lambda L) {901  Ctx.Lambdas[Name] = L;902}903 904void Template::registerLambda(std::string Name, SectionLambda L) {905  Ctx.SectionLambdas[Name] = L;906}907 908void Template::overrideEscapeCharacters(EscapeMap E) {909  Ctx.Escapes = std::move(E);910}911 912Template::Template(StringRef TemplateStr, MustacheContext &Ctx) : Ctx(Ctx) {913  Parser P(TemplateStr, Ctx);914  Tree = P.parse();915  // The default behavior is to escape html entities.916  const EscapeMap HtmlEntities = {{'&', "&amp;"},917                                  {'<', "&lt;"},918                                  {'>', "&gt;"},919                                  {'"', "&quot;"},920                                  {'\'', "&#39;"}};921  overrideEscapeCharacters(HtmlEntities);922}923 924Template::Template(Template &&Other) noexcept925    : Ctx(Other.Ctx), Tree(Other.Tree) {926  Other.Tree = nullptr;927}928 929Template::~Template() = default;930 931} // namespace llvm::mustache932 933#undef DEBUG_TYPE934