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