blob: d743bb2296831a541f756b714cfaf475522ee965 [file] [log] [blame]
/*
* Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/dynamic.h>
namespace folly {
//////////////////////////////////////////////////////////////////////
#define DEF_TYPE(T, str, typen) \
template<> char const dynamic::TypeInfo<T>::name[] = str; \
template<> dynamic::Type const dynamic::TypeInfo<T>::type = typen
DEF_TYPE(void*, "null", dynamic::NULLT);
DEF_TYPE(bool, "boolean", dynamic::BOOL);
DEF_TYPE(fbstring, "string", dynamic::STRING);
DEF_TYPE(dynamic::Array, "array", dynamic::ARRAY);
DEF_TYPE(double, "double", dynamic::DOUBLE);
DEF_TYPE(int64_t, "int64", dynamic::INT64);
DEF_TYPE(dynamic::ObjectImpl, "object", dynamic::OBJECT);
#undef DEF_TYPE
const char* dynamic::typeName() const {
return typeName(type_);
}
TypeError::TypeError(const std::string& expected, dynamic::Type actual)
: std::runtime_error(to<std::string>("TypeError: expected dynamic "
"type `", expected, '\'', ", but had type `",
dynamic::typeName(actual), '\''))
{}
TypeError::TypeError(const std::string& expected,
dynamic::Type actual1, dynamic::Type actual2)
: std::runtime_error(to<std::string>("TypeError: expected dynamic "
"types `", expected, '\'', ", but had types `",
dynamic::typeName(actual1), "' and `", dynamic::typeName(actual2),
'\''))
{}
TypeError::~TypeError() = default;
// This is a higher-order preprocessor macro to aid going from runtime
// types to the compile time type system.
#define FB_DYNAMIC_APPLY(type, apply) do { \
switch ((type)) { \
case NULLT: apply(void*); break; \
case ARRAY: apply(Array); break; \
case BOOL: apply(bool); break; \
case DOUBLE: apply(double); break; \
case INT64: apply(int64_t); break; \
case OBJECT: apply(ObjectImpl); break; \
case STRING: apply(fbstring); break; \
default: CHECK(0); abort(); \
} \
} while (0)
bool dynamic::operator<(dynamic const& o) const {
if (UNLIKELY(type_ == OBJECT || o.type_ == OBJECT)) {
throw TypeError("object", type_);
}
if (type_ != o.type_) {
return type_ < o.type_;
}
#define FB_X(T) return CompareOp<T>::comp(*getAddress<T>(), \
*o.getAddress<T>())
FB_DYNAMIC_APPLY(type_, FB_X);
#undef FB_X
}
bool dynamic::operator==(dynamic const& o) const {
if (type() != o.type()) {
if (isNumber() && o.isNumber()) {
auto& integ = isInt() ? *this : o;
auto& doubl = isInt() ? o : *this;
return integ.asInt() == doubl.asDouble();
}
return false;
}
#define FB_X(T) return *getAddress<T>() == *o.getAddress<T>();
FB_DYNAMIC_APPLY(type_, FB_X);
#undef FB_X
}
dynamic& dynamic::operator=(dynamic const& o) {
if (&o != this) {
if (type_ == o.type_) {
#define FB_X(T) *getAddress<T>() = *o.getAddress<T>()
FB_DYNAMIC_APPLY(type_, FB_X);
#undef FB_X
} else {
destroy();
#define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
FB_DYNAMIC_APPLY(o.type_, FB_X);
#undef FB_X
type_ = o.type_;
}
}
return *this;
}
dynamic& dynamic::operator=(dynamic&& o) noexcept {
if (&o != this) {
if (type_ == o.type_) {
#define FB_X(T) *getAddress<T>() = std::move(*o.getAddress<T>())
FB_DYNAMIC_APPLY(type_, FB_X);
#undef FB_X
} else {
destroy();
#define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
FB_DYNAMIC_APPLY(o.type_, FB_X);
#undef FB_X
type_ = o.type_;
}
}
return *this;
}
dynamic& dynamic::operator[](dynamic const& k) & {
if (!isObject() && !isArray()) {
throw TypeError("object/array", type());
}
if (isArray()) {
return at(k);
}
auto& obj = get<ObjectImpl>();
auto ret = obj.insert({k, nullptr});
return ret.first->second;
}
dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) const& {
auto& obj = get<ObjectImpl>();
auto it = obj.find(k);
return it == obj.end() ? v : it->second;
}
dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) const& {
auto& obj = get<ObjectImpl>();
auto it = obj.find(k);
// Avoid clang bug with ternary
if (it == obj.end()) {
return std::move(v);
} else {
return it->second;
}
}
dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) && {
auto& obj = get<ObjectImpl>();
auto it = obj.find(k);
// Avoid clang bug with ternary
if (it == obj.end()) {
return v;
} else {
return std::move(it->second);
}
}
dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) && {
auto& obj = get<ObjectImpl>();
auto it = obj.find(k);
return std::move(it == obj.end() ? v : it->second);
}
const dynamic* dynamic::get_ptr(dynamic const& idx) const& {
if (auto* parray = get_nothrow<Array>()) {
if (!idx.isInt()) {
throw TypeError("int64", idx.type());
}
if (idx < 0 || idx >= parray->size()) {
return nullptr;
}
return &(*parray)[idx.asInt()];
} else if (auto* pobject = get_nothrow<ObjectImpl>()) {
auto it = pobject->find(idx);
if (it == pobject->end()) {
return nullptr;
}
return &it->second;
} else {
throw TypeError("object/array", type());
}
}
dynamic const& dynamic::at(dynamic const& idx) const& {
if (auto* parray = get_nothrow<Array>()) {
if (!idx.isInt()) {
throw TypeError("int64", idx.type());
}
if (idx < 0 || idx >= parray->size()) {
throw std::out_of_range("out of range in dynamic array");
}
return (*parray)[idx.asInt()];
} else if (auto* pobject = get_nothrow<ObjectImpl>()) {
auto it = pobject->find(idx);
if (it == pobject->end()) {
throw std::out_of_range(to<std::string>(
"couldn't find key ", idx.asString(), " in dynamic object"));
}
return it->second;
} else {
throw TypeError("object/array", type());
}
}
std::size_t dynamic::size() const {
if (auto* ar = get_nothrow<Array>()) {
return ar->size();
}
if (auto* obj = get_nothrow<ObjectImpl>()) {
return obj->size();
}
if (auto* str = get_nothrow<fbstring>()) {
return str->size();
}
throw TypeError("array/object", type());
}
dynamic::const_iterator
dynamic::erase(const_iterator first, const_iterator last) {
auto& arr = get<Array>();
return get<Array>().erase(
arr.begin() + (first - arr.begin()),
arr.begin() + (last - arr.begin()));
}
std::size_t dynamic::hash() const {
switch (type()) {
case OBJECT:
case ARRAY:
case NULLT:
throw TypeError("not null/object/array", type());
case INT64:
return std::hash<int64_t>()(asInt());
case DOUBLE:
return std::hash<double>()(asDouble());
case BOOL:
return std::hash<bool>()(asBool());
case STRING:
return std::hash<fbstring>()(asString());
default:
CHECK(0); abort();
}
}
char const* dynamic::typeName(Type t) {
#define FB_X(T) return TypeInfo<T>::name
FB_DYNAMIC_APPLY(t, FB_X);
#undef FB_X
}
void dynamic::destroy() noexcept {
// This short-circuit speeds up some microbenchmarks.
if (type_ == NULLT) return;
#define FB_X(T) detail::Destroy::destroy(getAddress<T>())
FB_DYNAMIC_APPLY(type_, FB_X);
#undef FB_X
type_ = NULLT;
u_.nul = nullptr;
}
//////////////////////////////////////////////////////////////////////
}