| /* |
| * 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_ */ |