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