libfilezilla
format.hpp
Go to the documentation of this file.
1 #ifndef LIBFILEZILLA_FORMAT_HEADER
2 #define LIBFILEZILLA_FORMAT_HEADER
3 
4 #include "encode.hpp"
5 #include "string.hpp"
6 
7 #include <cstdlib>
8 #include <type_traits>
9 
10 #ifdef LFZ_FORMAT_DEBUG
11 #include <assert.h>
12 #define format_assert(pred) assert((pred))
13 #else
14 #define format_assert(pred)
15 #endif
16 
21 namespace fz {
22 
24 namespace detail {
25 
26 // Get flags
27 enum : char {
28  pad_0 = 1,
29  pad_blank = 2,
30  with_width = 4,
31  left_align = 8,
32  always_sign = 16,
33  thousands = 32
34 };
35 
36 struct field final {
37  size_t width{};
38  char flags{};
39  char type{};
40 
41  explicit operator bool() const { return type != 0; }
42 };
43 
44 template<typename Arg>
45 bool is_negative([[maybe_unused]] Arg && v)
46 {
47  if constexpr (std::is_signed_v<std::decay_t<Arg>>) {
48  return v < 0;
49  }
50  else {
51  return false;
52  }
53 }
54 
55 // Converts integral type to desired string type...
56 // ... basic case: simple unsigned value
57 template<typename String, bool Unsigned, typename Arg>
58 typename std::enable_if_t<std::is_integral_v<std::decay_t<Arg>> && !std::is_enum_v<std::decay_t<Arg>>, String> integral_to_string(field const& f, Arg && arg)
59 {
60  std::decay_t<Arg> v = arg;
61 
62  char lead{};
63 
64  format_assert(!Unsigned || !std::is_signed_v<std::decay_t<Arg>> || arg >= 0);
65 
66  if (is_negative(arg)) {
67  lead = '-';
68  }
69  else if (f.flags & always_sign) {
70  lead = '+';
71  }
72  else if (f.flags & pad_blank) {
73  lead = ' ';
74  }
75 
76  // max decimal digits in b-bit integer is floor((b-1) * log_10(2)) + 1 < b * 0.5 + 1
77  typename String::value_type buf[sizeof(v) * 4 + 1];
78  auto *const end = buf + sizeof(v) * 4 + 1;
79  auto *p = end;
80 
81  do {
82  int const mod = std::abs(static_cast<int>(v % 10));
83  *(--p) = '0' + mod;
84  v /= 10;
85  } while (v);
86 
87  auto width = f.width;
88  if (f.flags & with_width) {
89  if (lead && width > 0) {
90  --width;
91  }
92 
93  String ret;
94 
95  if (f.flags & pad_0) {
96  if (lead) {
97  ret += lead;
98  }
99  if (static_cast<size_t>(end - p) < width) {
100  ret.append(width - (end - p), '0');
101  }
102  ret.append(p, end);
103  }
104  else {
105  if (static_cast<size_t>(end - p) < width && !(f.flags & left_align)) {
106  ret.append(width - (end - p), ' ');
107  }
108  if (lead) {
109  ret += lead;
110  }
111  ret.append(p, end);
112  if (static_cast<size_t>(end - p) < width && f.flags & left_align) {
113  ret.append(width - (end - p), ' ');
114  }
115  }
116 
117  return ret;
118  }
119  else {
120  if (lead) {
121  *(--p) = lead;
122  }
123  return String(p, end);
124  }
125 }
126 
127 // ... for strongly typed enums
128 template<typename String, bool Unsigned, typename Arg>
129 typename std::enable_if_t<std::is_enum_v<std::decay_t<Arg>>, String> integral_to_string(field const& f, Arg && arg)
130 {
131  return integral_to_string<String, Unsigned>(f, static_cast<std::underlying_type_t<std::decay_t<Arg>>>(arg));
132 }
133 
134 // ... assert otherwise
135 template<typename String, bool Unsigned, typename Arg>
136 typename std::enable_if_t<!std::is_integral_v<std::decay_t<Arg>> && !std::is_enum_v<std::decay_t<Arg>>, String> integral_to_string(field const&, Arg &&)
137 {
138  format_assert(0);
139  return String();
140 }
141 
142 template<typename String, class Arg, typename = void>
143 struct has_toString : std::false_type {};
144 
145 template<typename String, class Arg>
146 struct has_toString<String, Arg, std::void_t<decltype(toString<String>(std::declval<Arg>()))>> : std::true_type {};
147 
148 template <int N>
149 struct argument
150 {
151  template <class Arg>
152  struct of_type
153  {
154  template <typename String>
155  static constexpr bool is_formattable_as = std::disjunction<
156  std::is_enum<std::decay_t<Arg>>,
157  std::is_arithmetic<std::decay_t<Arg>>,
158  std::is_pointer<std::decay_t<Arg>>,
159  std::is_same<String, std::decay_t<Arg>>,
160  has_toString<String, Arg>
161  >::value;
162  };
163 };
164 
165 // Converts integral type to hex string with desired string type
166 template<typename String, bool Lowercase, typename Arg>
167 String integral_to_hex_string(Arg && arg) noexcept
168 {
169  if constexpr (std::is_enum_v<std::decay_t<Arg>>) {
170  // Special handling for enum, cast to underlying type
171  return integral_to_hex_string<String, Lowercase>(static_cast<std::underlying_type_t<std::decay_t<Arg>>>(arg));
172  }
173  else if constexpr (std::is_signed_v<std::decay_t<Arg>>) {
174  return integral_to_hex_string<String, Lowercase>(static_cast<std::make_unsigned_t<std::decay_t<Arg>>>(arg));
175  }
176  else if constexpr (std::is_integral_v<std::decay_t<Arg>>) {
177  std::decay_t<Arg> v = arg;
178  typename String::value_type buf[sizeof(v) * 2];
179  auto* const end = buf + sizeof(v) * 2;
180  auto* p = end;
181 
182  do {
183  *(--p) = fz::int_to_hex_char<typename String::value_type, Lowercase>(v & 0xf);
184  v >>= 4;
185  } while (v);
186 
187  return String(p, end);
188  }
189  else {
190  format_assert(0);
191  return String();
192  }
193 }
194 
195 // Converts pointer to hex string
196 template<typename String, typename Arg>
197 String pointer_to_string(Arg&& arg) noexcept
198 {
199  if constexpr (std::is_pointer_v<std::decay_t<Arg>>) {
200  return String({'0', 'x'}) + integral_to_hex_string<String, true>(reinterpret_cast<uintptr_t>(arg));
201  }
202  else {
203  format_assert(0);
204  return String();
205  }
206 }
207 
208 template<typename String, typename Arg>
209 String char_to_string(Arg&& arg)
210 {
211  if constexpr (std::is_integral_v<std::decay_t<Arg>>) {
212  return String({static_cast<typename String::value_type>(static_cast<unsigned char>(arg))});
213  }
214  else {
215  format_assert(0);
216  return String();
217  }
218 }
219 
220 
221 template<typename String>
222 void pad_arg(String& s, field const& f)
223 {
224  if (f.flags & with_width && s.size() < f.width) {
225  if (f.flags & left_align) {
226  s += String(f.width - s.size(), ' ');
227  }
228  else {
229  s = String(f.width - s.size(), (f.flags & pad_0) ? '0' : ' ') + s;
230  }
231  }
232 }
233 
234 template<typename String, typename Arg>
235 String format_arg(field const& f, Arg&& arg)
236 {
237  String ret;
238  if (f.type == 's') {
239  if constexpr (std::is_same_v<String, std::decay_t<Arg>>) {
240  ret = arg;
241  }
242  else if constexpr (has_toString<String, Arg>::value) {
243  // Converts argument to string
244  // if toString(arg) is valid expression
245  ret = toString<String>(std::forward<Arg>(arg));
246  }
247  else {
248  // Otherwise assert
249  format_assert(0);
250  }
251  pad_arg(ret, f);
252  }
253  else if (f.type == 'd' || f.type == 'i') {
254  ret = integral_to_string<String, false>(f, std::forward<Arg>(arg));
255  }
256  else if (f.type == 'u') {
257  ret = integral_to_string<String, true>(f, std::forward<Arg>(arg));
258  }
259  else if (f.type == 'x') {
260  ret = integral_to_hex_string<String, true>(std::forward<Arg>(arg));
261  pad_arg(ret, f);
262  }
263  else if (f.type == 'X') {
264  ret = integral_to_hex_string<String, false>(std::forward<Arg>(arg));
265  pad_arg(ret, f);
266  }
267  else if (f.type == 'p') {
268  ret = pointer_to_string<String>(std::forward<Arg>(arg));
269  pad_arg(ret, f);
270  }
271  else if (f.type == 'c') {
272  ret = char_to_string<String>(std::forward<Arg>(arg));
273  }
274  else {
275  format_assert(0);
276  }
277  return ret;
278 }
279 
280 template<typename String, typename... Args>
281 String extract_arg(field const&, size_t)
282 {
283  return String();
284 }
285 
286 
287 template<typename String, typename Arg, typename... Args>
288 String extract_arg(field const& f, size_t arg_n, Arg&& arg, Args&&...args)
289 {
290  String ret;
291 
292  if (!arg_n) {
293  ret = format_arg<String>(f, std::forward<Arg>(arg));
294  }
295  else {
296  ret = extract_arg<String>(f, arg_n - 1, std::forward<Args>(args)...);
297  }
298 
299  return ret;
300 }
301 
302 template<typename InString, typename OutString>
303 field get_field(InString const& fmt, typename InString::size_type & pos, size_t& arg_n, OutString & ret)
304 {
305  field f;
306  if (++pos >= fmt.size()) {
307  format_assert(0);
308  return f;
309  }
310 
311  // Get literal percent out of the way
312  if (fmt[pos] == '%') {
313  ret += '%';
314  ++pos;
315  return f;
316  }
317 
318 parse_start:
319  while (true) {
320  if (fmt[pos] == '0') {
321  f.flags |= pad_0;
322  }
323  else if (fmt[pos] == ' ') {
324  f.flags |= pad_blank;
325  }
326  else if (fmt[pos] == '-') {
327  f.flags &= ~pad_0;
328  f.flags |= left_align;
329  }
330  else if (fmt[pos] == '+') {
331  f.flags &= ~pad_blank;
332  f.flags |= always_sign;
333  }
334  else if (fmt[pos] == '\'') {
335  f.flags |= thousands;
336  }
337  else {
338  break;
339  }
340  if (++pos >= fmt.size()) {
341  format_assert(0);
342  return f;
343  }
344  }
345 
346  // Field width
347  while (fmt[pos] >= '0' && fmt[pos] <= '9') {
348  f.flags |= with_width;
349  f.width *= 10;
350  f.width += fmt[pos] - '0';
351  if (++pos >= fmt.size()) {
352  format_assert(0);
353  return f;
354  }
355  }
356  if (f.width > 10000) {
357  format_assert(0);
358  f.width = 10000;
359  }
360 
361  if (fmt[pos] == '$') {
362  // Positional argument, start over
363  arg_n = f.width - 1;
364  if (++pos >= fmt.size()) {
365  format_assert(0);
366  return f;
367  }
368  goto parse_start;
369  }
370 
371  // Ignore length modifier
372  while (true) {
373  auto c = fmt[pos];
374  if (c == 'h' || c == 'l' || c == 'L' || c == 'j' || c == 'z' || c == 't') {
375  if (++pos >= fmt.size()) {
376  format_assert(0);
377  return f;
378  }
379  }
380  else {
381  break;
382  }
383  }
384 
385  f.type = static_cast<char>(fmt[pos++]);
386  return f;
387 }
388 
389 template<typename String, typename Arg, int N>
390 constexpr bool check_argument()
391 {
392  static_assert(
393  argument<N>::template of_type<Arg>::template is_formattable_as<String>,
394  "Argument cannot be formatted by fz::sprintf()"
395  );
396 
397  return argument<N>::template of_type<Arg>::template is_formattable_as<String>;
398 }
399 
400 template<typename String, typename... Args, std::size_t... Is>
401 constexpr bool check_arguments(std::index_sequence<Is...>)
402 {
403  return (check_argument<String, Args, Is>() && ...);
404 }
405 
406 template<typename InString, typename CharType = typename InString::value_type, typename OutString = std::basic_string<CharType>, typename... Args>
407 OutString do_sprintf(InString const& fmt, Args&&... args)
408 {
409  OutString ret;
410 
411  // Find % characters
412  typename InString::size_type start = 0, pos;
413 
414  size_t arg_n{};
415  while ((pos = fmt.find('%', start)) != InString::npos) {
416 
417  // Copy segment preceding the %
418  ret += fmt.substr(start, pos - start);
419 
420  field f = detail::get_field(fmt, pos, arg_n, ret);
421  if (f) {
422  format_assert(arg_n < sizeof...(args));
423  ret += detail::extract_arg<OutString>(f, arg_n++, std::forward<Args>(args)...);
424  }
425 
426  start = pos;
427  }
428 
429  // Copy remainder of string
430  ret += fmt.substr(start);
431 
432  return ret;
433 }
434 }
436 
459 template<typename... Args>
460 std::string sprintf(std::string_view const& fmt, Args&&... args)
461 {
462  detail::check_arguments<std::string, Args...>(std::index_sequence_for<Args...>());
463 
464  return detail::do_sprintf(fmt, std::forward<Args>(args)...);
465 }
466 
467 template<typename... Args>
468 std::wstring sprintf(std::wstring_view const& fmt, Args&&... args)
469 {
470  detail::check_arguments<std::wstring, Args...>(std::index_sequence_for<Args...>());
471 
472  return detail::do_sprintf(fmt, std::forward<Args>(args)...);
473 }
474 
475 }
476 
477 #endif
Definition: impersonation.hpp:100
std::string sprintf(std::string_view const &fmt, Args &&...args)
A simple type-safe sprintf replacement.
Definition: format.hpp:460
Functions to encode/decode strings.
String types and assorted functions.
The namespace used by libfilezilla.
Definition: apply.hpp:17