Recycle heap on assignment
[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 DEF_TYPE(T, str, typen)                                 \
24   template<> char const dynamic::TypeInfo<T>::name[] = str;       \
25   template<> dynamic::Type const dynamic::TypeInfo<T>::type = typen
26
27 DEF_TYPE(void*,               "null",    dynamic::NULLT);
28 DEF_TYPE(bool,                "boolean", dynamic::BOOL);
29 DEF_TYPE(fbstring,            "string",  dynamic::STRING);
30 DEF_TYPE(dynamic::Array,      "array",   dynamic::ARRAY);
31 DEF_TYPE(double,              "double",  dynamic::DOUBLE);
32 DEF_TYPE(int64_t,             "int64",   dynamic::INT64);
33 DEF_TYPE(dynamic::ObjectImpl, "object",  dynamic::OBJECT);
34
35 #undef DEF_TYPE
36
37 const char* dynamic::typeName() const {
38   return typeName(type_);
39 }
40
41 TypeError::TypeError(const std::string& expected, dynamic::Type actual)
42   : std::runtime_error(to<std::string>("TypeError: expected dynamic "
43       "type `", expected, '\'', ", but had type `",
44       dynamic::typeName(actual), '\''))
45 {}
46
47 TypeError::TypeError(const std::string& expected,
48     dynamic::Type actual1, dynamic::Type actual2)
49   : std::runtime_error(to<std::string>("TypeError: expected dynamic "
50       "types `", expected, '\'', ", but had types `",
51       dynamic::typeName(actual1), "' and `", dynamic::typeName(actual2),
52       '\''))
53 {}
54
55 TypeError::~TypeError() {}
56
57 // This is a higher-order preprocessor macro to aid going from runtime
58 // types to the compile time type system.
59 #define FB_DYNAMIC_APPLY(type, apply) do {         \
60   switch ((type)) {                             \
61   case NULLT:   apply(void*);          break;   \
62   case ARRAY:   apply(Array);          break;   \
63   case BOOL:    apply(bool);           break;   \
64   case DOUBLE:  apply(double);         break;   \
65   case INT64:   apply(int64_t);        break;   \
66   case OBJECT:  apply(ObjectImpl);     break;   \
67   case STRING:  apply(fbstring);       break;   \
68   default:      CHECK(0); abort();              \
69   }                                             \
70 } while (0)
71
72 bool dynamic::operator<(dynamic const& o) const {
73   if (UNLIKELY(type_ == OBJECT || o.type_ == OBJECT)) {
74     throw TypeError("object", type_);
75   }
76   if (type_ != o.type_) {
77     return type_ < o.type_;
78   }
79
80 #define FB_X(T) return CompareOp<T>::comp(*getAddress<T>(),   \
81                                           *o.getAddress<T>())
82   FB_DYNAMIC_APPLY(type_, FB_X);
83 #undef FB_X
84 }
85
86 bool dynamic::operator==(dynamic const& o) const {
87   if (type() != o.type()) {
88     if (isNumber() && o.isNumber()) {
89       auto& integ = isInt() ? *this : o;
90       auto& doubl = isInt() ? o     : *this;
91       return integ.asInt() == doubl.asDouble();
92     }
93     return false;
94   }
95
96 #define FB_X(T) return *getAddress<T>() == *o.getAddress<T>();
97   FB_DYNAMIC_APPLY(type_, FB_X);
98 #undef FB_X
99 }
100
101 dynamic& dynamic::operator=(dynamic const& o) {
102   if (&o != this) {
103     if (type_ == o.type_) {
104 #define FB_X(T) *getAddress<T>() = *o.getAddress<T>()
105       FB_DYNAMIC_APPLY(type_, FB_X);
106 #undef FB_X
107     } else {
108       destroy();
109 #define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
110       FB_DYNAMIC_APPLY(o.type_, FB_X);
111 #undef FB_X
112       type_ = o.type_;
113     }
114   }
115   return *this;
116 }
117
118 dynamic& dynamic::operator=(dynamic&& o) noexcept {
119   if (&o != this) {
120     if (type_ == o.type_) {
121 #define FB_X(T) *getAddress<T>() = std::move(*o.getAddress<T>())
122       FB_DYNAMIC_APPLY(type_, FB_X);
123 #undef FB_X
124     } else {
125       destroy();
126 #define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
127       FB_DYNAMIC_APPLY(o.type_, FB_X);
128 #undef FB_X
129       type_ = o.type_;
130     }
131   }
132   return *this;
133 }
134
135 dynamic& dynamic::operator[](dynamic const& k) {
136   if (!isObject() && !isArray()) {
137     throw TypeError("object/array", type());
138   }
139   if (isArray()) {
140     return at(k);
141   }
142   auto& obj = get<ObjectImpl>();
143   auto ret = obj.insert({k, nullptr});
144   return ret.first->second;
145 }
146
147 dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) const {
148   auto& obj = get<ObjectImpl>();
149   auto it = obj.find(k);
150   return it == obj.end() ? v : it->second;
151 }
152
153 dynamic&& dynamic::getDefault(const dynamic& k, dynamic&& v) const {
154   auto& obj = get<ObjectImpl>();
155   auto it = obj.find(k);
156   if (it != obj.end()) {
157     v = it->second;
158   }
159
160   return std::move(v);
161 }
162
163 const dynamic* dynamic::get_ptr(dynamic const& idx) const {
164   if (auto* parray = get_nothrow<Array>()) {
165     if (!idx.isInt()) {
166       throw TypeError("int64", idx.type());
167     }
168     if (idx >= parray->size()) {
169       return nullptr;
170     }
171     return &(*parray)[idx.asInt()];
172   } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
173     auto it = pobject->find(idx);
174     if (it == pobject->end()) {
175       return nullptr;
176     }
177     return &it->second;
178   } else {
179     throw TypeError("object/array", type());
180   }
181 }
182
183 dynamic const& dynamic::at(dynamic const& idx) const {
184   if (auto* parray = get_nothrow<Array>()) {
185     if (!idx.isInt()) {
186       throw TypeError("int64", idx.type());
187     }
188     if (idx >= parray->size()) {
189       throw std::out_of_range("out of range in dynamic array");
190     }
191     return (*parray)[idx.asInt()];
192   } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
193     auto it = pobject->find(idx);
194     if (it == pobject->end()) {
195       throw std::out_of_range(to<std::string>(
196           "couldn't find key ", idx.asString(), " in dynamic object"));
197     }
198     return it->second;
199   } else {
200     throw TypeError("object/array", type());
201   }
202 }
203
204 std::size_t dynamic::size() const {
205   if (auto* ar = get_nothrow<Array>()) {
206     return ar->size();
207   }
208   if (auto* obj = get_nothrow<ObjectImpl>()) {
209     return obj->size();
210   }
211   if (auto* str = get_nothrow<fbstring>()) {
212     return str->size();
213   }
214   throw TypeError("array/object", type());
215 }
216
217 dynamic::const_iterator
218 dynamic::erase(const_iterator first, const_iterator last) {
219   auto& arr = get<Array>();
220   return get<Array>().erase(
221     arr.begin() + (first - arr.begin()),
222     arr.begin() + (last - arr.begin()));
223 }
224
225 std::size_t dynamic::hash() const {
226   switch (type()) {
227   case OBJECT:
228   case ARRAY:
229   case NULLT:
230     throw TypeError("not null/object/array", type());
231   case INT64:
232     return std::hash<int64_t>()(asInt());
233   case DOUBLE:
234     return std::hash<double>()(asDouble());
235   case BOOL:
236     return std::hash<bool>()(asBool());
237   case STRING:
238     return std::hash<fbstring>()(asString());
239   default:
240     CHECK(0); abort();
241   }
242 }
243
244 char const* dynamic::typeName(Type t) {
245 #define FB_X(T) return TypeInfo<T>::name
246   FB_DYNAMIC_APPLY(t, FB_X);
247 #undef FB_X
248 }
249
250 void dynamic::destroy() noexcept {
251   // This short-circuit speeds up some microbenchmarks.
252   if (type_ == NULLT) return;
253
254 #define FB_X(T) detail::Destroy::destroy(getAddress<T>())
255   FB_DYNAMIC_APPLY(type_, FB_X);
256 #undef FB_X
257   type_ = NULLT;
258   u_.nul = nullptr;
259 }
260
261 //////////////////////////////////////////////////////////////////////
262
263 }