1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
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 
28 namespace folly {
29 
30 class FOLLY_EXPORT BadFormatArg : public std::invalid_argument {
31   using invalid_argument::invalid_argument;
32 };
33 
34 /**
35  * Parsed format argument.
36  */
37 struct 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    */
FormatArgFormatArg42   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>
enforceFormatArg76   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    */
keyEmptyFormatArg170   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 
setNextIntKeyFormatArg180   void setNextIntKey(int val) {
181     assert(nextKeyMode_ == NextKeyMode::NONE);
182     nextKeyMode_ = NextKeyMode::INT;
183     nextIntKey_ = val;
184   }
185 
setNextKeyFormatArg186   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>
errorStr(Args &&...args)209 inline 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 
217 template <typename... Args>
error(Args &&...args)218 [[noreturn]] inline void FormatArg::error(Args&&... args) const {
219   throw_exception<BadFormatArg>(errorStr(std::forward<Args>(args)...));
220 }
221 
222 template <bool emptyOk>
splitKey()223 inline StringPiece FormatArg::splitKey() {
224   enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected");
225   return doSplitKey<emptyOk>();
226 }
227 
228 template <bool emptyOk>
doSplitKey()229 inline 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 
splitIntKey()267 inline 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