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 pointer to hex string
196template<typename String, typename Arg>
197String pointer_to_string(Arg&& arg) noexcept
198{
199 if constexpr (std::is_pointer_v<std::decay_t<Arg>>) {
200 return String({'0', 'x'}) + integral_to_hex_string<String, true>(reinterpret_cast<uintptr_t>(arg));
201 }
202 else {
203 format_assert(0);
204 return String();
205 }
206}
207
208template<typename String, typename Arg>
209String char_to_string(Arg&& arg)
210{
211 if constexpr (std::is_integral_v<std::decay_t<Arg>>) {
212 return String({static_cast<typename String::value_type>(static_cast<unsigned char>(arg))});
213 }
214 else {
215 format_assert(0);
216 return String();
217 }
218}
219
220
221template<typename String>
222void pad_arg(String& s, field const& f)
223{
224 if (f.flags & with_width && s.size() < f.width) {
225 if (f.flags & left_align) {
226 s += String(f.width - s.size(), ' ');
227 }
228 else {
229 s = String(f.width - s.size(), (f.flags & pad_0) ? '0' : ' ') + s;
230 }
231 }
232}
233
234template<typename String, typename Arg>
235String format_arg(field const& f, Arg&& arg)
236{
237 String ret;
238 if (f.type == 's') {
239 if constexpr (std::is_same_v<String, std::decay_t<Arg>>) {
240 ret = arg;
241 }
242 else if constexpr (has_toString<String, Arg>::value) {
243 // Converts argument to string
244 // if toString(arg) is valid expression
245 ret = toString<String>(std::forward<Arg>(arg));
246 }
247 else {
248 // Otherwise assert
249 format_assert(0);
250 }
251 pad_arg(ret, f);
252 }
253 else if (f.type == 'd' || f.type == 'i') {
254 ret = integral_to_string<String, false>(f, std::forward<Arg>(arg));
255 }
256 else if (f.type == 'u') {
257 ret = integral_to_string<String, true>(f, std::forward<Arg>(arg));
258 }
259 else if (f.type == 'x') {
260 ret = integral_to_hex_string<String, true>(std::forward<Arg>(arg));
261 pad_arg(ret, f);
262 }
263 else if (f.type == 'X') {
264 ret = integral_to_hex_string<String, false>(std::forward<Arg>(arg));
265 pad_arg(ret, f);
266 }
267 else if (f.type == 'p') {
268 ret = pointer_to_string<String>(std::forward<Arg>(arg));
269 pad_arg(ret, f);
270 }
271 else if (f.type == 'c') {
272 ret = char_to_string<String>(std::forward<Arg>(arg));
273 }
274 else {
275 format_assert(0);
276 }
277 return ret;
278}
279
280template<typename String, typename... Args>
281String extract_arg(field const&, size_t)
282{
283 return String();
284}
285
286
287template<typename String, typename Arg, typename... Args>
288String extract_arg(field const& f, size_t arg_n, Arg&& arg, Args&&...args)
289{
290 String ret;
291
292 if (!arg_n) {
293 ret = format_arg<String>(f, std::forward<Arg>(arg));
294 }
295 else {
296 ret = extract_arg<String>(f, arg_n - 1, std::forward<Args>(args)...);
297 }
298
299 return ret;
300}
301
302template<typename InString, typename OutString>
303field get_field(InString const& fmt, typename InString::size_type & pos, size_t& arg_n, OutString & ret)
304{
305 field f;
306 if (++pos >= fmt.size()) {
307 format_assert(0);
308 return f;
309 }
310
311 // Get literal percent out of the way
312 if (fmt[pos] == '%') {
313 ret += '%';
314 ++pos;
315 return f;
316 }
317
318parse_start:
319 while (true) {
320 if (fmt[pos] == '0') {
321 f.flags |= pad_0;
322 }
323 else if (fmt[pos] == ' ') {
324 f.flags |= pad_blank;
325 }
326 else if (fmt[pos] == '-') {
327 f.flags &= ~pad_0;
328 f.flags |= left_align;
329 }
330 else if (fmt[pos] == '+') {
331 f.flags &= ~pad_blank;
332 f.flags |= always_sign;
333 }
334 else if (fmt[pos] == '\'') {
335 f.flags |= thousands;
336 }
337 else {
338 break;
339 }
340 if (++pos >= fmt.size()) {
341 format_assert(0);
342 return f;
343 }
344 }
345
346 // Field width
347 while (fmt[pos] >= '0' && fmt[pos] <= '9') {
348 f.flags |= with_width;
349 f.width *= 10;
350 f.width += fmt[pos] - '0';
351 if (++pos >= fmt.size()) {
352 format_assert(0);
353 return f;
354 }
355 }
356 if (f.width > 10000) {
357 format_assert(0);
358 f.width = 10000;
359 }
360
361 if (fmt[pos] == '$') {
362 // Positional argument, start over
363 arg_n = f.width - 1;
364 if (++pos >= fmt.size()) {
365 format_assert(0);
366 return f;
367 }
368 goto parse_start;
369 }
370
371 // Ignore length modifier
372 while (true) {
373 auto c = fmt[pos];
374 if (c == 'h' || c == 'l' || c == 'L' || c == 'j' || c == 'z' || c == 't') {
375 if (++pos >= fmt.size()) {
376 format_assert(0);
377 return f;
378 }
379 }
380 else {
381 break;
382 }
383 }
384
385 f.type = static_cast<char>(fmt[pos++]);
386 return f;
387}
388
389template<typename String, typename Arg, int N>
390constexpr bool check_argument()
391{
392 static_assert(
393 argument<N>::template of_type<Arg>::template is_formattable_as<String>,
394 "Argument cannot be formatted by fz::sprintf()"
395 );
396
397 return argument<N>::template of_type<Arg>::template is_formattable_as<String>;
398}
399
400template<typename String, typename... Args, std::size_t... Is>
401constexpr bool check_arguments(std::index_sequence<Is...>)
402{
403 return (check_argument<String, Args, Is>() && ...);
404}
405
406template<typename InString, typename CharType = typename InString::value_type, typename OutString = std::basic_string<CharType>, typename... Args>
407OutString do_sprintf(InString const& fmt, Args&&... args)
408{
409 OutString ret;
410
411 // Find % characters
412 typename InString::size_type start = 0, pos;
413
414 size_t arg_n{};
415 while ((pos = fmt.find('%', start)) != InString::npos) {
416
417 // Copy segment preceding the %
418 ret += fmt.substr(start, pos - start);
419
420 field f = detail::get_field(fmt, pos, arg_n, ret);
421 if (f) {
422 format_assert(arg_n < sizeof...(args));
423 ret += detail::extract_arg<OutString>(f, arg_n++, std::forward<Args>(args)...);
424 }
425
426 start = pos;
427 }
428
429 // Copy remainder of string
430 ret += fmt.substr(start);
431
432 return ret;
433}
434}
436
459template<typename... Args>
460std::string sprintf(std::string_view const& fmt, Args&&... args)
461{
462 detail::check_arguments<std::string, Args...>(std::index_sequence_for<Args...>());
463
464 return detail::do_sprintf(fmt, std::forward<Args>(args)...);
465}
466
467template<typename... Args>
468std::wstring sprintf(std::wstring_view const& fmt, Args&&... args)
469{
470 detail::check_arguments<std::wstring, Args...>(std::index_sequence_for<Args...>());
471
472 return detail::do_sprintf(fmt, std::forward<Args>(args)...);
473}
474
475}
476
477#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:460
String types and assorted functions.