725cf56ad1bb0beaf90a2d38b8e24346d110af6a
[folly.git] / folly / dynamic.cpp
1 /*
2  * Copyright 2015 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <folly/dynamic.h>
18
19 namespace folly {
20
21 //////////////////////////////////////////////////////////////////////
22
23 #define FOLLY_DYNAMIC_DEF_TYPEINFO(T) \
24   constexpr const char* dynamic::TypeInfo<T>::name; \
25   constexpr dynamic::Type dynamic::TypeInfo<T>::type; \
26   //
27
28 FOLLY_DYNAMIC_DEF_TYPEINFO(void*)
29 FOLLY_DYNAMIC_DEF_TYPEINFO(bool)
30 FOLLY_DYNAMIC_DEF_TYPEINFO(fbstring)
31 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::Array)
32 FOLLY_DYNAMIC_DEF_TYPEINFO(double)
33 FOLLY_DYNAMIC_DEF_TYPEINFO(int64_t)
34 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::ObjectImpl)
35
36 #undef FOLLY_DYNAMIC_DEF_TYPEINFO
37
38 const char* dynamic::typeName() const {
39   return typeName(type_);
40 }
41
42 TypeError::TypeError(const std::string& expected, dynamic::Type actual)
43   : std::runtime_error(to<std::string>("TypeError: expected dynamic "
44       "type `", expected, '\'', ", but had type `",
45       dynamic::typeName(actual), '\''))
46 {}
47
48 TypeError::TypeError(const std::string& expected,
49     dynamic::Type actual1, dynamic::Type actual2)
50   : std::runtime_error(to<std::string>("TypeError: expected dynamic "
51       "types `", expected, '\'', ", but had types `",
52       dynamic::typeName(actual1), "' and `", dynamic::typeName(actual2),
53       '\''))
54 {}
55
56 TypeError::~TypeError() = default;
57
58 // This is a higher-order preprocessor macro to aid going from runtime
59 // types to the compile time type system.
60 #define FB_DYNAMIC_APPLY(type, apply) do {         \
61   switch ((type)) {                             \
62   case NULLT:   apply(void*);          break;   \
63   case ARRAY:   apply(Array);          break;   \
64   case BOOL:    apply(bool);           break;   \
65   case DOUBLE:  apply(double);         break;   \
66   case INT64:   apply(int64_t);        break;   \
67   case OBJECT:  apply(ObjectImpl);     break;   \
68   case STRING:  apply(fbstring);       break;   \
69   default:      CHECK(0); abort();              \
70   }                                             \
71 } while (0)
72
73 bool dynamic::operator<(dynamic const& o) const {
74   if (UNLIKELY(type_ == OBJECT || o.type_ == OBJECT)) {
75     throw TypeError("object", type_);
76   }
77   if (type_ != o.type_) {
78     return type_ < o.type_;
79   }
80
81 #define FB_X(T) return CompareOp<T>::comp(*getAddress<T>(),   \
82                                           *o.getAddress<T>())
83   FB_DYNAMIC_APPLY(type_, FB_X);
84 #undef FB_X
85 }
86
87 bool dynamic::operator==(dynamic const& o) const {
88   if (type() != o.type()) {
89     if (isNumber() && o.isNumber()) {
90       auto& integ = isInt() ? *this : o;
91       auto& doubl = isInt() ? o     : *this;
92       return integ.asInt() == doubl.asDouble();
93     }
94     return false;
95   }
96
97 #define FB_X(T) return *getAddress<T>() == *o.getAddress<T>();
98   FB_DYNAMIC_APPLY(type_, FB_X);
99 #undef FB_X
100 }
101
102 dynamic& dynamic::operator=(dynamic const& o) {
103   if (&o != this) {
104     if (type_ == o.type_) {
105 #define FB_X(T) *getAddress<T>() = *o.getAddress<T>()
106       FB_DYNAMIC_APPLY(type_, FB_X);
107 #undef FB_X
108     } else {
109       destroy();
110 #define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
111       FB_DYNAMIC_APPLY(o.type_, FB_X);
112 #undef FB_X
113       type_ = o.type_;
114     }
115   }
116   return *this;
117 }
118
119 dynamic& dynamic::operator=(dynamic&& o) noexcept {
120   if (&o != this) {
121     if (type_ == o.type_) {
122 #define FB_X(T) *getAddress<T>() = std::move(*o.getAddress<T>())
123       FB_DYNAMIC_APPLY(type_, FB_X);
124 #undef FB_X
125     } else {
126       destroy();
127 #define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
128       FB_DYNAMIC_APPLY(o.type_, FB_X);
129 #undef FB_X
130       type_ = o.type_;
131     }
132   }
133   return *this;
134 }
135
136 dynamic& dynamic::operator[](dynamic const& k) & {
137   if (!isObject() && !isArray()) {
138     throw TypeError("object/array", type());
139   }
140   if (isArray()) {
141     return at(k);
142   }
143   auto& obj = get<ObjectImpl>();
144   auto ret = obj.insert({k, nullptr});
145   return ret.first->second;
146 }
147
148 dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) const& {
149   auto& obj = get<ObjectImpl>();
150   auto it = obj.find(k);
151   return it == obj.end() ? v : it->second;
152 }
153
154 dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) const& {
155   auto& obj = get<ObjectImpl>();
156   auto it = obj.find(k);
157   // Avoid clang bug with ternary
158   if (it == obj.end()) {
159     return std::move(v);
160   } else {
161     return it->second;
162   }
163 }
164
165 dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) && {
166   auto& obj = get<ObjectImpl>();
167   auto it = obj.find(k);
168   // Avoid clang bug with ternary
169   if (it == obj.end()) {
170     return v;
171   } else {
172     return std::move(it->second);
173   }
174 }
175
176 dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) && {
177   auto& obj = get<ObjectImpl>();
178   auto it = obj.find(k);
179   return std::move(it == obj.end() ? v : it->second);
180 }
181
182 const dynamic* dynamic::get_ptr(dynamic const& idx) const& {
183   if (auto* parray = get_nothrow<Array>()) {
184     if (!idx.isInt()) {
185       throw TypeError("int64", idx.type());
186     }
187     if (idx < 0 || idx >= parray->size()) {
188       return nullptr;
189     }
190     return &(*parray)[idx.asInt()];
191   } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
192     auto it = pobject->find(idx);
193     if (it == pobject->end()) {
194       return nullptr;
195     }
196     return &it->second;
197   } else {
198     throw TypeError("object/array", type());
199   }
200 }
201
202 dynamic const& dynamic::at(dynamic const& idx) const& {
203   if (auto* parray = get_nothrow<Array>()) {
204     if (!idx.isInt()) {
205       throw TypeError("int64", idx.type());
206     }
207     if (idx < 0 || idx >= parray->size()) {
208       throw std::out_of_range("out of range in dynamic array");
209     }
210     return (*parray)[idx.asInt()];
211   } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
212     auto it = pobject->find(idx);
213     if (it == pobject->end()) {
214       throw std::out_of_range(to<std::string>(
215           "couldn't find key ", idx.asString(), " in dynamic object"));
216     }
217     return it->second;
218   } else {
219     throw TypeError("object/array", type());
220   }
221 }
222
223 std::size_t dynamic::size() const {
224   if (auto* ar = get_nothrow<Array>()) {
225     return ar->size();
226   }
227   if (auto* obj = get_nothrow<ObjectImpl>()) {
228     return obj->size();
229   }
230   if (auto* str = get_nothrow<fbstring>()) {
231     return str->size();
232   }
233   throw TypeError("array/object", type());
234 }
235
236 dynamic::const_iterator
237 dynamic::erase(const_iterator first, const_iterator last) {
238   auto& arr = get<Array>();
239   return get<Array>().erase(
240     arr.begin() + (first - arr.begin()),
241     arr.begin() + (last - arr.begin()));
242 }
243
244 std::size_t dynamic::hash() const {
245   switch (type()) {
246   case OBJECT:
247   case ARRAY:
248   case NULLT:
249     throw TypeError("not null/object/array", type());
250   case INT64:
251     return std::hash<int64_t>()(asInt());
252   case DOUBLE:
253     return std::hash<double>()(asDouble());
254   case BOOL:
255     return std::hash<bool>()(asBool());
256   case STRING:
257     return std::hash<fbstring>()(asString());
258   default:
259     CHECK(0); abort();
260   }
261 }
262
263 char const* dynamic::typeName(Type t) {
264 #define FB_X(T) return TypeInfo<T>::name
265   FB_DYNAMIC_APPLY(t, FB_X);
266 #undef FB_X
267 }
268
269 void dynamic::destroy() noexcept {
270   // This short-circuit speeds up some microbenchmarks.
271   if (type_ == NULLT) return;
272
273 #define FB_X(T) detail::Destroy::destroy(getAddress<T>())
274   FB_DYNAMIC_APPLY(type_, FB_X);
275 #undef FB_X
276   type_ = NULLT;
277   u_.nul = nullptr;
278 }
279
280 //////////////////////////////////////////////////////////////////////
281
282 }