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 // Converts integral type to hex string with desired string type
148 template<typename String, bool Lowercase, typename Arg>
149 String integral_to_hex_string(Arg && arg) noexcept
150 {
151  if constexpr (std::is_enum_v<std::decay_t<Arg>>) {
152  // Special handling for enum, cast to underlying type
153  return integral_to_hex_string<String, Lowercase>(static_cast<std::underlying_type_t<std::decay_t<Arg>>>(arg));
154  }
155  else if constexpr (std::is_signed_v<std::decay_t<Arg>>) {
156  return integral_to_hex_string<String, Lowercase>(static_cast<std::make_unsigned_t<std::decay_t<Arg>>>(arg));
157  }
158  else if constexpr (std::is_integral_v<std::decay_t<Arg>>) {
159  std::decay_t<Arg> v = arg;
160  typename String::value_type buf[sizeof(v) * 2];
161  auto* const end = buf + sizeof(v) * 2;
162  auto* p = end;
163 
164  do {
165  *(--p) = fz::int_to_hex_char<typename String::value_type, Lowercase>(v & 0xf);
166  v >>= 4;
167  } while (v);
168 
169  return String(p, end);
170  }
171  else {
172  format_assert(0);
173  return String();
174  }
175 }
176 
177 // Converts pointer to hex string
178 template<typename String, typename Arg>
179 String pointer_to_string(Arg&& arg) noexcept
180 {
181  if constexpr (std::is_pointer_v<std::decay_t<Arg>>) {
182  return String({'0', 'x'}) + integral_to_hex_string<String, true>(reinterpret_cast<uintptr_t>(arg));
183  }
184  else {
185  format_assert(0);
186  return String();
187  }
188 }
189 
190 template<typename String, typename Arg>
191 String char_to_string(Arg&& arg)
192 {
193  if constexpr (std::is_integral_v<std::decay_t<Arg>>) {
194  return String({static_cast<typename String::value_type>(static_cast<unsigned char>(arg))});
195  }
196  else {
197  format_assert(0);
198  return String();
199  }
200 }
201 
202 
203 template<typename String>
204 void pad_arg(String& s, field const& f)
205 {
206  if (f.flags & with_width && s.size() < f.width) {
207  if (f.flags & left_align) {
208  s += String(f.width - s.size(), ' ');
209  }
210  else {
211  s = String(f.width - s.size(), (f.flags & pad_0) ? '0' : ' ') + s;
212  }
213  }
214 }
215 
216 template<typename String, typename Arg>
217 String format_arg(field const& f, Arg&& arg)
218 {
219  String ret;
220  if (f.type == 's') {
221  if constexpr (std::is_same_v<String, std::decay_t<Arg>>) {
222  ret = arg;
223  }
224  else if constexpr (has_toString<String, Arg>::value) {
225  // Converts argument to string
226  // if toString(arg) is valid expression
227  ret = toString<String>(std::forward<Arg>(arg));
228  }
229  else {
230  // Otherwise assert
231  format_assert(0);
232  }
233  pad_arg(ret, f);
234  }
235  else if (f.type == 'd' || f.type == 'i') {
236  ret = integral_to_string<String, false>(f, std::forward<Arg>(arg));
237  }
238  else if (f.type == 'u') {
239  ret = integral_to_string<String, true>(f, std::forward<Arg>(arg));
240  }
241  else if (f.type == 'x') {
242  ret = integral_to_hex_string<String, true>(std::forward<Arg>(arg));
243  pad_arg(ret, f);
244  }
245  else if (f.type == 'X') {
246  ret = integral_to_hex_string<String, false>(std::forward<Arg>(arg));
247  pad_arg(ret, f);
248  }
249  else if (f.type == 'p') {
250  ret = pointer_to_string<String>(std::forward<Arg>(arg));
251  pad_arg(ret, f);
252  }
253  else if (f.type == 'c') {
254  ret = char_to_string<String>(std::forward<Arg>(arg));
255  }
256  else {
257  format_assert(0);
258  }
259  return ret;
260 }
261 
262 template<typename String, typename... Args>
263 String extract_arg(field const&, size_t)
264 {
265  return String();
266 }
267 
268 
269 template<typename String, typename Arg, typename... Args>
270 String extract_arg(field const& f, size_t arg_n, Arg&& arg, Args&&...args)
271 {
272  String ret;
273 
274  if (!arg_n) {
275  ret = format_arg<String>(f, std::forward<Arg>(arg));
276  }
277  else {
278  ret = extract_arg<String>(f, arg_n - 1, std::forward<Args>(args)...);
279  }
280 
281  return ret;
282 }
283 
284 template<typename InString, typename OutString, typename... Args>
285 field get_field(InString const& fmt, typename InString::size_type & pos, size_t& arg_n, OutString & ret)
286 {
287  field f;
288  if (++pos >= fmt.size()) {
289  format_assert(0);
290  return f;
291  }
292 
293  // Get literal percent out of the way
294  if (fmt[pos] == '%') {
295  ret += '%';
296  ++pos;
297  return f;
298  }
299 
300 parse_start:
301  while (true) {
302  if (fmt[pos] == '0') {
303  f.flags |= pad_0;
304  }
305  else if (fmt[pos] == ' ') {
306  f.flags |= pad_blank;
307  }
308  else if (fmt[pos] == '-') {
309  f.flags &= ~pad_0;
310  f.flags |= left_align;
311  }
312  else if (fmt[pos] == '+') {
313  f.flags &= ~pad_blank;
314  f.flags |= always_sign;
315  }
316  else {
317  break;
318  }
319  if (++pos >= fmt.size()) {
320  format_assert(0);
321  return f;
322  }
323  }
324 
325  // Field width
326  while (fmt[pos] >= '0' && fmt[pos] <= '9') {
327  f.flags |= with_width;
328  f.width *= 10;
329  f.width += fmt[pos] - '0';
330  if (++pos >= fmt.size()) {
331  format_assert(0);
332  return f;
333  }
334  }
335  if (f.width > 10000) {
336  format_assert(0);
337  f.width = 10000;
338  }
339 
340  if (fmt[pos] == '$') {
341  // Positional argument, start over
342  arg_n = f.width - 1;
343  if (++pos >= fmt.size()) {
344  format_assert(0);
345  return f;
346  }
347  goto parse_start;
348  }
349 
350  // Ignore length modifier
351  while (true) {
352  auto c = fmt[pos];
353  if (c == 'h' || c == 'l' || c == 'L' || c == 'j' || c == 'z' || c == 't') {
354  if (++pos >= fmt.size()) {
355  format_assert(0);
356  return f;
357  }
358  }
359  else {
360  break;
361  }
362  }
363 
364  f.type = static_cast<char>(fmt[pos++]);
365  return f;
366 }
367 
368 template<typename InString, typename CharType = typename InString::value_type, typename OutString = std::basic_string<CharType>, typename... Args>
369 OutString do_sprintf(InString const& fmt, Args&&... args)
370 {
371  OutString ret;
372 
373  // Find % characters
374  typename InString::size_type start = 0, pos;
375 
376  size_t arg_n{};
377  while ((pos = fmt.find('%', start)) != InString::npos) {
378 
379  // Copy segment preceding the %
380  ret += fmt.substr(start, pos - start);
381 
382  field f = detail::get_field(fmt, pos, arg_n, ret);
383  if (f) {
384  format_assert(arg_n < sizeof...(args));
385  ret += detail::extract_arg<OutString>(f, arg_n++, std::forward<Args>(args)...);
386  }
387 
388  start = pos;
389  }
390 
391  // Copy remainder of string
392  ret += fmt.substr(start);
393 
394  return ret;
395 }
396 }
398 
420 template<typename... Args>
421 std::string sprintf(std::string_view const& fmt, Args&&... args)
422 {
423  return detail::do_sprintf(fmt, std::forward<Args>(args)...);
424 }
425 
426 template<typename... Args>
427 std::wstring sprintf(std::wstring_view const& fmt, Args&&... args)
428 {
429  return detail::do_sprintf(fmt, std::forward<Args>(args)...);
430 }
431 
432 }
433 
434 #endif
Definition: impersonation.hpp:78
std::string sprintf(std::string_view const &fmt, Args &&...args)
A simple type-safe sprintf replacement.
Definition: format.hpp:421
Functions to encode/decode strings.
String types and assorted functions.
The namespace used by libfilezilla.
Definition: apply.hpp:17