blob: c4d94736aa6b39c25896ee17c3dc673386c9702b [file] [log] [blame]
/*
* 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_FORMATARG_H_
#define FOLLY_FORMATARG_H_
#include <stdexcept>
#include <folly/Conv.h>
#include <folly/Likely.h>
#include <folly/Portability.h>
#include <folly/Range.h>
namespace folly {
class BadFormatArg : public std::invalid_argument {
public:
explicit BadFormatArg(const std::string& msg)
: std::invalid_argument(msg) {}
};
/**
* Parsed format argument.
*/
struct FormatArg {
/**
* Parse a format argument from a string. Keeps a reference to the
* passed-in string -- does not copy the given characters.
*/
explicit FormatArg(StringPiece sp)
: fullArgString(sp),
fill(kDefaultFill),
align(Align::DEFAULT),
sign(Sign::DEFAULT),
basePrefix(false),
thousandsSeparator(false),
trailingDot(false),
width(kDefaultWidth),
widthIndex(kNoIndex),
precision(kDefaultPrecision),
presentation(kDefaultPresentation),
nextKeyMode_(NextKeyMode::NONE) {
if (!sp.empty()) {
initSlow();
}
}
enum class Type {
INTEGER,
FLOAT,
OTHER
};
/**
* Validate the argument for the given type; throws on error.
*/
void validate(Type type) const;
/**
* Throw an exception if the first argument is false. The exception
* message will contain the argument string as well as any passed-in
* arguments to enforce, formatted using folly::to<std::string>.
*/
template <typename... Args>
void enforce(bool v, Args&&... args) const {
if (UNLIKELY(!v)) {
error(std::forward<Args>(args)...);
}
}
template <typename... Args>
std::string errorStr(Args&&... args) const;
template <typename... Args>
FOLLY_NORETURN void error(Args&&... args) const;
/**
* Full argument string, as passed in to the constructor.
*/
StringPiece fullArgString;
/**
* Fill
*/
static constexpr char kDefaultFill = '\0';
char fill;
/**
* Alignment
*/
enum class Align : uint8_t {
DEFAULT,
LEFT,
RIGHT,
PAD_AFTER_SIGN,
CENTER,
INVALID
};
Align align;
/**
* Sign
*/
enum class Sign : uint8_t {
DEFAULT,
PLUS_OR_MINUS,
MINUS,
SPACE_OR_MINUS,
INVALID
};
Sign sign;
/**
* Output base prefix (0 for octal, 0x for hex)
*/
bool basePrefix;
/**
* Output thousands separator (comma)
*/
bool thousandsSeparator;
/**
* Force a trailing decimal on doubles which could be rendered as ints
*/
bool trailingDot;
/**
* Field width and optional argument index
*/
static constexpr int kDefaultWidth = -1;
static constexpr int kDynamicWidth = -2;
static constexpr int kNoIndex = -1;
int width;
int widthIndex;
/**
* Precision
*/
static constexpr int kDefaultPrecision = -1;
int precision;
/**
* Presentation
*/
static constexpr char kDefaultPresentation = '\0';
char presentation;
/**
* Split a key component from "key", which must be non-empty (an exception
* is thrown otherwise).
*/
template <bool emptyOk=false>
StringPiece splitKey();
/**
* Is the entire key empty?
*/
bool keyEmpty() const {
return nextKeyMode_ == NextKeyMode::NONE && key_.empty();
}
/**
* Split an key component from "key", which must be non-empty and a valid
* integer (an exception is thrown otherwise).
*/
int splitIntKey();
void setNextIntKey(int val) {
assert(nextKeyMode_ == NextKeyMode::NONE);
nextKeyMode_ = NextKeyMode::INT;
nextIntKey_ = val;
}
void setNextKey(StringPiece val) {
assert(nextKeyMode_ == NextKeyMode::NONE);
nextKeyMode_ = NextKeyMode::STRING;
nextKey_ = val;
}
private:
void initSlow();
template <bool emptyOk>
StringPiece doSplitKey();
StringPiece key_;
int nextIntKey_;
StringPiece nextKey_;
enum class NextKeyMode {
NONE,
INT,
STRING,
};
NextKeyMode nextKeyMode_;
};
template <typename... Args>
inline std::string FormatArg::errorStr(Args&&... args) const {
return to<std::string>(
"invalid format argument {", fullArgString, "}: ",
std::forward<Args>(args)...);
}
template <typename... Args>
inline void FormatArg::error(Args&&... args) const {
throw BadFormatArg(errorStr(std::forward<Args>(args)...));
}
template <bool emptyOk>
inline StringPiece FormatArg::splitKey() {
enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected");
return doSplitKey<emptyOk>();
}
template <bool emptyOk>
inline StringPiece FormatArg::doSplitKey() {
if (nextKeyMode_ == NextKeyMode::STRING) {
nextKeyMode_ = NextKeyMode::NONE;
if (!emptyOk) { // static
enforce(!nextKey_.empty(), "non-empty key required");
}
return nextKey_;
}
if (key_.empty()) {
if (!emptyOk) { // static
error("non-empty key required");
}
return StringPiece();
}
const char* b = key_.begin();
const char* e = key_.end();
const char* p;
if (e[-1] == ']') {
--e;
p = static_cast<const char*>(memchr(b, '[', e - b));
enforce(p, "unmatched ']'");
} else {
p = static_cast<const char*>(memchr(b, '.', e - b));
}
if (p) {
key_.assign(p + 1, e);
} else {
p = e;
key_.clear();
}
if (!emptyOk) { // static
enforce(b != p, "non-empty key required");
}
return StringPiece(b, p);
}
inline int FormatArg::splitIntKey() {
if (nextKeyMode_ == NextKeyMode::INT) {
nextKeyMode_ = NextKeyMode::NONE;
return nextIntKey_;
}
try {
return to<int>(doSplitKey<true>());
} catch (const std::out_of_range& e) {
error("integer key required");
return 0; // unreached
}
}
} // namespace folly
#endif /* FOLLY_FORMATARG_H_ */