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