1 //===-- MsgPackDocumentYAML.cpp - MsgPack Document YAML interface -------*-===//
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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 /// This file implements YAMLIO on a msgpack::Document.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "llvm/BinaryFormat/MsgPackDocument.h"
14 #include "llvm/Support/YAMLTraits.h"
15 
16 using namespace llvm;
17 using namespace msgpack;
18 
19 namespace {
20 
21 // Struct used to represent scalar node. (MapDocNode and ArrayDocNode already
22 // exist in MsgPackDocument.h.)
23 struct ScalarDocNode : DocNode {
24   ScalarDocNode(DocNode N) : DocNode(N) {}
25 
26   /// Get the YAML tag for this ScalarDocNode. This normally returns ""; it only
27   /// returns something else if the result of toString would be ambiguous, e.g.
28   /// a string that parses as a number or boolean.
29   StringRef getYAMLTag() const;
30 };
31 
32 } // namespace
33 
34 /// Convert this DocNode to a string, assuming it is scalar.
35 std::string DocNode::toString() const {
36   std::string S;
37   raw_string_ostream OS(S);
38   switch (getKind()) {
39   case msgpack::Type::String:
40     OS << Raw;
41     break;
42   case msgpack::Type::Nil:
43     break;
44   case msgpack::Type::Boolean:
45     OS << (Bool ? "true" : "false");
46     break;
47   case msgpack::Type::Int:
48     OS << Int;
49     break;
50   case msgpack::Type::UInt:
51     if (getDocument()->getHexMode())
52       OS << format("%#llx", (unsigned long long)UInt);
53     else
54       OS << UInt;
55     break;
56   case msgpack::Type::Float:
57     OS << Float;
58     break;
59   default:
60     llvm_unreachable("not scalar");
61     break;
62   }
63   return OS.str();
64 }
65 
66 /// Convert the StringRef and use it to set this DocNode (assuming scalar). If
67 /// it is a string, copy the string into the Document's strings list so we do
68 /// not rely on S having a lifetime beyond this call. Tag is "" or a YAML tag.
69 StringRef DocNode::fromString(StringRef S, StringRef Tag) {
70   if (Tag == "tag:yaml.org,2002:str")
71     Tag = "";
72   if (Tag == "!int" || Tag == "") {
73     // Try unsigned int then signed int.
74     *this = getDocument()->getNode(uint64_t(0));
75     StringRef Err = yaml::ScalarTraits<uint64_t>::input(S, nullptr, getUInt());
76     if (Err != "") {
77       *this = getDocument()->getNode(int64_t(0));
78       Err = yaml::ScalarTraits<int64_t>::input(S, nullptr, getInt());
79     }
80     if (Err == "" || Tag != "")
81       return Err;
82   }
83   if (Tag == "!nil") {
84     *this = getDocument()->getNode();
85     return "";
86   }
87   if (Tag == "!bool" || Tag == "") {
88     *this = getDocument()->getNode(false);
89     StringRef Err = yaml::ScalarTraits<bool>::input(S, nullptr, getBool());
90     if (Err == "" || Tag != "")
91       return Err;
92   }
93   if (Tag == "!float" || Tag == "") {
94     *this = getDocument()->getNode(0.0);
95     StringRef Err = yaml::ScalarTraits<double>::input(S, nullptr, getFloat());
96     if (Err == "" || Tag != "")
97       return Err;
98   }
99   assert((Tag == "!str" || Tag == "") && "unsupported tag");
100   std::string V;
101   StringRef Err = yaml::ScalarTraits<std::string>::input(S, nullptr, V);
102   if (Err == "")
103     *this = getDocument()->getNode(V, /*Copy=*/true);
104   return Err;
105 }
106 
107 /// Get the YAML tag for this ScalarDocNode. This normally returns ""; it only
108 /// returns something else if the result of toString would be ambiguous, e.g.
109 /// a string that parses as a number or boolean.
110 StringRef ScalarDocNode::getYAMLTag() const {
111   if (getKind() == msgpack::Type::Nil)
112     return "!nil";
113   // Try converting both ways and see if we get the same kind. If not, we need
114   // a tag.
115   ScalarDocNode N = getDocument()->getNode();
116   N.fromString(toString(), "");
117   if (N.getKind() == getKind())
118     return "";
119   // Tolerate signedness of int changing, as tags do not differentiate between
120   // them anyway.
121   if (N.getKind() == msgpack::Type::UInt && getKind() == msgpack::Type::Int)
122     return "";
123   if (N.getKind() == msgpack::Type::Int && getKind() == msgpack::Type::UInt)
124     return "";
125   // We do need a tag.
126   switch (getKind()) {
127   case msgpack::Type::String:
128     return "!str";
129   case msgpack::Type::Int:
130     return "!int";
131   case msgpack::Type::UInt:
132     return "!int";
133   case msgpack::Type::Boolean:
134     return "!bool";
135   case msgpack::Type::Float:
136     return "!float";
137   default:
138     llvm_unreachable("unrecognized kind");
139   }
140 }
141 
142 namespace llvm {
143 namespace yaml {
144 
145 /// YAMLIO for DocNode
146 template <> struct PolymorphicTraits<DocNode> {
147 
148   static NodeKind getKind(const DocNode &N) {
149     switch (N.getKind()) {
150     case msgpack::Type::Map:
151       return NodeKind::Map;
152     case msgpack::Type::Array:
153       return NodeKind::Sequence;
154     default:
155       return NodeKind::Scalar;
156     }
157   }
158 
159   static MapDocNode &getAsMap(DocNode &N) { return N.getMap(/*Convert=*/true); }
160 
161   static ArrayDocNode &getAsSequence(DocNode &N) {
162     N.getArray(/*Convert=*/true);
163     return *static_cast<ArrayDocNode *>(&N);
164   }
165 
166   static ScalarDocNode &getAsScalar(DocNode &N) {
167     return *static_cast<ScalarDocNode *>(&N);
168   }
169 };
170 
171 /// YAMLIO for ScalarDocNode
172 template <> struct TaggedScalarTraits<ScalarDocNode> {
173 
174   static void output(const ScalarDocNode &S, void *Ctxt, raw_ostream &OS,
175                      raw_ostream &TagOS) {
176     TagOS << S.getYAMLTag();
177     OS << S.toString();
178   }
179 
180   static StringRef input(StringRef Str, StringRef Tag, void *Ctxt,
181                          ScalarDocNode &S) {
182     return S.fromString(Str, Tag);
183   }
184 
185   static QuotingType mustQuote(const ScalarDocNode &S, StringRef ScalarStr) {
186     switch (S.getKind()) {
187     case Type::Int:
188       return ScalarTraits<int64_t>::mustQuote(ScalarStr);
189     case Type::UInt:
190       return ScalarTraits<uint64_t>::mustQuote(ScalarStr);
191     case Type::Nil:
192       return ScalarTraits<StringRef>::mustQuote(ScalarStr);
193     case Type::Boolean:
194       return ScalarTraits<bool>::mustQuote(ScalarStr);
195     case Type::Float:
196       return ScalarTraits<double>::mustQuote(ScalarStr);
197     case Type::Binary:
198     case Type::String:
199       return ScalarTraits<std::string>::mustQuote(ScalarStr);
200     default:
201       llvm_unreachable("unrecognized ScalarKind");
202     }
203   }
204 };
205 
206 /// YAMLIO for MapDocNode
207 template <> struct CustomMappingTraits<MapDocNode> {
208 
209   static void inputOne(IO &IO, StringRef Key, MapDocNode &M) {
210     ScalarDocNode KeyObj = M.getDocument()->getNode();
211     KeyObj.fromString(Key, "");
212     IO.mapRequired(Key.str().c_str(), M.getMap()[KeyObj]);
213   }
214 
215   static void output(IO &IO, MapDocNode &M) {
216     for (auto I : M.getMap()) {
217       IO.mapRequired(I.first.toString().c_str(), I.second);
218     }
219   }
220 };
221 
222 /// YAMLIO for ArrayNode
223 template <> struct SequenceTraits<ArrayDocNode> {
224 
225   static size_t size(IO &IO, ArrayDocNode &A) { return A.size(); }
226 
227   static DocNode &element(IO &IO, ArrayDocNode &A, size_t Index) {
228     return A[Index];
229   }
230 };
231 
232 } // namespace yaml
233 } // namespace llvm
234 
235 /// Convert MsgPack Document to YAML text.
236 void msgpack::Document::toYAML(raw_ostream &OS) {
237   yaml::Output Yout(OS);
238   Yout << getRoot();
239 }
240 
241 /// Read YAML text into the MsgPack document. Returns false on failure.
242 bool msgpack::Document::fromYAML(StringRef S) {
243   clear();
244   yaml::Input Yin(S);
245   Yin >> getRoot();
246   return !Yin.error();
247 }
248 
249