/*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#error This file may only be included from Format.h.
#endif
-#include "folly/Exception.h"
-#include "folly/Traits.h"
+#include <folly/Exception.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
-
-template <bool containerMode, class... Args>
-Formatter<containerMode, Args...>::Formatter(StringPiece str, Args&&... args)
- : str_(str),
- values_(FormatValue<typename std::decay<Args>::type>(
- std::forward<Args>(args))...) {
+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 <bool containerMode, class... Args>
+template <class Derived, bool containerMode, class... Args>
+void BaseFormatter<Derived, containerMode, Args...>::handleFormatStrError()
+ const {
+ if (crashOnError_) {
+ LOG(FATAL) << "folly::format: bad format string \"" << str_ << "\": " <<
+ folly::exceptionStr(std::current_exception());
+ }
+ throw;
+}
+
+template <class Derived, bool containerMode, class... Args>
template <class Output>
-void Formatter<containerMode, Args...>::operator()(Output& out) const {
+void BaseFormatter<Derived, containerMode, Args...>::operator()(Output& out)
+ const {
+ // Catch BadFormatArg and range_error exceptions, and call
+ // handleFormatStrError().
+ //
+ // These exception types indicate a problem with the format string. Most
+ // format strings are string literals specified by the programmer. If they
+ // have a problem, this is usually a programmer bug. We want to crash to
+ // ensure that these are found early on during development.
+ //
+ // BadFormatArg is thrown by the Format.h code, while range_error is thrown
+ // by Conv.h, which is used in several places in our format string
+ // processing.
+ //
+ // (Note: This behavior is slightly dangerous. If the Output object throws a
+ // BadFormatArg or a range_error, we will also crash the program, even if it
+ // wasn't an issue with the format string. This seems highly unlikely
+ // though, and none of our current Output objects can throw these errors.)
+ //
+ // We also throw out_of_range errors if the format string references an
+ // argument that isn't present (or a key that isn't present in one of the
+ // argument containers). However, at the moment we don't crash on these
+ // errors, as it is likely that the container is dynamic at runtime.
+ try {
+ appendOutput(out);
+ } catch (const BadFormatArg& ex) {
+ handleFormatStrError();
+ } catch (const std::range_error& ex) {
+ handleFormatStrError();
+ }
+}
+
+template <class Derived, bool containerMode, class... Args>
+template <class Output>
+void BaseFormatter<Derived, containerMode, Args...>::appendOutput(Output& out)
+ const {
auto p = str_.begin();
auto end = str_.end();
out(StringPiece(p, q));
p = q;
- CHECK(p != end && *p == '}') << "single '}' in format string";
+ if (p == end || *p != '}') {
+ throw BadFormatArg("folly::format: single '}' in format string");
+ }
++p;
}
};
outputString(StringPiece(p, q));
p = q + 1;
- CHECK(p != end) << "'{' at end of format string";
+ if (p == end) {
+ throw BadFormatArg("folly::format: '}' at end of format string");
+ }
// "{{" -> "{"
if (*p == '{') {
// Format string
q = static_cast<const char*>(memchr(p, '}', end - p));
- CHECK(q != end) << "missing ending '}'";
+ if (q == nullptr) {
+ throw BadFormatArg("folly::format: missing ending '}'");
+ }
FormatArg arg(StringPiece(p, q));
p = q + 1;
try {
argIndex = to<int>(piece);
} catch (const std::out_of_range& e) {
- LOG(FATAL) << "argument index must be integer";
+ arg.error("argument index must be integer");
}
- CHECK(argIndex >= 0)
- << arg.errorStr("argument index must be non-negative");
+ arg.enforce(argIndex >= 0, "argument index must be non-negative");
hasExplicitArgIndex = true;
}
}
- CHECK(!hasDefaultArgIndex || !hasExplicitArgIndex)
- << "may not have both default and explicit arg indexes";
+ if (hasDefaultArgIndex && hasExplicitArgIndex) {
+ throw BadFormatArg(
+ "folly::format: may not have both default and explicit arg indexes");
+ }
doFormat(argIndex, arg, out);
}
}
-template <bool containerMode, class... Args>
-void writeTo(FILE* fp, const Formatter<containerMode, Args...>& formatter) {
+template <class Derived, bool containerMode, class... Args>
+void writeTo(FILE* fp,
+ const BaseFormatter<Derived, containerMode, Args...>& formatter) {
auto writer = [fp] (StringPiece sp) {
ssize_t n = fwrite(sp.data(), 1, sp.size(), fp);
if (n < sp.size()) {
int padRemaining = 0;
if (arg.width != FormatArg::kDefaultWidth && val.size() < arg.width) {
char fill = arg.fill == FormatArg::kDefaultFill ? ' ' : arg.fill;
- int padChars = arg.width - val.size();
+ int padChars = static_cast<int> (arg.width - val.size());
memset(padBuf, fill, std::min(padBufSize, padChars));
switch (arg.align) {
format_value::formatString(val, arg, cb);
}
-template <class FormatCallback, bool containerMode, class... Args>
-void formatFormatter(const Formatter<containerMode, Args...>& formatter,
- FormatArg& arg,
- FormatCallback& 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
uval = val_;
sign = '\0';
- CHECK(arg.sign == FormatArg::Sign::DEFAULT)
- << arg.errorStr("sign specifications not allowed for unsigned values");
+ arg.enforce(arg.sign == FormatArg::Sign::DEFAULT,
+ "sign specifications not allowed for unsigned values");
}
// max of:
switch (presentation) {
case 'n': // TODO(tudorb): locale awareness?
case 'd':
- CHECK(!arg.basePrefix)
- << arg.errorStr("base prefix not allowed with '", presentation,
- "' specifier");
+ arg.enforce(!arg.basePrefix,
+ "base prefix not allowed with '", presentation,
+ "' specifier");
if (arg.thousandsSeparator) {
useSprintf("%'ju");
} else {
}
break;
case 'c':
- CHECK(!arg.basePrefix)
- << arg.errorStr("base prefix not allowed with '", presentation,
- "' specifier");
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ 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':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ 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) {
}
break;
case 'x':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufEnd = valBuf + valBufSize - 1;
valBufBegin = valBuf + detail::uintToHexLower(valBuf, valBufSize - 1,
uval);
}
break;
case 'X':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufEnd = valBuf + valBufSize - 1;
valBufBegin = valBuf + detail::uintToHexUpper(valBuf, valBufSize - 1,
uval);
break;
case 'b':
case 'B':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufEnd = valBuf + valBufSize - 1;
valBufBegin = valBuf + detail::uintToBinary(valBuf, valBufSize - 1,
uval);
}
break;
default:
- LOG(FATAL) << arg.errorStr("invalid specifier '", presentation, "'");
+ arg.error("invalid specifier '", presentation, "'");
}
if (sign) {
DoubleToStringConverter::kMaxFixedDigitsAfterPoint),
(8 + DoubleToStringConverter::kMaxExponentialDigits),
(7 + DoubleToStringConverter::kMaxPrecisionDigits)})];
- StringBuilder builder(buf + 1, sizeof(buf) - 1);
+ StringBuilder builder(buf + 1, static_cast<int> (sizeof(buf) - 1));
char plusSign;
switch (arg.sign) {
break;
};
+ auto flags =
+ DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN |
+ (arg.trailingDot ? DoubleToStringConverter::EMIT_TRAILING_DECIMAL_POINT
+ : 0);
+
double val = val_;
switch (arg.presentation) {
case '%':
DoubleToStringConverter::kMaxFixedDigitsAfterPoint) {
arg.precision = DoubleToStringConverter::kMaxFixedDigitsAfterPoint;
}
- DoubleToStringConverter conv(
- DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN,
- infinitySymbol,
- nanSymbol,
- exponentSymbol,
- -4, arg.precision,
- 0, 0);
+ DoubleToStringConverter conv(flags,
+ infinitySymbol,
+ nanSymbol,
+ exponentSymbol,
+ -4,
+ arg.precision,
+ 0,
+ 0);
arg.enforce(conv.ToFixed(val, arg.precision, &builder),
"fixed double conversion failed");
}
arg.precision = DoubleToStringConverter::kMaxExponentialDigits;
}
- DoubleToStringConverter conv(
- DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN,
- infinitySymbol,
- nanSymbol,
- exponentSymbol,
- -4, arg.precision,
- 0, 0);
- CHECK(conv.ToExponential(val, arg.precision, &builder));
+ 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
DoubleToStringConverter::kMaxPrecisionDigits) {
arg.precision = DoubleToStringConverter::kMaxPrecisionDigits;
}
- DoubleToStringConverter conv(
- DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN,
- infinitySymbol,
- nanSymbol,
- exponentSymbol,
- -4, arg.precision,
- 0, 0);
- CHECK(conv.ToShortest(val, &builder));
+ DoubleToStringConverter conv(flags,
+ infinitySymbol,
+ nanSymbol,
+ exponentSymbol,
+ -4,
+ arg.precision,
+ 0,
+ 0);
+ arg.enforce(conv.ToShortest(val, &builder));
}
break;
default:
- LOG(FATAL) << arg.errorStr("invalid specifier '", arg.presentation, "'");
+ arg.error("invalid specifier '", arg.presentation, "'");
}
int len = builder.position();
void format(FormatArg& arg, FormatCallback& cb) const {
if (arg.keyEmpty()) {
arg.validate(FormatArg::Type::OTHER);
- CHECK(arg.presentation == FormatArg::kDefaultPresentation ||
- arg.presentation == 's')
- << arg.errorStr("invalid specifier '", arg.presentation, "'");
+ 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);
template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
arg.validate(FormatArg::Type::OTHER);
- CHECK(arg.presentation == FormatArg::kDefaultPresentation)
- << arg.errorStr("invalid specifier '", arg.presentation, "'");
+ arg.enforce(arg.presentation == FormatArg::kDefaultPresentation,
+ "invalid specifier '", arg.presentation, "'");
format_value::formatString("(null)", arg, cb);
}
};
} else {
// Print as a pointer, in hex.
arg.validate(FormatArg::Type::OTHER);
- CHECK(arg.presentation == FormatArg::kDefaultPresentation)
- << arg.errorStr("invalid specifier '", arg.presentation, "'");
+ arg.enforce(arg.presentation == FormatArg::kDefaultPresentation,
+ "invalid specifier '", arg.presentation, "'");
arg.basePrefix = true;
arg.presentation = 'x';
if (arg.align == FormatArg::Align::DEFAULT) {
public:
template <class FormatCallback>
static void formatOrFail(T& value, FormatArg& arg, FormatCallback& cb) {
- LOG(FATAL) << arg.errorStr("No formatter available for this type");
+ arg.error("No formatter available for this type");
}
};
static const value_type& at(const C& c, int idx) {
return c.at(idx);
}
+
+ static const value_type& at(const C& c, int idx,
+ const value_type& dflt) {
+ return (idx >= 0 && idx < c.size()) ? c.at(idx) : dflt;
+ }
};
// Base class for associative types (maps)
static const value_type& at(const C& c, int idx) {
return c.at(static_cast<typename C::key_type>(idx));
}
+ static const value_type& at(const C& c, int idx,
+ const value_type& dflt) {
+ auto pos = c.find(static_cast<typename C::key_type>(idx));
+ return pos != c.end() ? pos->second : dflt;
+ }
};
// std::array
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
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
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>> {
FormatValue<typename std::decay<B>::type>(val_.second).format(arg, cb);
break;
default:
- LOG(FATAL) << arg.errorStr("invalid index for pair");
+ arg.error("invalid index for pair");
}
}
template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
int key = arg.splitIntKey();
- CHECK(key >= 0) << arg.errorStr("tuple index must be non-negative");
+ arg.enforce(key >= 0, "tuple index must be non-negative");
doFormat(key, arg, cb);
}
template <size_t K, class Callback>
typename std::enable_if<K == valueCount>::type
doFormatFrom(size_t i, FormatArg& arg, Callback& cb) const {
- LOG(FATAL) << arg.errorStr("tuple index out of range, max=", i);
+ arg.enforce("tuple index out of range, max=", i);
}
template <size_t K, class Callback>
};
// Partial specialization of FormatValue for nested Formatters
-template <bool containerMode, class... Args>
-class FormatValue<Formatter<containerMode, Args...>, void> {
- typedef Formatter<containerMode, Args...> FormatterValue;
+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) { }
* Formatter objects can be appended to strings, and therefore they're
* compatible with folly::toAppend and folly::to.
*/
-template <class Tgt, bool containerMode, class... Args>
-typename std::enable_if<
- detail::IsSomeString<Tgt>::value>::type
-toAppend(const Formatter<containerMode, Args...>& value, Tgt * result) {
+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