Some more OpenSSL 1.1.0 compat APIs
[folly.git] / folly / Format.cpp
index 385a9bb8b9d215bd1dfecd7cb78b18ec2a1f08d6..25ddf6312bdd8f8462c0a2b6f45ba6a386a1d660 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#include "folly/Format.h"
+#include <folly/Format.h>
+
+#include <folly/CppAttributes.h>
+#include <folly/portability/Constexpr.h>
+
+#include <double-conversion/double-conversion.h>
 
 namespace folly {
 namespace detail {
@@ -26,12 +31,146 @@ 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.
+  constexpr int bufLen =
+      2 + constexpr_max(
+              2 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint +
+                  DoubleToStringConverter::kMaxFixedDigitsAfterPoint,
+              constexpr_max(8 + DoubleToStringConverter::kMaxExponentialDigits,
+                            7 + DoubleToStringConverter::kMaxPrecisionDigits));
+  char buf[bufLen];
+  StringBuilder builder(buf + 1, bufLen - 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;
+    FOLLY_FALLTHROUGH;
+  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, size_t(len));
+}
+
+
 void FormatArg::initSlow() {
   auto b = fullArgString.begin();
   auto end = fullArgString.end();
 
   // Parse key
-  auto p = static_cast<const char*>(memchr(b, ':', end - b));
+  auto p = static_cast<const char*>(memchr(b, ':', size_t(end - b)));
   if (!p) {
     key_ = StringPiece(b, end);
     return;
@@ -76,12 +215,25 @@ void FormatArg::initSlow() {
       if (++p == end) return;
     }
 
-    if (*p >= '0' && *p <= '9') {
-      auto b = p;
+    auto readInt = [&] {
+      auto const c = p;
       do {
         ++p;
       } while (p != end && *p >= '0' && *p <= '9');
-      width = to<int>(StringPiece(b, p));
+      return to<int>(StringPiece(c, p));
+    };
+
+    if (*p == '*') {
+      width = kDynamicWidth;
+      ++p;
+
+      if (p == end) return;
+
+      if (*p >= '0' && *p <= '9') widthIndex = readInt();
+
+      if (p == end) return;
+    } else if (*p >= '0' && *p <= '9') {
+      width = readInt();
 
       if (p == end) return;
     }
@@ -92,11 +244,19 @@ void FormatArg::initSlow() {
     }
 
     if (*p == '.') {
-      auto b = ++p;
+      auto d = ++p;
       while (p != end && *p >= '0' && *p <= '9') {
         ++p;
       }
-      precision = to<int>(StringPiece(b, p));
+      if (p != d) {
+        precision = to<int>(StringPiece(d, p));
+        if (p != end && *p == '.') {
+          trailingDot = true;
+          ++p;
+        }
+      } else {
+        trailingDot = true;
+      }
 
       if (p == end) return;
     }
@@ -134,4 +294,40 @@ void FormatArg::validate(Type type) const {
   }
 }
 
+namespace detail {
+void insertThousandsGroupingUnsafe(char* start_buffer, char** end_buffer) {
+  uint32_t remaining_digits = uint32_t(*end_buffer - start_buffer);
+  uint32_t separator_size = (remaining_digits - 1) / 3;
+  uint32_t result_size = remaining_digits + separator_size;
+  *end_buffer = *end_buffer + separator_size;
+
+  // get the end of the new string with the separators
+  uint32_t buffer_write_index = result_size - 1;
+  uint32_t buffer_read_index = remaining_digits - 1;
+  start_buffer[buffer_write_index + 1] = 0;
+
+  bool done = false;
+  uint32_t next_group_size = 3;
+
+  while (!done) {
+    uint32_t current_group_size = std::max<uint32_t>(1,
+      std::min<uint32_t>(remaining_digits, next_group_size));
+
+    // write out the current group's digits to the buffer index
+    for (uint32_t i = 0; i < current_group_size; i++) {
+      start_buffer[buffer_write_index--] = start_buffer[buffer_read_index--];
+    }
+
+    // if not finished, write the separator before the next group
+    if (buffer_write_index < buffer_write_index + 1) {
+      start_buffer[buffer_write_index--] = ',';
+    } else {
+      done = true;
+    }
+
+    remaining_digits -= current_group_size;
+  }
+}
+} // detail
+
 }  // namespace folly