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