================ @@ -0,0 +1,730 @@ +//===-- Mustache.cpp ------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/Mustache.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include <sstream> + +using namespace llvm; +using namespace llvm::json; + +namespace llvm { +namespace mustache { +namespace { + +class Token { +public: + enum class Type { + Text, + Variable, + Partial, + SectionOpen, + SectionClose, + InvertSectionOpen, + UnescapeVariable, + Comment, + }; + + Token(std::string Str); + + Token(std::string RawBody, std::string TokenBody, char Identifier); + + StringRef getTokenBody() const { return TokenBody; }; + + StringRef getRawBody() const { return RawBody; }; + + void setTokenBody(std::string NewBody) { TokenBody = std::move(NewBody); }; + + Accessor getAccessor() const { return Accessor; }; + + Type getType() const { return TokenType; }; + + void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }; + + size_t getIndentation() const { return Indentation; }; + + static Type getTokenType(char Identifier); + +private: + Type TokenType; + // RawBody is the original string that was tokenized + std::string RawBody; + // TokenBody is the original string with the identifier removed + std::string TokenBody; + Accessor Accessor; + size_t Indentation; +}; + +class ASTNode { +public: + enum Type { + Root, + Text, + Partial, + Variable, + UnescapeVariable, + Section, + InvertSection, + }; + + ASTNode() : T(Type::Root), ParentContext(nullptr) {}; + + ASTNode(StringRef Body, ASTNode *Parent) + : T(Type::Text), Body(Body), Parent(Parent), ParentContext(nullptr) {}; + + // Constructor for Section/InvertSection/Variable/UnescapeVariable + ASTNode(Type T, Accessor Accessor, ASTNode *Parent) + : T(T), Parent(Parent), Children({}), Accessor(Accessor), + ParentContext(nullptr) {}; + + void addChild(ASTNode *Child) { Children.emplace_back(Child); }; + + void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); }; + + void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; }; + + void render(const llvm::json::Value &Data, llvm::raw_ostream &OS); + + void setUpNode(llvm::BumpPtrAllocator &Alloc, StringMap<ASTNode *> &Partials, + StringMap<Lambda> &Lambdas, + StringMap<SectionLambda> &SectionLambdas, + DenseMap<char, std::string> &Escapes); + +private: + void renderLambdas(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, + Lambda &L); + + void renderSectionLambdas(const llvm::json::Value &Contexts, + llvm::raw_ostream &OS, SectionLambda &L); + + void renderPartial(const llvm::json::Value &Contexts, llvm::raw_ostream &OS, + ASTNode *Partial); + + void renderChild(const llvm::json::Value &Context, llvm::raw_ostream &OS); + + const llvm::json::Value *findContext(); + + llvm::BumpPtrAllocator *Allocator; + StringMap<ASTNode *> *Partials; + StringMap<Lambda> *Lambdas; + StringMap<SectionLambda> *SectionLambdas; + DenseMap<char, std::string> *Escapes; + Type T; + size_t Indentation = 0; + std::string RawBody; + std::string Body; + ASTNode *Parent; + // TODO: switch implementation to SmallVector<T> + std::vector<ASTNode *> Children; + const Accessor Accessor; + const llvm::json::Value *ParentContext; +}; + +// Custom stream to escape strings +class EscapeStringStream : public raw_ostream { +public: + explicit EscapeStringStream(llvm::raw_ostream &WrappedStream, + DenseMap<char, std::string> &Escape) + : Escape(Escape), WrappedStream(WrappedStream) { + SetUnbuffered(); + } + +protected: + void write_impl(const char *Ptr, size_t Size) override { + llvm::StringRef Data(Ptr, Size); + for (char C : Data) { + auto It = Escape.find(C); + if (It != Escape.end()) + WrappedStream << It->getSecond(); + else + WrappedStream << C; + } + } + + uint64_t current_pos() const override { return WrappedStream.tell(); } + +private: + DenseMap<char, std::string> &Escape; + llvm::raw_ostream &WrappedStream; +}; + +// Custom stream to add indentation used to for rendering partials +class AddIndentationStringStream : public raw_ostream { +public: + explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream, + size_t Indentation) + : Indentation(Indentation), WrappedStream(WrappedStream) { + SetUnbuffered(); + } + +protected: + void write_impl(const char *Ptr, size_t Size) override { + llvm::StringRef Data(Ptr, Size); + std::string Indent(Indentation, ' '); + for (char C : Data) { + WrappedStream << C; + if (C == '\n') + WrappedStream << Indent; + } + } + + uint64_t current_pos() const override { return WrappedStream.tell(); } + +private: + size_t Indentation; + llvm::raw_ostream &WrappedStream; +}; + +Accessor splitMustacheString(StringRef Str) { + // We split the mustache string into an accessor + // For example: "a.b.c" would be split into {"a", "b", "c"} + // We make an exception for a single dot which + // refers to the current context + Accessor Tokens; + if (Str == ".") { + Tokens.emplace_back(Str); + return Tokens; + } + StringRef Ref(Str); + while (!Ref.empty()) { + StringRef Part; + std::tie(Part, Ref) = Ref.split("."); + Tokens.emplace_back(Part.trim()); + } + return Tokens; +} + +Token::Token(std::string RawBody, std::string TokenBody, char Identifier) + : RawBody(std::move(RawBody)), TokenBody(std::move(TokenBody)), + Indentation(0) { + TokenType = getTokenType(Identifier); + if (TokenType == Type::Comment) + return; + + std::string AccessorStr = + TokenType == Type::Variable ? this->TokenBody : this->TokenBody.substr(1); + + Accessor = splitMustacheString(StringRef(AccessorStr).trim()); +} + +Token::Token(std::string Str) + : TokenType(Type::Text), RawBody(std::move(Str)), Accessor({}), + TokenBody(RawBody), Indentation(0) {} + +Token::Type Token::getTokenType(char Identifier) { + switch (Identifier) { + case '#': + return Type::SectionOpen; + case '/': + return Type::SectionClose; + case '^': + return Type::InvertSectionOpen; + case '!': + return Type::Comment; + case '>': + return Type::Partial; + case '&': + return Type::UnescapeVariable; + default: + return Type::Variable; + } +} + +// Function to check if there is meaningful text behind. +// We determine if a token has meaningful text behind +// if the right of previous token contains anything that is +// not a newline +// For example: +// "Stuff {{#Section}}" (returns true) +// vs +// "{{#Section}} \n" (returns false) +// We make an exception for when previous token is empty +// and the current token is the second token +// For example: "{{#Section}}" ---------------- ilovepi wrote:
```suggestion // Function to check if there is meaningful text behind. // We determine if a token has meaningful text behind // if the right of previous token contains anything that is // not a newline. // For example: // "Stuff {{#Section}}" (returns true) // vs // "{{#Section}} \n" (returns false) // We make an exception for when previous token is empty // and the current token is the second token. // For example: "{{#Section}}" ``` https://github.com/llvm/llvm-project/pull/105893 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits