| //===-- MsgPackDocumentYAML.cpp - MsgPack Document YAML interface -------*-===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| /// This file implements YAMLIO on a msgpack::Document. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/BinaryFormat/MsgPackDocument.h" | |
| #include "llvm/Support/YAMLTraits.h" | |
| using namespace llvm; | |
| using namespace msgpack; | |
| namespace { | |
| // Struct used to represent scalar node. (MapDocNode and ArrayDocNode already | |
| // exist in MsgPackDocument.h.) | |
| struct ScalarDocNode : DocNode { | |
| ScalarDocNode(DocNode N) : DocNode(N) {} | |
| /// Get the YAML tag for this ScalarDocNode. This normally returns ""; it only | |
| /// returns something else if the result of toString would be ambiguous, e.g. | |
| /// a string that parses as a number or boolean. | |
| StringRef getYAMLTag() const; | |
| }; | |
| bool isJSONSchemaBoolLiteral(StringRef S) { | |
| return S == "true" || S == "false"; | |
| } | |
| } // namespace | |
| /// Convert this DocNode to a string, assuming it is scalar. | |
| std::string DocNode::toString() const { | |
| std::string S; | |
| raw_string_ostream OS(S); | |
| switch (getKind()) { | |
| case msgpack::Type::String: | |
| OS << Raw; | |
| break; | |
| case msgpack::Type::Nil: | |
| break; | |
| case msgpack::Type::Boolean: | |
| OS << (Bool ? "true" : "false"); | |
| break; | |
| case msgpack::Type::Int: | |
| OS << Int; | |
| break; | |
| case msgpack::Type::UInt: | |
| if (getDocument()->getHexMode()) | |
| OS << format("%#llx", (unsigned long long)UInt); | |
| else | |
| OS << UInt; | |
| break; | |
| case msgpack::Type::Float: | |
| OS << Float; | |
| break; | |
| default: | |
| llvm_unreachable("not scalar"); | |
| break; | |
| } | |
| return OS.str(); | |
| } | |
| /// 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 DocNode::fromString(StringRef S, StringRef Tag) { | |
| if (Tag == "tag:yaml.org,2002:str") | |
| Tag = ""; | |
| if (Tag == "!int" || Tag == "") { | |
| // Try unsigned int then signed int. | |
| *this = getDocument()->getNode(uint64_t(0)); | |
| StringRef Err = yaml::ScalarTraits<uint64_t>::input(S, nullptr, getUInt()); | |
| if (Err != "") { | |
| *this = getDocument()->getNode(int64_t(0)); | |
| Err = yaml::ScalarTraits<int64_t>::input(S, nullptr, getInt()); | |
| } | |
| if (Err == "" || Tag != "") | |
| return Err; | |
| } | |
| if (Tag == "!nil") { | |
| *this = getDocument()->getNode(); | |
| return ""; | |
| } | |
| if (Tag == "!bool") { | |
| *this = getDocument()->getNode(false); | |
| return yaml::ScalarTraits<bool>::input(S, nullptr, getBool()); | |
| } | |
| // FIXME: This enforces the "JSON Schema" tag resolution for boolean literals, | |
| // defined at https://yaml.org/spec/1.2.2/#json-schema which we adopt because | |
| // the more general pre-1.2 resolution includes many common strings (e.g. "n", | |
| // "no", "y", "yes", ...). This should be handled at the YAMLTraits level, but | |
| // that change would have a much broader impact. | |
| if (Tag == "" && isJSONSchemaBoolLiteral(S)) { | |
| *this = getDocument()->getNode(S == "true"); | |
| return ""; | |
| } | |
| if (Tag == "!float" || Tag == "") { | |
| *this = getDocument()->getNode(0.0); | |
| StringRef Err = yaml::ScalarTraits<double>::input(S, nullptr, getFloat()); | |
| if (Err == "" || Tag != "") | |
| return Err; | |
| } | |
| assert((Tag == "!str" || Tag == "") && "unsupported tag"); | |
| std::string V; | |
| StringRef Err = yaml::ScalarTraits<std::string>::input(S, nullptr, V); | |
| if (Err == "") | |
| *this = getDocument()->getNode(V, /*Copy=*/true); | |
| return Err; | |
| } | |
| /// Get the YAML tag for this ScalarDocNode. This normally returns ""; it only | |
| /// returns something else if the result of toString would be ambiguous, e.g. | |
| /// a string that parses as a number or boolean. | |
| StringRef ScalarDocNode::getYAMLTag() const { | |
| if (getKind() == msgpack::Type::Nil) | |
| return "!nil"; | |
| // Try converting both ways and see if we get the same kind. If not, we need | |
| // a tag. | |
| ScalarDocNode N = getDocument()->getNode(); | |
| N.fromString(toString(), ""); | |
| if (N.getKind() == getKind()) | |
| return ""; | |
| // Tolerate signedness of int changing, as tags do not differentiate between | |
| // them anyway. | |
| if (N.getKind() == msgpack::Type::UInt && getKind() == msgpack::Type::Int) | |
| return ""; | |
| if (N.getKind() == msgpack::Type::Int && getKind() == msgpack::Type::UInt) | |
| return ""; | |
| // We do need a tag. | |
| switch (getKind()) { | |
| case msgpack::Type::String: | |
| return "!str"; | |
| case msgpack::Type::Int: | |
| return "!int"; | |
| case msgpack::Type::UInt: | |
| return "!int"; | |
| case msgpack::Type::Boolean: | |
| return "!bool"; | |
| case msgpack::Type::Float: | |
| return "!float"; | |
| default: | |
| llvm_unreachable("unrecognized kind"); | |
| } | |
| } | |
| namespace llvm { | |
| namespace yaml { | |
| /// YAMLIO for DocNode | |
| template <> struct PolymorphicTraits<DocNode> { | |
| static NodeKind getKind(const DocNode &N) { | |
| switch (N.getKind()) { | |
| case msgpack::Type::Map: | |
| return NodeKind::Map; | |
| case msgpack::Type::Array: | |
| return NodeKind::Sequence; | |
| default: | |
| return NodeKind::Scalar; | |
| } | |
| } | |
| static MapDocNode &getAsMap(DocNode &N) { return N.getMap(/*Convert=*/true); } | |
| static ArrayDocNode &getAsSequence(DocNode &N) { | |
| N.getArray(/*Convert=*/true); | |
| return *static_cast<ArrayDocNode *>(&N); | |
| } | |
| static ScalarDocNode &getAsScalar(DocNode &N) { | |
| return *static_cast<ScalarDocNode *>(&N); | |
| } | |
| }; | |
| /// YAMLIO for ScalarDocNode | |
| template <> struct TaggedScalarTraits<ScalarDocNode> { | |
| static void output(const ScalarDocNode &S, void *Ctxt, raw_ostream &OS, | |
| raw_ostream &TagOS) { | |
| TagOS << S.getYAMLTag(); | |
| OS << S.toString(); | |
| } | |
| static StringRef input(StringRef Str, StringRef Tag, void *Ctxt, | |
| ScalarDocNode &S) { | |
| return S.fromString(Str, Tag); | |
| } | |
| static QuotingType mustQuote(const ScalarDocNode &S, StringRef ScalarStr) { | |
| switch (S.getKind()) { | |
| case Type::Int: | |
| return ScalarTraits<int64_t>::mustQuote(ScalarStr); | |
| case Type::UInt: | |
| return ScalarTraits<uint64_t>::mustQuote(ScalarStr); | |
| case Type::Nil: | |
| return ScalarTraits<StringRef>::mustQuote(ScalarStr); | |
| case Type::Boolean: | |
| return ScalarTraits<bool>::mustQuote(ScalarStr); | |
| case Type::Float: | |
| return ScalarTraits<double>::mustQuote(ScalarStr); | |
| case Type::Binary: | |
| case Type::String: | |
| return ScalarTraits<std::string>::mustQuote(ScalarStr); | |
| default: | |
| llvm_unreachable("unrecognized ScalarKind"); | |
| } | |
| } | |
| }; | |
| /// YAMLIO for MapDocNode | |
| template <> struct CustomMappingTraits<MapDocNode> { | |
| static void inputOne(IO &IO, StringRef Key, MapDocNode &M) { | |
| ScalarDocNode KeyObj = M.getDocument()->getNode(); | |
| KeyObj.fromString(Key, ""); | |
| IO.mapRequired(Key, M.getMap()[KeyObj]); | |
| } | |
| static void output(IO &IO, MapDocNode &M) { | |
| for (auto I : M.getMap()) { | |
| IO.mapRequired(I.first.toString(), I.second); | |
| } | |
| } | |
| }; | |
| /// YAMLIO for ArrayNode | |
| template <> struct SequenceTraits<ArrayDocNode> { | |
| static size_t size(IO &IO, ArrayDocNode &A) { return A.size(); } | |
| static DocNode &element(IO &IO, ArrayDocNode &A, size_t Index) { | |
| return A[Index]; | |
| } | |
| }; | |
| } // namespace yaml | |
| } // namespace llvm | |
| /// Convert MsgPack Document to YAML text. | |
| void msgpack::Document::toYAML(raw_ostream &OS) { | |
| yaml::Output Yout(OS); | |
| Yout << getRoot(); | |
| } | |
| /// Read YAML text into the MsgPack document. Returns false on failure. | |
| bool msgpack::Document::fromYAML(StringRef S) { | |
| clear(); | |
| yaml::Input Yin(S); | |
| Yin >> getRoot(); | |
| return !Yin.error(); | |
| } | |