From: Marc Horowitz Date: Fri, 17 Apr 2015 01:03:39 +0000 (-0700) Subject: folly refactorings to better support cross-platform X-Git-Tag: v0.40.0~9 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=3b4d7b6a8e7c001af8ff7fc4a55229d58b2114db;p=folly.git folly refactorings to better support cross-platform Summary: I'm looking into pulling parts of folly (right now, dynamic, json, and their dependencies) into fbandroid for use as part of xplat. This diff includes a few kinds of changes: portability fixes for arm; reduce the size of the code generated by moving non-templated functions and methods into cpp files; refactoring header usages which require extra compiler flags on android to cpp files; and slicing up the libraries a bit differently to reduce dependencies. This should all be backward-compatible, and do no harm to fbcode. Test Plan: runtests, sandcastle Reviewed By: njormrod@fb.com Subscribers: darshan, davejwatson, tudorb, dancol, folly-diffs@, yfeldblum, chalfant FB internal diff: D2057797 Tasks: 7005344 Signature: t1:2057797:1432145435:fa10f129fc669e682da5b4b207fc96986ca035fc --- diff --git a/folly/Conv.cpp b/folly/Conv.cpp index 57b1aeef..0ae9b222 100644 --- a/folly/Conv.cpp +++ b/folly/Conv.cpp @@ -81,14 +81,13 @@ bool str_to_bool(StringPiece* src) { switch (*b) { case '0': case '1': { - // Attempt to parse the value as an integer - StringPiece tmp(*src); - uint8_t value = to(&tmp); - // Only accept 0 or 1 - FOLLY_RANGE_CHECK_STRINGPIECE( - value <= 1, "Integer overflow when parsing bool: must be 0 or 1", *src); - b = tmp.begin(); - result = (value == 1); + result = false; + for (; b < e && isdigit(*b); ++b) { + FOLLY_RANGE_CHECK_STRINGPIECE( + !result && (*b == '0' || *b == '1'), + "Integer overflow when parsing bool: must be 0 or 1", *src); + result = (*b == '1'); + } break; } case 'y': diff --git a/folly/Conv.h b/folly/Conv.h index 9b958ad1..ad2d32c1 100644 --- a/folly/Conv.h +++ b/folly/Conv.h @@ -1425,7 +1425,12 @@ to(const Src & value) { if (value != witness) { throw std::range_error( to("to<>: loss of precision when converting ", value, - " to type ", typeid(Tgt).name()).c_str()); +#ifdef FOLLY_HAS_RTTI + " to type ", typeid(Tgt).name() +#else + " to other type" +#endif + ).c_str()); } return result; } diff --git a/folly/Format-inl.h b/folly/Format-inl.h index c7ebb9f3..6205a568 100644 --- a/folly/Format-inl.h +++ b/folly/Format-inl.h @@ -453,7 +453,7 @@ class FormatValue< int prefixLen = 0; switch (presentation) { - case 'n': + case 'n': { arg.enforce(!arg.basePrefix, "base prefix not allowed with '", presentation, "' specifier"); @@ -463,9 +463,14 @@ class FormatValue< "' specifier"); valBufBegin = valBuf + 3; // room for sign and base prefix - valBufEnd = valBufBegin + sprintf(valBufBegin, "%'ju", - static_cast(uval)); + int len = snprintf(valBufBegin, (valBuf + valBufSize) - valBufBegin, + "%'ju", static_cast(uval)); + // valBufSize should always be big enough, so this should never + // happen. + assert(len < valBuf + valBufSize - valBufBegin); + valBufEnd = valBufBegin + len; break; + } case 'd': arg.enforce(!arg.basePrefix, "base prefix not allowed with '", presentation, @@ -586,135 +591,15 @@ class FormatValue { template void format(FormatArg& arg, FormatCallback& cb) const { - using ::double_conversion::DoubleToStringConverter; - using ::double_conversion::StringBuilder; - - arg.validate(FormatArg::Type::FLOAT); - - if (arg.presentation == FormatArg::kDefaultPresentation) { - arg.presentation = 'g'; - } - - const char* infinitySymbol = isupper(arg.presentation) ? "INF" : "inf"; - const char* nanSymbol = isupper(arg.presentation) ? "NAN" : "nan"; - char exponentSymbol = isupper(arg.presentation) ? 'E' : 'e'; - - if (arg.precision == FormatArg::kDefaultPrecision) { - arg.precision = 6; - } - - // 2+: for null terminator and optional sign shenanigans. - char buf[2 + std::max({ - (2 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint + - DoubleToStringConverter::kMaxFixedDigitsAfterPoint), - (8 + DoubleToStringConverter::kMaxExponentialDigits), - (7 + DoubleToStringConverter::kMaxPrecisionDigits)})]; - StringBuilder builder(buf + 1, static_cast (sizeof(buf) - 1)); - - char plusSign; - switch (arg.sign) { - case FormatArg::Sign::PLUS_OR_MINUS: - plusSign = '+'; - break; - case FormatArg::Sign::SPACE_OR_MINUS: - plusSign = ' '; - break; - default: - plusSign = '\0'; - break; - }; - - auto flags = - DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN | - (arg.trailingDot ? DoubleToStringConverter::EMIT_TRAILING_DECIMAL_POINT - : 0); - - double val = val_; - switch (arg.presentation) { - case '%': - val *= 100; - case 'f': - case 'F': - { - if (arg.precision > - DoubleToStringConverter::kMaxFixedDigitsAfterPoint) { - arg.precision = DoubleToStringConverter::kMaxFixedDigitsAfterPoint; - } - DoubleToStringConverter conv(flags, - infinitySymbol, - nanSymbol, - exponentSymbol, - -4, - arg.precision, - 0, - 0); - arg.enforce(conv.ToFixed(val, arg.precision, &builder), - "fixed double conversion failed"); - } - break; - case 'e': - case 'E': - { - if (arg.precision > DoubleToStringConverter::kMaxExponentialDigits) { - arg.precision = DoubleToStringConverter::kMaxExponentialDigits; - } - - DoubleToStringConverter conv(flags, - infinitySymbol, - nanSymbol, - exponentSymbol, - -4, - arg.precision, - 0, - 0); - arg.enforce(conv.ToExponential(val, arg.precision, &builder)); - } - break; - case 'n': // should be locale-aware, but isn't - case 'g': - case 'G': - { - if (arg.precision < DoubleToStringConverter::kMinPrecisionDigits) { - arg.precision = DoubleToStringConverter::kMinPrecisionDigits; - } else if (arg.precision > - DoubleToStringConverter::kMaxPrecisionDigits) { - arg.precision = DoubleToStringConverter::kMaxPrecisionDigits; - } - DoubleToStringConverter conv(flags, - infinitySymbol, - nanSymbol, - exponentSymbol, - -4, - arg.precision, - 0, - 0); - arg.enforce(conv.ToShortest(val, &builder)); - } - break; - default: - arg.error("invalid specifier '", arg.presentation, "'"); - } - - int len = builder.position(); - builder.Finalize(); - DCHECK_GT(len, 0); - - // Add '+' or ' ' sign if needed - char* p = buf + 1; - // anything that's neither negative nor nan - int prefixLen = 0; - if (plusSign && (*p != '-' && *p != 'n' && *p != 'N')) { - *--p = plusSign; - ++len; - prefixLen = 1; - } else if (*p == '-') { - prefixLen = 1; - } - - format_value::formatNumber(StringPiece(p, len), prefixLen, arg, cb); + fbstring piece; + int prefixLen; + formatHelper(piece, prefixLen, arg); + format_value::formatNumber(piece, prefixLen, arg, cb); } private: + void formatHelper(fbstring& piece, int& prefixLen, FormatArg& arg) const; + double val_; }; diff --git a/folly/Format.cpp b/folly/Format.cpp index e2e51b80..b09152da 100644 --- a/folly/Format.cpp +++ b/folly/Format.cpp @@ -16,6 +16,8 @@ #include +#include + namespace folly { namespace detail { @@ -26,6 +28,137 @@ extern const FormatArg::Sign formatSignTable[]; using namespace folly::detail; +void FormatValue::formatHelper( + fbstring& piece, int& prefixLen, FormatArg& arg) const { + using ::double_conversion::DoubleToStringConverter; + using ::double_conversion::StringBuilder; + + arg.validate(FormatArg::Type::FLOAT); + + if (arg.presentation == FormatArg::kDefaultPresentation) { + arg.presentation = 'g'; + } + + const char* infinitySymbol = isupper(arg.presentation) ? "INF" : "inf"; + const char* nanSymbol = isupper(arg.presentation) ? "NAN" : "nan"; + char exponentSymbol = isupper(arg.presentation) ? 'E' : 'e'; + + if (arg.precision == FormatArg::kDefaultPrecision) { + arg.precision = 6; + } + + // 2+: for null terminator and optional sign shenanigans. + char buf[2 + std::max({ + (2 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint + + DoubleToStringConverter::kMaxFixedDigitsAfterPoint), + (8 + DoubleToStringConverter::kMaxExponentialDigits), + (7 + DoubleToStringConverter::kMaxPrecisionDigits)})]; + StringBuilder builder(buf + 1, static_cast (sizeof(buf) - 1)); + + char plusSign; + switch (arg.sign) { + case FormatArg::Sign::PLUS_OR_MINUS: + plusSign = '+'; + break; + case FormatArg::Sign::SPACE_OR_MINUS: + plusSign = ' '; + break; + default: + plusSign = '\0'; + break; + }; + + auto flags = + DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN | + (arg.trailingDot ? DoubleToStringConverter::EMIT_TRAILING_DECIMAL_POINT + : 0); + + double val = val_; + switch (arg.presentation) { + case '%': + val *= 100; + case 'f': + case 'F': + { + if (arg.precision > + DoubleToStringConverter::kMaxFixedDigitsAfterPoint) { + arg.precision = DoubleToStringConverter::kMaxFixedDigitsAfterPoint; + } + DoubleToStringConverter conv(flags, + infinitySymbol, + nanSymbol, + exponentSymbol, + -4, + arg.precision, + 0, + 0); + arg.enforce(conv.ToFixed(val, arg.precision, &builder), + "fixed double conversion failed"); + } + break; + case 'e': + case 'E': + { + if (arg.precision > DoubleToStringConverter::kMaxExponentialDigits) { + arg.precision = DoubleToStringConverter::kMaxExponentialDigits; + } + + DoubleToStringConverter conv(flags, + infinitySymbol, + nanSymbol, + exponentSymbol, + -4, + arg.precision, + 0, + 0); + arg.enforce(conv.ToExponential(val, arg.precision, &builder)); + } + break; + case 'n': // should be locale-aware, but isn't + case 'g': + case 'G': + { + if (arg.precision < DoubleToStringConverter::kMinPrecisionDigits) { + arg.precision = DoubleToStringConverter::kMinPrecisionDigits; + } else if (arg.precision > + DoubleToStringConverter::kMaxPrecisionDigits) { + arg.precision = DoubleToStringConverter::kMaxPrecisionDigits; + } + DoubleToStringConverter conv(flags, + infinitySymbol, + nanSymbol, + exponentSymbol, + -4, + arg.precision, + 0, + 0); + arg.enforce(conv.ToShortest(val, &builder)); + } + break; + default: + arg.error("invalid specifier '", arg.presentation, "'"); + } + + int len = builder.position(); + builder.Finalize(); + DCHECK_GT(len, 0); + + // Add '+' or ' ' sign if needed + char* p = buf + 1; + // anything that's neither negative nor nan + prefixLen = 0; + if (plusSign && (*p != '-' && *p != 'n' && *p != 'N')) { + *--p = plusSign; + ++len; + prefixLen = 1; + } else if (*p == '-') { + prefixLen = 1; + } + + piece = fbstring(p, len); +} + + void FormatArg::initSlow() { auto b = fullArgString.begin(); auto end = fullArgString.end(); diff --git a/folly/Format.h b/folly/Format.h index 7ffc1527..4afe7e40 100644 --- a/folly/Format.h +++ b/folly/Format.h @@ -26,8 +26,6 @@ #include #include -#include - #include #include #include diff --git a/folly/Makefile.am b/folly/Makefile.am index 2220ceca..9ada6b01 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -337,6 +337,7 @@ libfollybase_la_SOURCES = \ FormatTables.cpp \ Malloc.cpp \ Range.cpp \ + StringBase.cpp \ String.cpp \ Unicode.cpp diff --git a/folly/Portability.h b/folly/Portability.h index 9c1cec44..d42f0031 100644 --- a/folly/Portability.h +++ b/folly/Portability.h @@ -273,4 +273,9 @@ inline size_t malloc_usable_size(void* ptr) { } #endif +// RTTI may not be enabled for this compilation unit. +#if defined(__GXX_RTTI) || defined(__cpp_rtti) +# define FOLLY_HAS_RTTI 1 +#endif + #endif // FOLLY_PORTABILITY_H_ diff --git a/folly/String.cpp b/folly/String.cpp index a3fa43f4..337b7e37 100644 --- a/folly/String.cpp +++ b/folly/String.cpp @@ -351,25 +351,6 @@ fbstring errnoStr(int err) { return result; } -StringPiece skipWhitespace(StringPiece sp) { - // Spaces other than ' ' characters are less common but should be - // checked. This configuration where we loop on the ' ' - // separately from oddspaces was empirically fastest. - auto oddspace = [] (char c) { - return c == '\n' || c == '\t' || c == '\r'; - }; - -loop: - for (; !sp.empty() && sp.front() == ' '; sp.pop_front()) { - } - if (!sp.empty() && oddspace(sp.front())) { - sp.pop_front(); - goto loop; - } - - return sp; -} - namespace { void toLowerAscii8(char& c) { diff --git a/folly/String.h b/folly/String.h index 586dd8d6..ef0ef3f6 100644 --- a/folly/String.h +++ b/folly/String.h @@ -364,9 +364,17 @@ fbstring errnoStr(int err); * defined. */ inline fbstring exceptionStr(const std::exception& e) { +#ifdef FOLLY_HAS_RTTI return folly::to(demangle(typeid(e)), ": ", e.what()); +#else + return folly::to("Exception (no RTTI available): ", e.what()); +#endif } +// Empirically, this indicates if the runtime supports +// std::exception_ptr, as not all (arm, for instance) do. +#if defined(__GNUC__) && defined(__GCC_ATOMIC_INT_LOCK_FREE) && \ + __GCC_ATOMIC_INT_LOCK_FREE > 1 inline fbstring exceptionStr(std::exception_ptr ep) { try { std::rethrow_exception(ep); @@ -376,13 +384,18 @@ inline fbstring exceptionStr(std::exception_ptr ep) { return ""; } } +#endif template auto exceptionStr(const E& e) -> typename std::enable_if::value, fbstring>::type { +#ifdef FOLLY_HAS_RTTI return folly::to(demangle(typeid(e))); +#else + return "Exception (no RTTI available)"; +#endif } /* diff --git a/folly/StringBase.cpp b/folly/StringBase.cpp new file mode 100644 index 00000000..6c428d68 --- /dev/null +++ b/folly/StringBase.cpp @@ -0,0 +1,40 @@ +/* + * 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 + +namespace folly { + +StringPiece skipWhitespace(StringPiece sp) { + // Spaces other than ' ' characters are less common but should be + // checked. This configuration where we loop on the ' ' + // separately from oddspaces was empirically fastest. + auto oddspace = [] (char c) { + return c == '\n' || c == '\t' || c == '\r'; + }; + +loop: + for (; !sp.empty() && sp.front() == ' '; sp.pop_front()) { + } + if (!sp.empty() && oddspace(sp.front())) { + sp.pop_front(); + goto loop; + } + + return sp; +} + +} diff --git a/folly/dynamic-inl.h b/folly/dynamic-inl.h index 3bd5689c..324e7118 100644 --- a/folly/dynamic-inl.h +++ b/folly/dynamic-inl.h @@ -59,18 +59,10 @@ struct hash< ::folly::dynamic> { namespace folly { struct TypeError : std::runtime_error { - explicit TypeError(const std::string& expected, dynamic::Type actual) - : std::runtime_error(to("TypeError: expected dynamic " - "type `", expected, '\'', ", but had type `", - dynamic::typeName(actual), '\'')) - {} + explicit TypeError(const std::string& expected, dynamic::Type actual); explicit TypeError(const std::string& expected, - dynamic::Type actual1, dynamic::Type actual2) - : std::runtime_error(to("TypeError: expected dynamic " - "types `", expected, '\'', ", but had types `", - dynamic::typeName(actual1), "' and `", dynamic::typeName(actual2), - '\'')) - {} + dynamic::Type actual1, dynamic::Type actual2); + ~TypeError(); }; @@ -418,35 +410,6 @@ struct dynamic::CompareOp { } }; -inline 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::comp(*getAddress(), \ - *o.getAddress()) - FB_DYNAMIC_APPLY(type_, FB_X); -#undef FB_X -} - -inline 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() == *o.getAddress(); - FB_DYNAMIC_APPLY(type_, FB_X); -#undef FB_X -} - inline dynamic& dynamic::operator+=(dynamic const& o) { if (type() == STRING && o.type() == STRING) { *getAddress() += *o.getAddress(); @@ -497,60 +460,10 @@ inline dynamic& dynamic::operator--() { return *this; } -inline dynamic& dynamic::operator=(dynamic const& o) { - if (&o != this) { - destroy(); -#define FB_X(T) new (getAddress()) T(*o.getAddress()) - FB_DYNAMIC_APPLY(o.type_, FB_X); -#undef FB_X - type_ = o.type_; - } - return *this; -} - -inline dynamic& dynamic::operator=(dynamic&& o) noexcept { - if (&o != this) { - destroy(); -#define FB_X(T) new (getAddress()) T(std::move(*o.getAddress())) - FB_DYNAMIC_APPLY(o.type_, FB_X); -#undef FB_X - type_ = o.type_; - } - return *this; -} - -inline dynamic& dynamic::operator[](dynamic const& k) { - if (!isObject() && !isArray()) { - throw TypeError("object/array", type()); - } - if (isArray()) { - return at(k); - } - auto& obj = get(); - auto ret = obj.insert({k, nullptr}); - return ret.first->second; -} - inline dynamic const& dynamic::operator[](dynamic const& idx) const { return at(idx); } -inline dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) const { - auto& obj = get(); - auto it = obj.find(k); - return it == obj.end() ? v : it->second; -} - -inline dynamic&& dynamic::getDefault(const dynamic& k, dynamic&& v) const { - auto& obj = get(); - auto it = obj.find(k); - if (it != obj.end()) { - v = it->second; - } - - return std::move(v); -} - template inline dynamic& dynamic::setDefault(K&& k, V&& v) { auto& obj = get(); return obj.insert(std::make_pair(std::forward(k), @@ -561,51 +474,10 @@ inline dynamic* dynamic::get_ptr(dynamic const& idx) { return const_cast(const_cast(this)->get_ptr(idx)); } -inline const dynamic* dynamic::get_ptr(dynamic const& idx) const { - if (auto* parray = get_nothrow()) { - if (!idx.isInt()) { - throw TypeError("int64", idx.type()); - } - if (idx >= parray->size()) { - return nullptr; - } - return &(*parray)[idx.asInt()]; - } else if (auto* pobject = get_nothrow()) { - auto it = pobject->find(idx); - if (it == pobject->end()) { - return nullptr; - } - return &it->second; - } else { - throw TypeError("object/array", type()); - } -} - inline dynamic& dynamic::at(dynamic const& idx) { return const_cast(const_cast(this)->at(idx)); } -inline dynamic const& dynamic::at(dynamic const& idx) const { - if (auto* parray = get_nothrow()) { - if (!idx.isInt()) { - throw TypeError("int64", idx.type()); - } - if (idx >= parray->size()) { - throw std::out_of_range("out of range in dynamic array"); - } - return (*parray)[idx.asInt()]; - } else if (auto* pobject = get_nothrow()) { - auto it = pobject->find(idx); - if (it == pobject->end()) { - throw std::out_of_range(to( - "couldn't find key ", idx.asString(), " in dynamic object")); - } - return it->second; - } else { - throw TypeError("object/array", type()); - } -} - inline bool dynamic::empty() const { if (isNull()) { return true; @@ -613,19 +485,6 @@ inline bool dynamic::empty() const { return !size(); } -inline std::size_t dynamic::size() const { - if (auto* ar = get_nothrow()) { - return ar->size(); - } - if (auto* obj = get_nothrow()) { - return obj->size(); - } - if (auto* str = get_nothrow()) { - return str->size(); - } - throw TypeError("array/object", type()); -} - inline std::size_t dynamic::count(dynamic const& key) const { return find(key) != items().end(); } @@ -653,14 +512,6 @@ inline dynamic::const_iterator dynamic::erase(const_iterator it) { return get().erase(arr.begin() + (it - arr.begin())); } -inline dynamic::const_iterator -dynamic::erase(const_iterator first, const_iterator last) { - auto& arr = get(); - return get().erase( - arr.begin() + (first - arr.begin()), - arr.begin() + (last - arr.begin())); -} - inline dynamic::const_key_iterator dynamic::erase(const_key_iterator it) { return const_key_iterator(get().erase(it.base())); } @@ -711,25 +562,6 @@ inline void dynamic::pop_back() { array.pop_back(); } -inline 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()(asInt()); - case DOUBLE: - return std::hash()(asDouble()); - case BOOL: - return std::hash()(asBool()); - case STRING: - return std::hash()(asString()); - default: - CHECK(0); abort(); - } -} - ////////////////////////////////////////////////////////////////////// template struct dynamic::TypeInfo { @@ -833,23 +665,6 @@ T const& dynamic::get() const { return const_cast(this)->get(); } -inline char const* dynamic::typeName(Type t) { -#define FB_X(T) return TypeInfo::name - FB_DYNAMIC_APPLY(t, FB_X); -#undef FB_X -} - -inline void dynamic::destroy() noexcept { - // This short-circuit speeds up some microbenchmarks. - if (type_ == NULLT) return; - -#define FB_X(T) detail::Destroy::destroy(getAddress()) - FB_DYNAMIC_APPLY(type_, FB_X); -#undef FB_X - type_ = NULLT; - u_.nul = nullptr; -} - ////////////////////////////////////////////////////////////////////// /* diff --git a/folly/dynamic.cpp b/folly/dynamic.cpp index f56465a5..63b73755 100644 --- a/folly/dynamic.cpp +++ b/folly/dynamic.cpp @@ -38,6 +38,214 @@ const char* dynamic::typeName() const { return typeName(type_); } +TypeError::TypeError(const std::string& expected, dynamic::Type actual) + : std::runtime_error(to("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("TypeError: expected dynamic " + "types `", expected, '\'', ", but had types `", + dynamic::typeName(actual1), "' and `", dynamic::typeName(actual2), + '\'')) +{} + +TypeError::~TypeError() {} + +// 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::comp(*getAddress(), \ + *o.getAddress()) + 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() == *o.getAddress(); + FB_DYNAMIC_APPLY(type_, FB_X); +#undef FB_X +} + +dynamic& dynamic::operator=(dynamic const& o) { + if (&o != this) { + destroy(); +#define FB_X(T) new (getAddress()) T(*o.getAddress()) + FB_DYNAMIC_APPLY(o.type_, FB_X); +#undef FB_X + type_ = o.type_; + } + return *this; +} + +dynamic& dynamic::operator=(dynamic&& o) noexcept { + if (&o != this) { + destroy(); +#define FB_X(T) new (getAddress()) T(std::move(*o.getAddress())) + 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(); + auto ret = obj.insert({k, nullptr}); + return ret.first->second; +} + +dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) const { + auto& obj = get(); + auto it = obj.find(k); + return it == obj.end() ? v : it->second; +} + +dynamic&& dynamic::getDefault(const dynamic& k, dynamic&& v) const { + auto& obj = get(); + auto it = obj.find(k); + if (it != obj.end()) { + v = it->second; + } + + return std::move(v); +} + +const dynamic* dynamic::get_ptr(dynamic const& idx) const { + if (auto* parray = get_nothrow()) { + if (!idx.isInt()) { + throw TypeError("int64", idx.type()); + } + if (idx >= parray->size()) { + return nullptr; + } + return &(*parray)[idx.asInt()]; + } else if (auto* pobject = get_nothrow()) { + 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()) { + if (!idx.isInt()) { + throw TypeError("int64", idx.type()); + } + if (idx >= parray->size()) { + throw std::out_of_range("out of range in dynamic array"); + } + return (*parray)[idx.asInt()]; + } else if (auto* pobject = get_nothrow()) { + auto it = pobject->find(idx); + if (it == pobject->end()) { + throw std::out_of_range(to( + "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()) { + return ar->size(); + } + if (auto* obj = get_nothrow()) { + return obj->size(); + } + if (auto* str = get_nothrow()) { + return str->size(); + } + throw TypeError("array/object", type()); +} + +dynamic::const_iterator +dynamic::erase(const_iterator first, const_iterator last) { + auto& arr = get(); + return get().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()(asInt()); + case DOUBLE: + return std::hash()(asDouble()); + case BOOL: + return std::hash()(asBool()); + case STRING: + return std::hash()(asString()); + default: + CHECK(0); abort(); + } +} + +char const* dynamic::typeName(Type t) { +#define FB_X(T) return TypeInfo::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()) + FB_DYNAMIC_APPLY(type_, FB_X); +#undef FB_X + type_ = NULLT; + u_.nul = nullptr; +} + ////////////////////////////////////////////////////////////////////// }