Mark constructing an Unexpected as cold
[folly.git] / folly / FormatArg.h
1 /*
2  * Copyright 2017 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/Conv.h>
22 #include <folly/Likely.h>
23 #include <folly/Portability.h>
24 #include <folly/Range.h>
25
26 namespace folly {
27
28 class BadFormatArg : public std::invalid_argument {
29   using invalid_argument::invalid_argument;
30 };
31
32 [[noreturn]] void throwBadFormatArg(char const* msg);
33 [[noreturn]] void throwBadFormatArg(std::string const& msg);
34
35 /**
36  * Parsed format argument.
37  */
38 struct FormatArg {
39   /**
40    * Parse a format argument from a string.  Keeps a reference to the
41    * passed-in string -- does not copy the given characters.
42    */
43   explicit FormatArg(StringPiece sp)
44     : fullArgString(sp),
45       fill(kDefaultFill),
46       align(Align::DEFAULT),
47       sign(Sign::DEFAULT),
48       basePrefix(false),
49       thousandsSeparator(false),
50       trailingDot(false),
51       width(kDefaultWidth),
52       widthIndex(kNoIndex),
53       precision(kDefaultPrecision),
54       presentation(kDefaultPresentation),
55       nextKeyMode_(NextKeyMode::NONE) {
56     if (!sp.empty()) {
57       initSlow();
58     }
59   }
60
61   enum class Type {
62     INTEGER,
63     FLOAT,
64     OTHER
65   };
66   /**
67    * Validate the argument for the given type; throws on error.
68    */
69   void validate(Type type) const;
70
71   /**
72    * Throw an exception if the first argument is false.  The exception
73    * message will contain the argument string as well as any passed-in
74    * arguments to enforce, formatted using folly::to<std::string>.
75    */
76   template <typename... Args>
77   void enforce(bool v, Args&&... args) const {
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
208 template <typename... Args>
209 inline std::string FormatArg::errorStr(Args&&... args) const {
210   return to<std::string>(
211     "invalid format argument {", fullArgString, "}: ",
212     std::forward<Args>(args)...);
213 }
214
215 template <typename... Args>
216 [[noreturn]] inline void FormatArg::error(Args&&... args) const {
217   throwBadFormatArg(errorStr(std::forward<Args>(args)...));
218 }
219
220 template <bool emptyOk>
221 inline StringPiece FormatArg::splitKey() {
222   enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected");
223   return doSplitKey<emptyOk>();
224 }
225
226 template <bool emptyOk>
227 inline StringPiece FormatArg::doSplitKey() {
228   if (nextKeyMode_ == NextKeyMode::STRING) {
229     nextKeyMode_ = NextKeyMode::NONE;
230     if (!emptyOk) {  // static
231       enforce(!nextKey_.empty(), "non-empty key required");
232     }
233     return nextKey_;
234   }
235
236   if (key_.empty()) {
237     if (!emptyOk) {  // static
238       error("non-empty key required");
239     }
240     return StringPiece();
241   }
242
243   const char* b = key_.begin();
244   const char* e = key_.end();
245   const char* p;
246   if (e[-1] == ']') {
247     --e;
248     p = static_cast<const char*>(memchr(b, '[', size_t(e - b)));
249     enforce(p != nullptr, "unmatched ']'");
250   } else {
251     p = static_cast<const char*>(memchr(b, '.', size_t(e - b)));
252   }
253   if (p) {
254     key_.assign(p + 1, e);
255   } else {
256     p = e;
257     key_.clear();
258   }
259   if (!emptyOk) {  // static
260     enforce(b != p, "non-empty key required");
261   }
262   return StringPiece(b, p);
263 }
264
265 inline int FormatArg::splitIntKey() {
266   if (nextKeyMode_ == NextKeyMode::INT) {
267     nextKeyMode_ = NextKeyMode::NONE;
268     return nextIntKey_;
269   }
270   try {
271     return to<int>(doSplitKey<true>());
272   } catch (const std::out_of_range&) {
273     error("integer key required");
274     return 0;  // unreached
275   }
276 }
277
278 } // namespace folly