folly refactorings to better support cross-platform
authorMarc Horowitz <mhorowitz@fb.com>
Fri, 17 Apr 2015 01:03:39 +0000 (18:03 -0700)
committerwoo <woo@fb.com>
Tue, 26 May 2015 18:31:44 +0000 (11:31 -0700)
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

12 files changed:
folly/Conv.cpp
folly/Conv.h
folly/Format-inl.h
folly/Format.cpp
folly/Format.h
folly/Makefile.am
folly/Portability.h
folly/String.cpp
folly/String.h
folly/StringBase.cpp [new file with mode: 0644]
folly/dynamic-inl.h
folly/dynamic.cpp

index 57b1aeef718e2d0d00ccc87e43c92e791f9ba5f8..0ae9b2222d6ba80164d6f37397f03b01866c850d 100644 (file)
@@ -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<uint8_t>(&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':
index 9b958ad10d2305a8874360725bec404d566519e8..ad2d32c1261d2dcf5d37139b2d0aed7c9d3b2dcb 100644 (file)
@@ -1425,7 +1425,12 @@ to(const Src & value) {
   if (value != witness) {
     throw std::range_error(
       to<std::string>("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;
 }
index c7ebb9f31ac8f5ef87973b87a73c276b5b9444c7..6205a5682eb6e7a09c3fa124bc1913ba967e2a04 100644 (file)
@@ -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<uintmax_t>(uval));
+      int len = snprintf(valBufBegin, (valBuf + valBufSize) - valBufBegin,
+                         "%'ju", static_cast<uintmax_t>(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<double> {
 
   template <class FormatCallback>
   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<int> (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_;
 };
 
index e2e51b8005f76e05a62736f2bee46fbe39a4ef3e..b09152daf1f7b82533f162e33683086cad8e7f96 100644 (file)
@@ -16,6 +16,8 @@
 
 #include <folly/Format.h>
 
+#include <double-conversion/double-conversion.h>
+
 namespace folly {
 namespace detail {
 
@@ -26,6 +28,137 @@ extern const FormatArg::Sign formatSignTable[];
 
 using namespace folly::detail;
 
+void FormatValue<double>::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<int> (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();
index 7ffc1527ba3d1528377351e2a8aaa6fbec3027e5..4afe7e4035148d24a885a9afff57e27e9eaf7e4f 100644 (file)
@@ -26,8 +26,6 @@
 #include <map>
 #include <unordered_map>
 
-#include <double-conversion/double-conversion.h>
-
 #include <folly/FBVector.h>
 #include <folly/Conv.h>
 #include <folly/Range.h>
index 2220cecae943a683d66e74aaf84ffebc32ad35bc..9ada6b0141433c07291d1187faca9a5d8020c7aa 100644 (file)
@@ -337,6 +337,7 @@ libfollybase_la_SOURCES = \
        FormatTables.cpp \
        Malloc.cpp \
        Range.cpp \
+       StringBase.cpp \
        String.cpp \
        Unicode.cpp
 
index 9c1cec440162ab3ecc78b3796e0d7720b3126c13..d42f00318e7ceb175bbcf9ceebab348e30f8ab5d 100644 (file)
@@ -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_
index a3fa43f415c305e20f762ea317801bed84917cfc..337b7e37dac94371203e3a3894861dcdb14bfa4c 100644 (file)
@@ -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) {
index 586dd8d6bedea19c0e2ddd8cdc28d91d639bc80d..ef0ef3f6374ff70ce0ba5b5a324e880eee3e25a6 100644 (file)
@@ -364,9 +364,17 @@ fbstring errnoStr(int err);
  * defined.
  */
 inline fbstring exceptionStr(const std::exception& e) {
+#ifdef FOLLY_HAS_RTTI
   return folly::to<fbstring>(demangle(typeid(e)), ": ", e.what());
+#else
+  return folly::to<fbstring>("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 "<unknown exception>";
   }
 }
+#endif
 
 template<typename E>
 auto exceptionStr(const E& e)
   -> typename std::enable_if<!std::is_base_of<std::exception, E>::value,
                              fbstring>::type
 {
+#ifdef FOLLY_HAS_RTTI
   return folly::to<fbstring>(demangle(typeid(e)));
+#else
+  return "Exception (no RTTI available)";
+#endif
 }
 
 /*
diff --git a/folly/StringBase.cpp b/folly/StringBase.cpp
new file mode 100644 (file)
index 0000000..6c428d6
--- /dev/null
@@ -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 <folly/String.h>
+
+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;
+}
+
+}
index 3bd5689cd4ad40cda792c38ea5ca2d59d0d37c8d..324e7118ca3a7b8ca060450814ec10bfd3519f57 100644 (file)
@@ -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<std::string>("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<std::string>("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<dynamic::ObjectImpl> {
   }
 };
 
-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<T>::comp(*getAddress<T>(),   \
-                                          *o.getAddress<T>())
-  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<T>() == *o.getAddress<T>();
-  FB_DYNAMIC_APPLY(type_, FB_X);
-#undef FB_X
-}
-
 inline dynamic& dynamic::operator+=(dynamic const& o) {
   if (type() == STRING && o.type() == STRING) {
     *getAddress<fbstring>() += *o.getAddress<fbstring>();
@@ -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>()) T(*o.getAddress<T>())
-    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>()) T(std::move(*o.getAddress<T>()))
-    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<ObjectImpl>();
-  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<ObjectImpl>();
-  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<ObjectImpl>();
-  auto it = obj.find(k);
-  if (it != obj.end()) {
-    v = it->second;
-  }
-
-  return std::move(v);
-}
-
 template<class K, class V> inline dynamic& dynamic::setDefault(K&& k, V&& v) {
   auto& obj = get<ObjectImpl>();
   return obj.insert(std::make_pair(std::forward<K>(k),
@@ -561,51 +474,10 @@ inline dynamic* dynamic::get_ptr(dynamic const& idx) {
   return const_cast<dynamic*>(const_cast<dynamic const*>(this)->get_ptr(idx));
 }
 
-inline 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 >= 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());
-  }
-}
-
 inline dynamic& dynamic::at(dynamic const& idx) {
   return const_cast<dynamic&>(const_cast<dynamic const*>(this)->at(idx));
 }
 
-inline dynamic const& dynamic::at(dynamic const& idx) const {
-  if (auto* parray = get_nothrow<Array>()) {
-    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<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());
-  }
-}
-
 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<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());
-}
-
 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<Array>().erase(arr.begin() + (it - arr.begin()));
 }
 
-inline 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()));
-}
-
 inline dynamic::const_key_iterator dynamic::erase(const_key_iterator it) {
   return const_key_iterator(get<ObjectImpl>().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<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();
-  }
-}
-
 //////////////////////////////////////////////////////////////////////
 
 template<class T> struct dynamic::TypeInfo {
@@ -833,23 +665,6 @@ T const& dynamic::get() const {
   return const_cast<dynamic*>(this)->get<T>();
 }
 
-inline char const* dynamic::typeName(Type t) {
-#define FB_X(T) return TypeInfo<T>::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<T>())
-  FB_DYNAMIC_APPLY(type_, FB_X);
-#undef FB_X
-  type_ = NULLT;
-  u_.nul = nullptr;
-}
-
 //////////////////////////////////////////////////////////////////////
 
 /*
index f56465a5544af35c3f0ea36519ef6160638cd750..63b737558a0e21897d75d6d79a9d282a1e5e4356 100644 (file)
@@ -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<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() {}
+
+// 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) {
+    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) {
+    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);
+  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<Array>()) {
+    if (!idx.isInt()) {
+      throw TypeError("int64", idx.type());
+    }
+    if (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 >= 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;
+}
+
 //////////////////////////////////////////////////////////////////////
 
 }