| //===-- MsgPackDocument.h - MsgPack Document --------------------*- C++ -*-===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| /// \file |
| /// This file declares a class that exposes a simple in-memory representation |
| /// of a document of MsgPack objects, that can be read from MsgPack, written to |
| /// MsgPack, and inspected and modified in memory. This is intended to be a |
| /// lighter-weight (in terms of memory allocations) replacement for |
| /// MsgPackTypes. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_BINARYFORMAT_MSGPACKDOCUMENT_H |
| #define LLVM_BINARYFORMAT_MSGPACKDOCUMENT_H |
| |
| #include "llvm/BinaryFormat/MsgPackReader.h" |
| #include <map> |
| |
| namespace llvm { |
| namespace msgpack { |
| |
| class ArrayDocNode; |
| class Document; |
| class MapDocNode; |
| |
| /// The kind of a DocNode and its owning Document. |
| struct KindAndDocument { |
| Document *Doc; |
| Type Kind; |
| }; |
| |
| /// A node in a MsgPack Document. This is a simple copyable and |
| /// passable-by-value type that does not own any memory. |
| class DocNode { |
| friend Document; |
| |
| public: |
| typedef std::map<DocNode, DocNode> MapTy; |
| typedef std::vector<DocNode> ArrayTy; |
| |
| private: |
| // Using KindAndDocument allows us to squeeze Kind and a pointer to the |
| // owning Document into the same word. Having a pointer to the owning |
| // Document makes the API of DocNode more convenient, and allows its use in |
| // YAMLIO. |
| const KindAndDocument *KindAndDoc; |
| |
| protected: |
| // The union of different values. |
| union { |
| int64_t Int; |
| uint64_t UInt; |
| bool Bool; |
| double Float; |
| StringRef Raw; |
| ArrayTy *Array; |
| MapTy *Map; |
| }; |
| |
| public: |
| // Default constructor gives an empty node with no associated Document. All |
| // you can do with it is "isEmpty()". |
| DocNode() : KindAndDoc(nullptr) {} |
| |
| // Type methods |
| bool isMap() const { return getKind() == Type::Map; } |
| bool isArray() const { return getKind() == Type::Array; } |
| bool isScalar() const { return !isMap() && !isArray(); } |
| bool isString() const { return getKind() == Type::String; } |
| |
| // Accessors. isEmpty() returns true for both a default-constructed DocNode |
| // that has no associated Document, and the result of getEmptyNode(), which |
| // does have an associated document. |
| bool isEmpty() const { return !KindAndDoc || getKind() == Type::Empty; } |
| Type getKind() const { return KindAndDoc->Kind; } |
| Document *getDocument() const { return KindAndDoc->Doc; } |
| |
| int64_t &getInt() { |
| assert(getKind() == Type::Int); |
| return Int; |
| } |
| |
| uint64_t &getUInt() { |
| assert(getKind() == Type::UInt); |
| return UInt; |
| } |
| |
| bool &getBool() { |
| assert(getKind() == Type::Boolean); |
| return Bool; |
| } |
| |
| double &getFloat() { |
| assert(getKind() == Type::Float); |
| return Float; |
| } |
| |
| int64_t getInt() const { |
| assert(getKind() == Type::Int); |
| return Int; |
| } |
| |
| uint64_t getUInt() const { |
| assert(getKind() == Type::UInt); |
| return UInt; |
| } |
| |
| bool getBool() const { |
| assert(getKind() == Type::Boolean); |
| return Bool; |
| } |
| |
| double getFloat() const { |
| assert(getKind() == Type::Float); |
| return Float; |
| } |
| |
| StringRef getString() const { |
| assert(getKind() == Type::String); |
| return Raw; |
| } |
| |
| /// Get an ArrayDocNode for an array node. If Convert, convert the node to an |
| /// array node if necessary. |
| ArrayDocNode &getArray(bool Convert = false) { |
| if (getKind() != Type::Array) { |
| assert(Convert); |
| convertToArray(); |
| } |
| // This could be a static_cast, except ArrayDocNode is a forward reference. |
| return *reinterpret_cast<ArrayDocNode *>(this); |
| } |
| |
| /// Get a MapDocNode for a map node. If Convert, convert the node to a map |
| /// node if necessary. |
| MapDocNode &getMap(bool Convert = false) { |
| if (getKind() != Type::Map) { |
| assert(Convert); |
| convertToMap(); |
| } |
| // This could be a static_cast, except MapDocNode is a forward reference. |
| return *reinterpret_cast<MapDocNode *>(this); |
| } |
| |
| /// Comparison operator, used for map keys. |
| friend bool operator<(const DocNode &Lhs, const DocNode &Rhs) { |
| // This has to cope with one or both of the nodes being default-constructed, |
| // such that KindAndDoc is not set. |
| if (Rhs.isEmpty()) |
| return false; |
| if (Lhs.KindAndDoc != Rhs.KindAndDoc) { |
| if (Lhs.isEmpty()) |
| return true; |
| return (unsigned)Lhs.getKind() < (unsigned)Rhs.getKind(); |
| } |
| switch (Lhs.getKind()) { |
| case Type::Int: |
| return Lhs.Int < Rhs.Int; |
| case Type::UInt: |
| return Lhs.UInt < Rhs.UInt; |
| case Type::Nil: |
| return false; |
| case Type::Boolean: |
| return Lhs.Bool < Rhs.Bool; |
| case Type::Float: |
| return Lhs.Float < Rhs.Float; |
| case Type::String: |
| case Type::Binary: |
| return Lhs.Raw < Rhs.Raw; |
| default: |
| llvm_unreachable("bad map key type"); |
| } |
| } |
| |
| /// Equality operator |
| friend bool operator==(const DocNode &Lhs, const DocNode &Rhs) { |
| return !(Lhs < Rhs) && !(Rhs < Lhs); |
| } |
| |
| /// Inequality operator |
| friend bool operator!=(const DocNode &Lhs, const DocNode &Rhs) { |
| return !(Lhs == Rhs); |
| } |
| |
| /// Convert this node to a string, assuming it is scalar. |
| std::string toString() const; |
| |
| /// Convert the StringRef and use it to set this DocNode (assuming scalar). If |
| /// it is a string, copy the string into the Document's strings list so we do |
| /// not rely on S having a lifetime beyond this call. Tag is "" or a YAML tag. |
| StringRef fromString(StringRef S, StringRef Tag = ""); |
| |
| /// Convenience assignment operators. This only works if the destination |
| /// DocNode has an associated Document, i.e. it was not constructed using the |
| /// default constructor. The string one does not copy, so the string must |
| /// remain valid for the lifetime of the Document. Use fromString to avoid |
| /// that restriction. |
| DocNode &operator=(const char *Val) { return *this = StringRef(Val); } |
| DocNode &operator=(StringRef Val); |
| DocNode &operator=(bool Val); |
| DocNode &operator=(int Val); |
| DocNode &operator=(unsigned Val); |
| DocNode &operator=(int64_t Val); |
| DocNode &operator=(uint64_t Val); |
| |
| private: |
| // Private constructor setting KindAndDoc, used by methods in Document. |
| DocNode(const KindAndDocument *KindAndDoc) : KindAndDoc(KindAndDoc) {} |
| |
| void convertToArray(); |
| void convertToMap(); |
| }; |
| |
| /// A DocNode that is a map. |
| class MapDocNode : public DocNode { |
| public: |
| MapDocNode() {} |
| MapDocNode(DocNode &N) : DocNode(N) { assert(getKind() == Type::Map); } |
| |
| // Map access methods. |
| size_t size() const { return Map->size(); } |
| bool empty() const { return !size(); } |
| MapTy::iterator begin() { return Map->begin(); } |
| MapTy::iterator end() { return Map->end(); } |
| MapTy::iterator find(DocNode Key) { return Map->find(Key); } |
| MapTy::iterator find(StringRef Key); |
| MapTy::iterator erase(MapTy::const_iterator I) { return Map->erase(I); } |
| size_t erase(DocNode Key) { return Map->erase(Key); } |
| MapTy::iterator erase(MapTy::const_iterator First, |
| MapTy::const_iterator Second) { |
| return Map->erase(First, Second); |
| } |
| /// Member access. The string data must remain valid for the lifetime of the |
| /// Document. |
| DocNode &operator[](StringRef S); |
| /// Member access, with convenience versions for an integer key. |
| DocNode &operator[](DocNode Key); |
| DocNode &operator[](int Key); |
| DocNode &operator[](unsigned Key); |
| DocNode &operator[](int64_t Key); |
| DocNode &operator[](uint64_t Key); |
| }; |
| |
| /// A DocNode that is an array. |
| class ArrayDocNode : public DocNode { |
| public: |
| ArrayDocNode() {} |
| ArrayDocNode(DocNode &N) : DocNode(N) { assert(getKind() == Type::Array); } |
| |
| // Array access methods. |
| size_t size() const { return Array->size(); } |
| bool empty() const { return !size(); } |
| DocNode &back() const { return Array->back(); } |
| ArrayTy::iterator begin() { return Array->begin(); } |
| ArrayTy::iterator end() { return Array->end(); } |
| void push_back(DocNode N) { |
| assert(N.isEmpty() || N.getDocument() == getDocument()); |
| Array->push_back(N); |
| } |
| |
| /// Element access. This extends the array if necessary, with empty nodes. |
| DocNode &operator[](size_t Index); |
| }; |
| |
| /// Simple in-memory representation of a document of msgpack objects with |
| /// ability to find and create array and map elements. Does not currently cope |
| /// with any extension types. |
| class Document { |
| // Maps, arrays and strings used by nodes in the document. No attempt is made |
| // to free unused ones. |
| std::vector<std::unique_ptr<DocNode::MapTy>> Maps; |
| std::vector<std::unique_ptr<DocNode::ArrayTy>> Arrays; |
| std::vector<std::unique_ptr<char[]>> Strings; |
| |
| // The root node of the document. |
| DocNode Root; |
| |
| // The KindAndDocument structs pointed to by nodes in the document. |
| KindAndDocument KindAndDocs[size_t(Type::Empty) + 1]; |
| |
| // Whether YAML output uses hex for UInt. |
| bool HexMode = false; |
| |
| public: |
| Document() { |
| clear(); |
| for (unsigned T = 0; T != unsigned(Type::Empty) + 1; ++T) |
| KindAndDocs[T] = {this, Type(T)}; |
| } |
| |
| /// Get ref to the document's root element. |
| DocNode &getRoot() { return Root; } |
| |
| /// Restore the Document to an empty state. |
| void clear() { getRoot() = getEmptyNode(); } |
| |
| /// Create an empty node associated with this Document. |
| DocNode getEmptyNode() { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Empty)]); |
| return N; |
| } |
| |
| /// Create a nil node associated with this Document. |
| DocNode getNode() { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Nil)]); |
| return N; |
| } |
| |
| /// Create an Int node associated with this Document. |
| DocNode getNode(int64_t V) { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Int)]); |
| N.Int = V; |
| return N; |
| } |
| |
| /// Create an Int node associated with this Document. |
| DocNode getNode(int V) { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Int)]); |
| N.Int = V; |
| return N; |
| } |
| |
| /// Create a UInt node associated with this Document. |
| DocNode getNode(uint64_t V) { |
| auto N = DocNode(&KindAndDocs[size_t(Type::UInt)]); |
| N.UInt = V; |
| return N; |
| } |
| |
| /// Create a UInt node associated with this Document. |
| DocNode getNode(unsigned V) { |
| auto N = DocNode(&KindAndDocs[size_t(Type::UInt)]); |
| N.UInt = V; |
| return N; |
| } |
| |
| /// Create a Boolean node associated with this Document. |
| DocNode getNode(bool V) { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Boolean)]); |
| N.Bool = V; |
| return N; |
| } |
| |
| /// Create a Float node associated with this Document. |
| DocNode getNode(double V) { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Float)]); |
| N.Float = V; |
| return N; |
| } |
| |
| /// Create a String node associated with this Document. If !Copy, the passed |
| /// string must remain valid for the lifetime of the Document. |
| DocNode getNode(StringRef V, bool Copy = false) { |
| if (Copy) |
| V = addString(V); |
| auto N = DocNode(&KindAndDocs[size_t(Type::String)]); |
| N.Raw = V; |
| return N; |
| } |
| |
| /// Create a String node associated with this Document. If !Copy, the passed |
| /// string must remain valid for the lifetime of the Document. |
| DocNode getNode(const char *V, bool Copy = false) { |
| return getNode(StringRef(V), Copy); |
| } |
| |
| /// Create an empty Map node associated with this Document. |
| MapDocNode getMapNode() { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Map)]); |
| Maps.push_back(std::unique_ptr<DocNode::MapTy>(new DocNode::MapTy)); |
| N.Map = Maps.back().get(); |
| return N.getMap(); |
| } |
| |
| /// Create an empty Array node associated with this Document. |
| ArrayDocNode getArrayNode() { |
| auto N = DocNode(&KindAndDocs[size_t(Type::Array)]); |
| Arrays.push_back(std::unique_ptr<DocNode::ArrayTy>(new DocNode::ArrayTy)); |
| N.Array = Arrays.back().get(); |
| return N.getArray(); |
| } |
| |
| /// Read a document from a binary msgpack blob, merging into anything already |
| /// in the Document. The blob data must remain valid for the lifetime of this |
| /// Document (because a string object in the document contains a StringRef |
| /// into the original blob). If Multi, then this sets root to an array and |
| /// adds top-level objects to it. If !Multi, then it only reads a single |
| /// top-level object, even if there are more, and sets root to that. Returns |
| /// false if failed due to illegal format or merge error. |
| /// |
| /// The Merger arg is a callback function that is called when the merge has a |
| /// conflict, that is, it is trying to set an item that is already set. If the |
| /// conflict cannot be resolved, the callback function returns -1. If the |
| /// conflict can be resolved, the callback returns a non-negative number and |
| /// sets *DestNode to the resolved node. The returned non-negative number is |
| /// significant only for an array node; it is then the array index to start |
| /// populating at. That allows Merger to choose whether to merge array |
| /// elements (returns 0) or append new elements (returns existing size). |
| /// |
| /// If SrcNode is an array or map, the resolution must be that *DestNode is an |
| /// array or map respectively, although it could be the array or map |
| /// (respectively) that was already there. MapKey is the key if *DestNode is a |
| /// map entry, a nil node otherwise. |
| /// |
| /// The default for Merger is to disallow any conflict. |
| bool readFromBlob( |
| StringRef Blob, bool Multi, |
| function_ref<int(DocNode *DestNode, DocNode SrcNode, DocNode MapKey)> |
| Merger = [](DocNode *DestNode, DocNode SrcNode, DocNode MapKey) { |
| return -1; |
| }); |
| |
| /// Write a MsgPack document to a binary MsgPack blob. |
| void writeToBlob(std::string &Blob); |
| |
| /// Copy a string into the Document's strings list, and return the copy that |
| /// is owned by the Document. |
| StringRef addString(StringRef S) { |
| Strings.push_back(std::unique_ptr<char[]>(new char[S.size()])); |
| memcpy(&Strings.back()[0], S.data(), S.size()); |
| return StringRef(&Strings.back()[0], S.size()); |
| } |
| |
| /// Set whether YAML output uses hex for UInt. Default off. |
| void setHexMode(bool Val = true) { HexMode = Val; } |
| |
| /// Get Hexmode flag. |
| bool getHexMode() const { return HexMode; } |
| |
| /// Convert MsgPack Document to YAML text. |
| void toYAML(raw_ostream &OS); |
| |
| /// Read YAML text into the MsgPack document. Returns false on failure. |
| bool fromYAML(StringRef S); |
| }; |
| |
| } // namespace msgpack |
| } // namespace llvm |
| |
| #endif // LLVM_BINARYFORMAT_MSGPACKDOCUMENT_H |