Since YAMLIO generally knows the types ahead of time (since it primarily functions to (de)serialize concrete C++ types) "tag resolution" isn't really a meaningful isssue. With MsgPackDocument we permit arbitrary documents with arbitrary type nodes, and so the YAML "NO" problem is an issue. Address this in MsgPackDocument itself to avoid a significant and far-reaching backwards-incompatible change to YAMLIO (although we could still consider tightening things up there in the future). Use the "JSON Schema" for tag resolution where the only literals to resolve to bool by default are "true" and "false".
260 lines
7.6 KiB
C++
260 lines
7.6 KiB
C++
//===-- 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();
|
|
}
|
|
|