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