/* * Copyright 2016 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. */ #pragma once #include #include #include #include #include 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. */ template void enforce(bool v, Args&&... args) const { if (UNLIKELY(!v)) { error(std::forward(args)...); } } template std::string errorStr(Args&&... args) const; template [[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 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 StringPiece doSplitKey(); StringPiece key_; int nextIntKey_; StringPiece nextKey_; enum class NextKeyMode { NONE, INT, STRING, }; NextKeyMode nextKeyMode_; }; template inline std::string FormatArg::errorStr(Args&&... args) const { return to( "invalid format argument {", fullArgString, "}: ", std::forward(args)...); } template [[noreturn]] inline void FormatArg::error(Args&&... args) const { throw BadFormatArg(errorStr(std::forward(args)...)); } template inline StringPiece FormatArg::splitKey() { enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected"); return doSplitKey(); } template 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(memchr(b, '[', e - b)); enforce(p, "unmatched ']'"); } else { p = static_cast(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(doSplitKey()); } catch (const std::out_of_range& e) { error("integer key required"); return 0; // unreached } } } // namespace folly