| /* |
| * 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. |
| */ |
| |
| #ifndef FOLLY_FORMAT_H_ |
| #error This file may only be included from Format.h. |
| #endif |
| |
| #include <array> |
| #include <deque> |
| #include <map> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <folly/Exception.h> |
| #include <folly/FormatTraits.h> |
| #include <folly/Traits.h> |
| |
| // Ignore -Wformat-nonliteral warnings within this file |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" |
| |
| namespace folly { |
| |
| namespace detail { |
| |
| // Updates the end of the buffer after the comma separators have been added. |
| void insertThousandsGroupingUnsafe(char* start_buffer, char** end_buffer); |
| |
| extern const char formatHexUpper[256][2]; |
| extern const char formatHexLower[256][2]; |
| extern const char formatOctal[512][3]; |
| extern const char formatBinary[256][8]; |
| |
| const size_t kMaxHexLength = 2 * sizeof(uintmax_t); |
| const size_t kMaxOctalLength = 3 * sizeof(uintmax_t); |
| const size_t kMaxBinaryLength = 8 * sizeof(uintmax_t); |
| |
| /** |
| * Convert an unsigned to hex, using repr (which maps from each possible |
| * 2-hex-bytes value to the 2-character representation). |
| * |
| * Just like folly::detail::uintToBuffer in Conv.h, writes at the *end* of |
| * the supplied buffer and returns the offset of the beginning of the string |
| * from the start of the buffer. The formatted string will be in range |
| * [buf+begin, buf+bufLen). |
| */ |
| template <class Uint> |
| size_t uintToHex(char* buffer, size_t bufLen, Uint v, |
| const char (&repr)[256][2]) { |
| // 'v >>= 7, v >>= 1' is no more than a work around to get rid of shift size |
| // warning when Uint = uint8_t (it's false as v >= 256 implies sizeof(v) > 1). |
| for (; !less_than<unsigned, 256>(v); v >>= 7, v >>= 1) { |
| auto b = v & 0xff; |
| bufLen -= 2; |
| buffer[bufLen] = repr[b][0]; |
| buffer[bufLen + 1] = repr[b][1]; |
| } |
| buffer[--bufLen] = repr[v][1]; |
| if (v >= 16) { |
| buffer[--bufLen] = repr[v][0]; |
| } |
| return bufLen; |
| } |
| |
| /** |
| * Convert an unsigned to hex, using lower-case letters for the digits |
| * above 9. See the comments for uintToHex. |
| */ |
| template <class Uint> |
| inline size_t uintToHexLower(char* buffer, size_t bufLen, Uint v) { |
| return uintToHex(buffer, bufLen, v, formatHexLower); |
| } |
| |
| /** |
| * Convert an unsigned to hex, using upper-case letters for the digits |
| * above 9. See the comments for uintToHex. |
| */ |
| template <class Uint> |
| inline size_t uintToHexUpper(char* buffer, size_t bufLen, Uint v) { |
| return uintToHex(buffer, bufLen, v, formatHexUpper); |
| } |
| |
| /** |
| * Convert an unsigned to octal. |
| * |
| * Just like folly::detail::uintToBuffer in Conv.h, writes at the *end* of |
| * the supplied buffer and returns the offset of the beginning of the string |
| * from the start of the buffer. The formatted string will be in range |
| * [buf+begin, buf+bufLen). |
| */ |
| template <class Uint> |
| size_t uintToOctal(char* buffer, size_t bufLen, Uint v) { |
| auto& repr = formatOctal; |
| // 'v >>= 7, v >>= 2' is no more than a work around to get rid of shift size |
| // warning when Uint = uint8_t (it's false as v >= 512 implies sizeof(v) > 1). |
| for (; !less_than<unsigned, 512>(v); v >>= 7, v >>= 2) { |
| auto b = v & 0x1ff; |
| bufLen -= 3; |
| buffer[bufLen] = repr[b][0]; |
| buffer[bufLen + 1] = repr[b][1]; |
| buffer[bufLen + 2] = repr[b][2]; |
| } |
| buffer[--bufLen] = repr[v][2]; |
| if (v >= 8) { |
| buffer[--bufLen] = repr[v][1]; |
| } |
| if (v >= 64) { |
| buffer[--bufLen] = repr[v][0]; |
| } |
| return bufLen; |
| } |
| |
| /** |
| * Convert an unsigned to binary. |
| * |
| * Just like folly::detail::uintToBuffer in Conv.h, writes at the *end* of |
| * the supplied buffer and returns the offset of the beginning of the string |
| * from the start of the buffer. The formatted string will be in range |
| * [buf+begin, buf+bufLen). |
| */ |
| template <class Uint> |
| size_t uintToBinary(char* buffer, size_t bufLen, Uint v) { |
| auto& repr = formatBinary; |
| if (v == 0) { |
| buffer[--bufLen] = '0'; |
| return bufLen; |
| } |
| for (; v; v >>= 7, v >>= 1) { |
| auto b = v & 0xff; |
| bufLen -= 8; |
| memcpy(buffer + bufLen, &(repr[b][0]), 8); |
| } |
| while (buffer[bufLen] == '0') { |
| ++bufLen; |
| } |
| return bufLen; |
| } |
| |
| } // namespace detail |
| |
| template <class Derived, bool containerMode, class... Args> |
| BaseFormatter<Derived, containerMode, Args...>::BaseFormatter(StringPiece str, |
| Args&&... args) |
| : str_(str), |
| values_(FormatValue<typename std::decay<Args>::type>( |
| std::forward<Args>(args))...) { |
| static_assert(!containerMode || sizeof...(Args) == 1, |
| "Exactly one argument required in container mode"); |
| } |
| |
| template <class Derived, bool containerMode, class... Args> |
| template <class Output> |
| void BaseFormatter<Derived, containerMode, Args...>::operator()(Output& out) |
| const { |
| // Copy raw string (without format specifiers) to output; |
| // not as simple as we'd like, as we still need to translate "}}" to "}" |
| // and throw if we see any lone "}" |
| auto outputString = [&out] (StringPiece s) { |
| auto p = s.begin(); |
| auto end = s.end(); |
| while (p != end) { |
| auto q = static_cast<const char*>(memchr(p, '}', end - p)); |
| if (!q) { |
| out(StringPiece(p, end)); |
| break; |
| } |
| ++q; |
| out(StringPiece(p, q)); |
| p = q; |
| |
| if (p == end || *p != '}') { |
| throw BadFormatArg("folly::format: single '}' in format string"); |
| } |
| ++p; |
| } |
| }; |
| |
| auto p = str_.begin(); |
| auto end = str_.end(); |
| |
| int nextArg = 0; |
| bool hasDefaultArgIndex = false; |
| bool hasExplicitArgIndex = false; |
| while (p != end) { |
| auto q = static_cast<const char*>(memchr(p, '{', end - p)); |
| if (!q) { |
| outputString(StringPiece(p, end)); |
| break; |
| } |
| outputString(StringPiece(p, q)); |
| p = q + 1; |
| |
| if (p == end) { |
| throw BadFormatArg("folly::format: '}' at end of format string"); |
| } |
| |
| // "{{" -> "{" |
| if (*p == '{') { |
| out(StringPiece(p, 1)); |
| ++p; |
| continue; |
| } |
| |
| // Format string |
| q = static_cast<const char*>(memchr(p, '}', end - p)); |
| if (q == nullptr) { |
| throw BadFormatArg("folly::format: missing ending '}'"); |
| } |
| FormatArg arg(StringPiece(p, q)); |
| p = q + 1; |
| |
| int argIndex = 0; |
| auto piece = arg.splitKey<true>(); // empty key component is okay |
| if (containerMode) { // static |
| arg.enforce(arg.width != FormatArg::kDynamicWidth, |
| "dynamic field width not supported in vformat()"); |
| if (piece.empty()) { |
| arg.setNextIntKey(nextArg++); |
| hasDefaultArgIndex = true; |
| } else { |
| arg.setNextKey(piece); |
| hasExplicitArgIndex = true; |
| } |
| } else { |
| if (piece.empty()) { |
| if (arg.width == FormatArg::kDynamicWidth) { |
| arg.enforce(arg.widthIndex == FormatArg::kNoIndex, |
| "cannot provide width arg index without value arg index"); |
| int sizeArg = nextArg++; |
| arg.width = getSizeArg(sizeArg, arg); |
| } |
| |
| argIndex = nextArg++; |
| hasDefaultArgIndex = true; |
| } else { |
| if (arg.width == FormatArg::kDynamicWidth) { |
| arg.enforce(arg.widthIndex != FormatArg::kNoIndex, |
| "cannot provide value arg index without width arg index"); |
| arg.width = getSizeArg(arg.widthIndex, arg); |
| } |
| |
| try { |
| argIndex = to<int>(piece); |
| } catch (const std::out_of_range& e) { |
| arg.error("argument index must be integer"); |
| } |
| arg.enforce(argIndex >= 0, "argument index must be non-negative"); |
| hasExplicitArgIndex = true; |
| } |
| } |
| |
| if (hasDefaultArgIndex && hasExplicitArgIndex) { |
| throw BadFormatArg( |
| "folly::format: may not have both default and explicit arg indexes"); |
| } |
| |
| doFormat(argIndex, arg, out); |
| } |
| } |
| |
| template <class Derived, bool containerMode, class... Args> |
| void writeTo(FILE* fp, |
| const BaseFormatter<Derived, containerMode, Args...>& formatter) { |
| auto writer = [fp] (StringPiece sp) { |
| size_t n = fwrite(sp.data(), 1, sp.size(), fp); |
| if (n < sp.size()) { |
| throwSystemError("Formatter writeTo", "fwrite failed"); |
| } |
| }; |
| formatter(writer); |
| } |
| |
| namespace format_value { |
| |
| template <class FormatCallback> |
| void formatString(StringPiece val, FormatArg& arg, FormatCallback& cb) { |
| if (arg.width != FormatArg::kDefaultWidth && arg.width < 0) { |
| throw BadFormatArg("folly::format: invalid width"); |
| } |
| if (arg.precision != FormatArg::kDefaultPrecision && arg.precision < 0) { |
| throw BadFormatArg("folly::format: invalid precision"); |
| } |
| |
| // XXX: clang should be smart enough to not need the two static_cast<size_t> |
| // uses below given the above checks. If clang ever becomes that smart, we |
| // should remove the otherwise unnecessary warts. |
| |
| if (arg.precision != FormatArg::kDefaultPrecision && |
| val.size() > static_cast<size_t>(arg.precision)) { |
| val.reset(val.data(), arg.precision); |
| } |
| |
| constexpr int padBufSize = 128; |
| char padBuf[padBufSize]; |
| |
| // Output padding, no more than padBufSize at once |
| auto pad = [&padBuf, &cb, padBufSize] (int chars) { |
| while (chars) { |
| int n = std::min(chars, padBufSize); |
| cb(StringPiece(padBuf, n)); |
| chars -= n; |
| } |
| }; |
| |
| int padRemaining = 0; |
| if (arg.width != FormatArg::kDefaultWidth && |
| val.size() < static_cast<size_t>(arg.width)) { |
| char fill = arg.fill == FormatArg::kDefaultFill ? ' ' : arg.fill; |
| int padChars = static_cast<int> (arg.width - val.size()); |
| memset(padBuf, fill, std::min(padBufSize, padChars)); |
| |
| switch (arg.align) { |
| case FormatArg::Align::DEFAULT: |
| case FormatArg::Align::LEFT: |
| padRemaining = padChars; |
| break; |
| case FormatArg::Align::CENTER: |
| pad(padChars / 2); |
| padRemaining = padChars - padChars / 2; |
| break; |
| case FormatArg::Align::RIGHT: |
| case FormatArg::Align::PAD_AFTER_SIGN: |
| pad(padChars); |
| break; |
| default: |
| abort(); |
| break; |
| } |
| } |
| |
| cb(val); |
| |
| if (padRemaining) { |
| pad(padRemaining); |
| } |
| } |
| |
| template <class FormatCallback> |
| void formatNumber(StringPiece val, int prefixLen, FormatArg& arg, |
| FormatCallback& cb) { |
| // precision means something different for numbers |
| arg.precision = FormatArg::kDefaultPrecision; |
| if (arg.align == FormatArg::Align::DEFAULT) { |
| arg.align = FormatArg::Align::RIGHT; |
| } else if (prefixLen && arg.align == FormatArg::Align::PAD_AFTER_SIGN) { |
| // Split off the prefix, then do any padding if necessary |
| cb(val.subpiece(0, prefixLen)); |
| val.advance(prefixLen); |
| arg.width = std::max(arg.width - prefixLen, 0); |
| } |
| format_value::formatString(val, arg, cb); |
| } |
| |
| template <class FormatCallback, |
| class Derived, |
| bool containerMode, |
| class... Args> |
| void formatFormatter( |
| const BaseFormatter<Derived, containerMode, Args...>& formatter, |
| FormatArg& arg, |
| FormatCallback& cb) { |
| if (arg.width == FormatArg::kDefaultWidth && |
| arg.precision == FormatArg::kDefaultPrecision) { |
| // nothing to do |
| formatter(cb); |
| } else if (arg.align != FormatArg::Align::LEFT && |
| arg.align != FormatArg::Align::DEFAULT) { |
| // We can only avoid creating a temporary string if we align left, |
| // as we'd need to know the size beforehand otherwise |
| format_value::formatString(formatter.fbstr(), arg, cb); |
| } else { |
| auto fn = [&arg, &cb] (StringPiece sp) mutable { |
| int sz = static_cast<int>(sp.size()); |
| if (arg.precision != FormatArg::kDefaultPrecision) { |
| sz = std::min(arg.precision, sz); |
| sp.reset(sp.data(), sz); |
| arg.precision -= sz; |
| } |
| if (!sp.empty()) { |
| cb(sp); |
| if (arg.width != FormatArg::kDefaultWidth) { |
| arg.width = std::max(arg.width - sz, 0); |
| } |
| } |
| }; |
| formatter(fn); |
| if (arg.width != FormatArg::kDefaultWidth && arg.width != 0) { |
| // Rely on formatString to do appropriate padding |
| format_value::formatString(StringPiece(), arg, cb); |
| } |
| } |
| } |
| |
| } // namespace format_value |
| |
| // Definitions for default FormatValue classes |
| |
| // Integral types (except bool) |
| template <class T> |
| class FormatValue< |
| T, typename std::enable_if< |
| std::is_integral<T>::value && |
| !std::is_same<T, bool>::value>::type> |
| { |
| public: |
| explicit FormatValue(T val) : val_(val) { } |
| |
| T getValue() const { |
| return val_; |
| } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| arg.validate(FormatArg::Type::INTEGER); |
| doFormat(arg, cb); |
| } |
| |
| template <class FormatCallback> |
| void doFormat(FormatArg& arg, FormatCallback& cb) const { |
| char presentation = arg.presentation; |
| if (presentation == FormatArg::kDefaultPresentation) { |
| presentation = std::is_same<T, char>::value ? 'c' : 'd'; |
| } |
| |
| // Do all work as unsigned, we'll add the prefix ('0' or '0x' if necessary) |
| // and sign ourselves. |
| typedef typename std::make_unsigned<T>::type UT; |
| UT uval; |
| char sign; |
| if (std::is_signed<T>::value) { |
| if (folly::is_negative(val_)) { |
| uval = static_cast<UT>(-val_); |
| sign = '-'; |
| } else { |
| uval = static_cast<UT>(val_); |
| switch (arg.sign) { |
| case FormatArg::Sign::PLUS_OR_MINUS: |
| sign = '+'; |
| break; |
| case FormatArg::Sign::SPACE_OR_MINUS: |
| sign = ' '; |
| break; |
| default: |
| sign = '\0'; |
| break; |
| } |
| } |
| } else { |
| uval = val_; |
| sign = '\0'; |
| |
| arg.enforce(arg.sign == FormatArg::Sign::DEFAULT, |
| "sign specifications not allowed for unsigned values"); |
| } |
| |
| // max of: |
| // #x: 0x prefix + 16 bytes = 18 bytes |
| // #o: 0 prefix + 22 bytes = 23 bytes |
| // #b: 0b prefix + 64 bytes = 65 bytes |
| // ,d: 26 bytes (including thousands separators!) |
| // + nul terminator |
| // + 3 for sign and prefix shenanigans (see below) |
| constexpr size_t valBufSize = 69; |
| char valBuf[valBufSize]; |
| char* valBufBegin = nullptr; |
| char* valBufEnd = nullptr; |
| |
| int prefixLen = 0; |
| switch (presentation) { |
| case 'n': { |
| arg.enforce(!arg.basePrefix, |
| "base prefix not allowed with '", presentation, |
| "' specifier"); |
| |
| arg.enforce(!arg.thousandsSeparator, |
| "cannot use ',' with the '", presentation, |
| "' specifier"); |
| |
| valBufBegin = valBuf + 3; // room for sign and base prefix |
| #ifdef _MSC_VER |
| char valBuf2[valBufSize]; |
| snprintf(valBuf2, valBufSize, "%ju", static_cast<uintmax_t>(uval)); |
| int len = GetNumberFormat( |
| LOCALE_USER_DEFAULT, |
| 0, |
| valBuf2, |
| nullptr, |
| valBufBegin, |
| (int)((valBuf + valBufSize) - valBufBegin) |
| ); |
| #else |
| int len = snprintf(valBufBegin, (valBuf + valBufSize) - valBufBegin, |
| "%'ju", static_cast<uintmax_t>(uval)); |
| #endif |
| // 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, |
| "' specifier"); |
| valBufBegin = valBuf + 3; // room for sign and base prefix |
| |
| // Use uintToBuffer, faster than sprintf |
| valBufEnd = valBufBegin + uint64ToBufferUnsafe(uval, valBufBegin); |
| if (arg.thousandsSeparator) { |
| detail::insertThousandsGroupingUnsafe(valBufBegin, &valBufEnd); |
| } |
| break; |
| case 'c': |
| arg.enforce(!arg.basePrefix, |
| "base prefix not allowed with '", presentation, |
| "' specifier"); |
| arg.enforce(!arg.thousandsSeparator, |
| "thousands separator (',') not allowed with '", |
| presentation, "' specifier"); |
| valBufBegin = valBuf + 3; |
| *valBufBegin = static_cast<char>(uval); |
| valBufEnd = valBufBegin + 1; |
| break; |
| case 'o': |
| case 'O': |
| arg.enforce(!arg.thousandsSeparator, |
| "thousands separator (',') not allowed with '", |
| presentation, "' specifier"); |
| valBufEnd = valBuf + valBufSize - 1; |
| valBufBegin = valBuf + detail::uintToOctal(valBuf, valBufSize - 1, uval); |
| if (arg.basePrefix) { |
| *--valBufBegin = '0'; |
| prefixLen = 1; |
| } |
| break; |
| case 'x': |
| arg.enforce(!arg.thousandsSeparator, |
| "thousands separator (',') not allowed with '", |
| presentation, "' specifier"); |
| valBufEnd = valBuf + valBufSize - 1; |
| valBufBegin = valBuf + detail::uintToHexLower(valBuf, valBufSize - 1, |
| uval); |
| if (arg.basePrefix) { |
| *--valBufBegin = 'x'; |
| *--valBufBegin = '0'; |
| prefixLen = 2; |
| } |
| break; |
| case 'X': |
| arg.enforce(!arg.thousandsSeparator, |
| "thousands separator (',') not allowed with '", |
| presentation, "' specifier"); |
| valBufEnd = valBuf + valBufSize - 1; |
| valBufBegin = valBuf + detail::uintToHexUpper(valBuf, valBufSize - 1, |
| uval); |
| if (arg.basePrefix) { |
| *--valBufBegin = 'X'; |
| *--valBufBegin = '0'; |
| prefixLen = 2; |
| } |
| break; |
| case 'b': |
| case 'B': |
| arg.enforce(!arg.thousandsSeparator, |
| "thousands separator (',') not allowed with '", |
| presentation, "' specifier"); |
| valBufEnd = valBuf + valBufSize - 1; |
| valBufBegin = valBuf + detail::uintToBinary(valBuf, valBufSize - 1, |
| uval); |
| if (arg.basePrefix) { |
| *--valBufBegin = presentation; // 0b or 0B |
| *--valBufBegin = '0'; |
| prefixLen = 2; |
| } |
| break; |
| default: |
| arg.error("invalid specifier '", presentation, "'"); |
| } |
| |
| if (sign) { |
| *--valBufBegin = sign; |
| ++prefixLen; |
| } |
| |
| format_value::formatNumber(StringPiece(valBufBegin, valBufEnd), prefixLen, |
| arg, cb); |
| } |
| |
| private: |
| T val_; |
| }; |
| |
| // Bool |
| template <> |
| class FormatValue<bool> { |
| public: |
| explicit FormatValue(bool val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| if (arg.presentation == FormatArg::kDefaultPresentation) { |
| arg.validate(FormatArg::Type::OTHER); |
| format_value::formatString(val_ ? "true" : "false", arg, cb); |
| } else { // number |
| FormatValue<int>(val_).format(arg, cb); |
| } |
| } |
| |
| private: |
| bool val_; |
| }; |
| |
| // double |
| template <> |
| class FormatValue<double> { |
| public: |
| explicit FormatValue(double val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| 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_; |
| }; |
| |
| // float (defer to double) |
| template <> |
| class FormatValue<float> { |
| public: |
| explicit FormatValue(float val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| FormatValue<double>(val_).format(arg, cb); |
| } |
| |
| private: |
| float val_; |
| }; |
| |
| // Sring-y types (implicitly convertible to StringPiece, except char*) |
| template <class T> |
| class FormatValue< |
| T, typename std::enable_if< |
| (!std::is_pointer<T>::value || |
| !std::is_same<char, typename std::decay< |
| typename std::remove_pointer<T>::type>::type>::value) && |
| std::is_convertible<T, StringPiece>::value>::type> |
| { |
| public: |
| explicit FormatValue(StringPiece val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| if (arg.keyEmpty()) { |
| arg.validate(FormatArg::Type::OTHER); |
| arg.enforce(arg.presentation == FormatArg::kDefaultPresentation || |
| arg.presentation == 's', |
| "invalid specifier '", arg.presentation, "'"); |
| format_value::formatString(val_, arg, cb); |
| } else { |
| FormatValue<char>(val_.at(arg.splitIntKey())).format(arg, cb); |
| } |
| } |
| |
| private: |
| StringPiece val_; |
| }; |
| |
| // Null |
| template <> |
| class FormatValue<std::nullptr_t> { |
| public: |
| explicit FormatValue(std::nullptr_t) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| arg.validate(FormatArg::Type::OTHER); |
| arg.enforce(arg.presentation == FormatArg::kDefaultPresentation, |
| "invalid specifier '", arg.presentation, "'"); |
| format_value::formatString("(null)", arg, cb); |
| } |
| }; |
| |
| // Partial specialization of FormatValue for char* |
| template <class T> |
| class FormatValue< |
| T*, |
| typename std::enable_if< |
| std::is_same<char, typename std::decay<T>::type>::value>::type> |
| { |
| public: |
| explicit FormatValue(T* val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| if (arg.keyEmpty()) { |
| if (!val_) { |
| FormatValue<std::nullptr_t>(nullptr).format(arg, cb); |
| } else { |
| FormatValue<StringPiece>(val_).format(arg, cb); |
| } |
| } else { |
| FormatValue<typename std::decay<T>::type>( |
| val_[arg.splitIntKey()]).format(arg, cb); |
| } |
| } |
| |
| private: |
| T* val_; |
| }; |
| |
| // Partial specialization of FormatValue for void* |
| template <class T> |
| class FormatValue< |
| T*, |
| typename std::enable_if< |
| std::is_same<void, typename std::decay<T>::type>::value>::type> |
| { |
| public: |
| explicit FormatValue(T* val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| if (!val_) { |
| FormatValue<std::nullptr_t>(nullptr).format(arg, cb); |
| } else { |
| // Print as a pointer, in hex. |
| arg.validate(FormatArg::Type::OTHER); |
| arg.enforce(arg.presentation == FormatArg::kDefaultPresentation, |
| "invalid specifier '", arg.presentation, "'"); |
| arg.basePrefix = true; |
| arg.presentation = 'x'; |
| if (arg.align == FormatArg::Align::DEFAULT) { |
| arg.align = FormatArg::Align::LEFT; |
| } |
| FormatValue<uintptr_t>( |
| reinterpret_cast<uintptr_t>(val_)).doFormat(arg, cb); |
| } |
| } |
| |
| private: |
| T* val_; |
| }; |
| |
| template <class T, class = void> |
| class TryFormatValue { |
| public: |
| template <class FormatCallback> |
| static void formatOrFail(T& value, FormatArg& arg, FormatCallback& cb) { |
| arg.error("No formatter available for this type"); |
| } |
| }; |
| |
| template <class T> |
| class TryFormatValue< |
| T, |
| typename std::enable_if< |
| 0 < sizeof(FormatValue<typename std::decay<T>::type>)>::type> |
| { |
| public: |
| template <class FormatCallback> |
| static void formatOrFail(T& value, FormatArg& arg, FormatCallback& cb) { |
| FormatValue<typename std::decay<T>::type>(value).format(arg, cb); |
| } |
| }; |
| |
| // Partial specialization of FormatValue for other pointers |
| template <class T> |
| class FormatValue< |
| T*, |
| typename std::enable_if< |
| !std::is_same<char, typename std::decay<T>::type>::value && |
| !std::is_same<void, typename std::decay<T>::type>::value>::type> |
| { |
| public: |
| explicit FormatValue(T* val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| if (arg.keyEmpty()) { |
| FormatValue<void*>((void*)val_).format(arg, cb); |
| } else { |
| TryFormatValue<T>::formatOrFail(val_[arg.splitIntKey()], arg, cb); |
| } |
| } |
| private: |
| T* val_; |
| }; |
| |
| namespace detail { |
| |
| // std::array |
| template <class T, size_t N> |
| struct IndexableTraits<std::array<T, N>> |
| : public IndexableTraitsSeq<std::array<T, N>> { |
| }; |
| |
| // std::vector |
| template <class T, class A> |
| struct IndexableTraits<std::vector<T, A>> |
| : public IndexableTraitsSeq<std::vector<T, A>> { |
| }; |
| |
| // std::deque |
| template <class T, class A> |
| struct IndexableTraits<std::deque<T, A>> |
| : public IndexableTraitsSeq<std::deque<T, A>> { |
| }; |
| |
| // std::map with integral keys |
| template <class K, class T, class C, class A> |
| struct IndexableTraits< |
| std::map<K, T, C, A>, |
| typename std::enable_if<std::is_integral<K>::value>::type> |
| : public IndexableTraitsAssoc<std::map<K, T, C, A>> { |
| }; |
| |
| // std::unordered_map with integral keys |
| template <class K, class T, class H, class E, class A> |
| struct IndexableTraits< |
| std::unordered_map<K, T, H, E, A>, |
| typename std::enable_if<std::is_integral<K>::value>::type> |
| : public IndexableTraitsAssoc<std::unordered_map<K, T, H, E, A>> { |
| }; |
| |
| } // namespace detail |
| |
| // Partial specialization of FormatValue for integer-indexable containers |
| template <class T> |
| class FormatValue< |
| T, |
| typename detail::IndexableTraits<T>::enabled> { |
| public: |
| explicit FormatValue(const T& val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| FormatValue<typename std::decay< |
| typename detail::IndexableTraits<T>::value_type>::type>( |
| detail::IndexableTraits<T>::at( |
| val_, arg.splitIntKey())).format(arg, cb); |
| } |
| |
| private: |
| const T& val_; |
| }; |
| |
| template <class Container, class Value> |
| class FormatValue< |
| detail::DefaultValueWrapper<Container, Value>, |
| typename detail::IndexableTraits<Container>::enabled> { |
| public: |
| explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val) |
| : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| FormatValue<typename std::decay< |
| typename detail::IndexableTraits<Container>::value_type>::type>( |
| detail::IndexableTraits<Container>::at( |
| val_.container, |
| arg.splitIntKey(), |
| val_.defaultValue)).format(arg, cb); |
| } |
| |
| private: |
| const detail::DefaultValueWrapper<Container, Value>& val_; |
| }; |
| |
| namespace detail { |
| |
| // Define enabled, key_type, convert from StringPiece to the key types |
| // that we support |
| template <class T> struct KeyFromStringPiece; |
| |
| // std::string |
| template <> |
| struct KeyFromStringPiece<std::string> : public FormatTraitsBase { |
| typedef std::string key_type; |
| static std::string convert(StringPiece s) { |
| return s.toString(); |
| } |
| typedef void enabled; |
| }; |
| |
| // fbstring |
| template <> |
| struct KeyFromStringPiece<fbstring> : public FormatTraitsBase { |
| typedef fbstring key_type; |
| static fbstring convert(StringPiece s) { |
| return s.toFbstring(); |
| } |
| }; |
| |
| // StringPiece |
| template <> |
| struct KeyFromStringPiece<StringPiece> : public FormatTraitsBase { |
| typedef StringPiece key_type; |
| static StringPiece convert(StringPiece s) { |
| return s; |
| } |
| }; |
| |
| // Base class for associative types keyed by strings |
| template <class T> struct KeyableTraitsAssoc : public FormatTraitsBase { |
| typedef typename T::key_type key_type; |
| typedef typename T::value_type::second_type value_type; |
| static const value_type& at(const T& map, StringPiece key) { |
| return map.at(KeyFromStringPiece<key_type>::convert(key)); |
| } |
| static const value_type& at(const T& map, StringPiece key, |
| const value_type& dflt) { |
| auto pos = map.find(KeyFromStringPiece<key_type>::convert(key)); |
| return pos != map.end() ? pos->second : dflt; |
| } |
| }; |
| |
| // Define enabled, key_type, value_type, at() for supported string-keyed |
| // types |
| template <class T, class Enabled=void> struct KeyableTraits; |
| |
| // std::map with string key |
| template <class K, class T, class C, class A> |
| struct KeyableTraits< |
| std::map<K, T, C, A>, |
| typename KeyFromStringPiece<K>::enabled> |
| : public KeyableTraitsAssoc<std::map<K, T, C, A>> { |
| }; |
| |
| // std::unordered_map with string key |
| template <class K, class T, class H, class E, class A> |
| struct KeyableTraits< |
| std::unordered_map<K, T, H, E, A>, |
| typename KeyFromStringPiece<K>::enabled> |
| : public KeyableTraitsAssoc<std::unordered_map<K, T, H, E, A>> { |
| }; |
| |
| } // namespace detail |
| |
| // Partial specialization of FormatValue for string-keyed containers |
| template <class T> |
| class FormatValue< |
| T, |
| typename detail::KeyableTraits<T>::enabled> { |
| public: |
| explicit FormatValue(const T& val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| FormatValue<typename std::decay< |
| typename detail::KeyableTraits<T>::value_type>::type>( |
| detail::KeyableTraits<T>::at( |
| val_, arg.splitKey())).format(arg, cb); |
| } |
| |
| private: |
| const T& val_; |
| }; |
| |
| template <class Container, class Value> |
| class FormatValue< |
| detail::DefaultValueWrapper<Container, Value>, |
| typename detail::KeyableTraits<Container>::enabled> { |
| public: |
| explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val) |
| : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| FormatValue<typename std::decay< |
| typename detail::KeyableTraits<Container>::value_type>::type>( |
| detail::KeyableTraits<Container>::at( |
| val_.container, |
| arg.splitKey(), |
| val_.defaultValue)).format(arg, cb); |
| } |
| |
| private: |
| const detail::DefaultValueWrapper<Container, Value>& val_; |
| }; |
| |
| // Partial specialization of FormatValue for pairs |
| template <class A, class B> |
| class FormatValue<std::pair<A, B>> { |
| public: |
| explicit FormatValue(const std::pair<A, B>& val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| int key = arg.splitIntKey(); |
| switch (key) { |
| case 0: |
| FormatValue<typename std::decay<A>::type>(val_.first).format(arg, cb); |
| break; |
| case 1: |
| FormatValue<typename std::decay<B>::type>(val_.second).format(arg, cb); |
| break; |
| default: |
| arg.error("invalid index for pair"); |
| } |
| } |
| |
| private: |
| const std::pair<A, B>& val_; |
| }; |
| |
| // Partial specialization of FormatValue for tuples |
| template <class... Args> |
| class FormatValue<std::tuple<Args...>> { |
| typedef std::tuple<Args...> Tuple; |
| public: |
| explicit FormatValue(const Tuple& val) : val_(val) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| int key = arg.splitIntKey(); |
| arg.enforce(key >= 0, "tuple index must be non-negative"); |
| doFormat(key, arg, cb); |
| } |
| |
| private: |
| static constexpr size_t valueCount = std::tuple_size<Tuple>::value; |
| |
| template <size_t K, class Callback> |
| typename std::enable_if<K == valueCount>::type |
| doFormatFrom(size_t i, FormatArg& arg, Callback& cb) const { |
| arg.enforce("tuple index out of range, max=", i); |
| } |
| |
| template <size_t K, class Callback> |
| typename std::enable_if<(K < valueCount)>::type |
| doFormatFrom(size_t i, FormatArg& arg, Callback& cb) const { |
| if (i == K) { |
| FormatValue<typename std::decay< |
| typename std::tuple_element<K, Tuple>::type>::type>( |
| std::get<K>(val_)).format(arg, cb); |
| } else { |
| doFormatFrom<K+1>(i, arg, cb); |
| } |
| } |
| |
| template <class Callback> |
| void doFormat(size_t i, FormatArg& arg, Callback& cb) const { |
| return doFormatFrom<0>(i, arg, cb); |
| } |
| |
| const Tuple& val_; |
| }; |
| |
| // Partial specialization of FormatValue for nested Formatters |
| template <bool containerMode, class... Args, |
| template <bool, class...> class F> |
| class FormatValue<F<containerMode, Args...>, |
| typename std::enable_if<detail::IsFormatter< |
| F<containerMode, Args...>>::value>::type> { |
| typedef typename F<containerMode, Args...>::BaseType FormatterValue; |
| |
| public: |
| explicit FormatValue(const FormatterValue& f) : f_(f) { } |
| |
| template <class FormatCallback> |
| void format(FormatArg& arg, FormatCallback& cb) const { |
| format_value::formatFormatter(f_, arg, cb); |
| } |
| private: |
| const FormatterValue& f_; |
| }; |
| |
| /** |
| * Formatter objects can be appended to strings, and therefore they're |
| * compatible with folly::toAppend and folly::to. |
| */ |
| template <class Tgt, class Derived, bool containerMode, class... Args> |
| typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend( |
| const BaseFormatter<Derived, containerMode, Args...>& value, Tgt* result) { |
| value.appendTo(*result); |
| } |
| |
| } // namespace folly |
| |
| #pragma GCC diagnostic pop |