1/*
2 * Copyright 2012-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include <stdexcept>
20
21#include <folly/CPortability.h>
22#include <folly/Conv.h>
23#include <folly/Likely.h>
24#include <folly/Portability.h>
25#include <folly/Range.h>
26#include <folly/lang/Exception.h>
27
28namespace folly {
29
30class FOLLY_EXPORT BadFormatArg : public std::invalid_argument {
31 using invalid_argument::invalid_argument;
32};
33
34/**
35 * Parsed format argument.
36 */
37struct FormatArg {
38 /**
39 * Parse a format argument from a string. Keeps a reference to the
40 * passed-in string -- does not copy the given characters.
41 */
42 explicit FormatArg(StringPiece sp)
43 : fullArgString(sp),
44 fill(kDefaultFill),
45 align(Align::DEFAULT),
46 sign(Sign::DEFAULT),
47 basePrefix(false),
48 thousandsSeparator(false),
49 trailingDot(false),
50 width(kDefaultWidth),
51 widthIndex(kNoIndex),
52 precision(kDefaultPrecision),
53 presentation(kDefaultPresentation),
54 nextKeyMode_(NextKeyMode::NONE) {
55 if (!sp.empty()) {
56 initSlow();
57 }
58 }
59
60 enum class Type {
61 INTEGER,
62 FLOAT,
63 OTHER,
64 };
65 /**
66 * Validate the argument for the given type; throws on error.
67 */
68 void validate(Type type) const;
69
70 /**
71 * Throw an exception if the first argument is false. The exception
72 * message will contain the argument string as well as any passed-in
73 * arguments to enforce, formatted using folly::to<std::string>.
74 */
75 template <typename Check, typename... Args>
76 void enforce(Check const& v, Args&&... args) const {
77 static_assert(std::is_constructible<bool, Check>::value, "not castable");
78 if (UNLIKELY(!v)) {
79 error(std::forward<Args>(args)...);
80 }
81 }
82
83 template <typename... Args>
84 std::string errorStr(Args&&... args) const;
85 template <typename... Args>
86 [[noreturn]] void error(Args&&... args) const;
87
88 /**
89 * Full argument string, as passed in to the constructor.
90 */
91 StringPiece fullArgString;
92
93 /**
94 * Fill
95 */
96 static constexpr char kDefaultFill = '\0';
97 char fill;
98
99 /**
100 * Alignment
101 */
102 enum class Align : uint8_t {
103 DEFAULT,
104 LEFT,
105 RIGHT,
106 PAD_AFTER_SIGN,
107 CENTER,
108 INVALID,
109 };
110 Align align;
111
112 /**
113 * Sign
114 */
115 enum class Sign : uint8_t {
116 DEFAULT,
117 PLUS_OR_MINUS,
118 MINUS,
119 SPACE_OR_MINUS,
120 INVALID,
121 };
122 Sign sign;
123
124 /**
125 * Output base prefix (0 for octal, 0x for hex)
126 */
127 bool basePrefix;
128
129 /**
130 * Output thousands separator (comma)
131 */
132 bool thousandsSeparator;
133
134 /**
135 * Force a trailing decimal on doubles which could be rendered as ints
136 */
137 bool trailingDot;
138
139 /**
140 * Field width and optional argument index
141 */
142 static constexpr int kDefaultWidth = -1;
143 static constexpr int kDynamicWidth = -2;
144 static constexpr int kNoIndex = -1;
145 int width;
146 int widthIndex;
147
148 /**
149 * Precision
150 */
151 static constexpr int kDefaultPrecision = -1;
152 int precision;
153
154 /**
155 * Presentation
156 */
157 static constexpr char kDefaultPresentation = '\0';
158 char presentation;
159
160 /**
161 * Split a key component from "key", which must be non-empty (an exception
162 * is thrown otherwise).
163 */
164 template <bool emptyOk = false>
165 StringPiece splitKey();
166
167 /**
168 * Is the entire key empty?
169 */
170 bool keyEmpty() const {
171 return nextKeyMode_ == NextKeyMode::NONE && key_.empty();
172 }
173
174 /**
175 * Split an key component from "key", which must be non-empty and a valid
176 * integer (an exception is thrown otherwise).
177 */
178 int splitIntKey();
179
180 void setNextIntKey(int val) {
181 assert(nextKeyMode_ == NextKeyMode::NONE);
182 nextKeyMode_ = NextKeyMode::INT;
183 nextIntKey_ = val;
184 }
185
186 void setNextKey(StringPiece val) {
187 assert(nextKeyMode_ == NextKeyMode::NONE);
188 nextKeyMode_ = NextKeyMode::STRING;
189 nextKey_ = val;
190 }
191
192 private:
193 void initSlow();
194 template <bool emptyOk>
195 StringPiece doSplitKey();
196
197 StringPiece key_;
198 int nextIntKey_;
199 StringPiece nextKey_;
200 enum class NextKeyMode {
201 NONE,
202 INT,
203 STRING,
204 };
205 NextKeyMode nextKeyMode_;
206};
207
208template <typename... Args>
209inline std::string FormatArg::errorStr(Args&&... args) const {
210 return to<std::string>(
211 "invalid format argument {",
212 fullArgString,
213 "}: ",
214 std::forward<Args>(args)...);
215}
216
217template <typename... Args>
218[[noreturn]] inline void FormatArg::error(Args&&... args) const {
219 throw_exception<BadFormatArg>(errorStr(std::forward<Args>(args)...));
220}
221
222template <bool emptyOk>
223inline StringPiece FormatArg::splitKey() {
224 enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected");
225 return doSplitKey<emptyOk>();
226}
227
228template <bool emptyOk>
229inline StringPiece FormatArg::doSplitKey() {
230 if (nextKeyMode_ == NextKeyMode::STRING) {
231 nextKeyMode_ = NextKeyMode::NONE;
232 if (!emptyOk) { // static
233 enforce(!nextKey_.empty(), "non-empty key required");
234 }
235 return nextKey_;
236 }
237
238 if (key_.empty()) {
239 if (!emptyOk) { // static
240 error("non-empty key required");
241 }
242 return StringPiece();
243 }
244
245 const char* b = key_.begin();
246 const char* e = key_.end();
247 const char* p;
248 if (e[-1] == ']') {
249 --e;
250 p = static_cast<const char*>(memchr(b, '[', size_t(e - b)));
251 enforce(p != nullptr, "unmatched ']'");
252 } else {
253 p = static_cast<const char*>(memchr(b, '.', size_t(e - b)));
254 }
255 if (p) {
256 key_.assign(p + 1, e);
257 } else {
258 p = e;
259 key_.clear();
260 }
261 if (!emptyOk) { // static
262 enforce(b != p, "non-empty key required");
263 }
264 return StringPiece(b, p);
265}
266
267inline int FormatArg::splitIntKey() {
268 if (nextKeyMode_ == NextKeyMode::INT) {
269 nextKeyMode_ = NextKeyMode::NONE;
270 return nextIntKey_;
271 }
272 auto result = tryTo<int>(doSplitKey<true>());
273 enforce(result, "integer key required");
274 return *result;
275}
276
277} // namespace folly
278