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};
34
35struct field final {
36 size_t width{};
37 char flags{};
38 char type{};
39
40 explicit operator bool() const { return type != 0; }
41};
42
43template<typename Arg>
44bool 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
56template<typename String, bool Unsigned, typename Arg>
57typename 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
127template<typename String, bool Unsigned, typename Arg>
128typename 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
134template<typename String, bool Unsigned, typename Arg>
135typename 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
141template<typename String, class Arg, typename = void>
142struct has_toString : std::false_type {};
143
144template<typename String, class Arg>
145struct has_toString<String, Arg, std::void_t<decltype(toString<String>(std::declval<Arg>()))>> : std::true_type {};
146
147template <int N>
148struct argument
149{
150 template <class Arg>
151 struct of_type
152 {
153 template <typename String>
154 static constexpr bool is_formattable_as = std::disjunction<
155 std::is_enum<std::decay_t<Arg>>,
156 std::is_arithmetic<std::decay_t<Arg>>,
157 std::is_pointer<std::decay_t<Arg>>,
158 std::is_same<String, std::decay_t<Arg>>,
159 has_toString<String, Arg>
160 >::value;
161 };
162};
163
164// Converts integral type to hex string with desired string type
165template<typename String, bool Lowercase, typename Arg>
166String integral_to_hex_string(Arg && arg) noexcept
167{
168 if constexpr (std::is_enum_v<std::decay_t<Arg>>) {
169 // Special handling for enum, cast to underlying type
170 return integral_to_hex_string<String, Lowercase>(static_cast<std::underlying_type_t<std::decay_t<Arg>>>(arg));
171 }
172 else if constexpr (std::is_signed_v<std::decay_t<Arg>>) {
173 return integral_to_hex_string<String, Lowercase>(static_cast<std::make_unsigned_t<std::decay_t<Arg>>>(arg));
174 }
175 else if constexpr (std::is_integral_v<std::decay_t<Arg>>) {
176 std::decay_t<Arg> v = arg;
177 typename String::value_type buf[sizeof(v) * 2];
178 auto* const end = buf + sizeof(v) * 2;
179 auto* p = end;
180
181 do {
183 v >>= 4;
184 } while (v);
185
186 return String(p, end);
187 }
188 else {
189 format_assert(0);
190 return String();
191 }
192}
193
194// Converts pointer to hex string
195template<typename String, typename Arg>
196String pointer_to_string(Arg&& arg) noexcept
197{
198 if constexpr (std::is_pointer_v<std::decay_t<Arg>>) {
199 return String({'0', 'x'}) + integral_to_hex_string<String, true>(reinterpret_cast<uintptr_t>(arg));
200 }
201 else {
202 format_assert(0);
203 return String();
204 }
205}
206
207template<typename String, typename Arg>
208String char_to_string(Arg&& arg)
209{
210 if constexpr (std::is_integral_v<std::decay_t<Arg>>) {
211 return String({static_cast<typename String::value_type>(static_cast<unsigned char>(arg))});
212 }
213 else {
214 format_assert(0);
215 return String();
216 }
217}
218
219
220template<typename String>
221void pad_arg(String& s, field const& f)
222{
223 if (f.flags & with_width && s.size() < f.width) {
224 if (f.flags & left_align) {
225 s += String(f.width - s.size(), ' ');
226 }
227 else {
228 s = String(f.width - s.size(), (f.flags & pad_0) ? '0' : ' ') + s;
229 }
230 }
231}
232
233template<typename String, typename Arg>
234String format_arg(field const& f, Arg&& arg)
235{
236 String ret;
237 if (f.type == 's') {
238 if constexpr (std::is_same_v<String, std::decay_t<Arg>>) {
239 ret = arg;
240 }
241 else if constexpr (has_toString<String, Arg>::value) {
242 // Converts argument to string
243 // if toString(arg) is valid expression
244 ret = toString<String>(std::forward<Arg>(arg));
245 }
246 else {
247 // Otherwise assert
248 format_assert(0);
249 }
250 pad_arg(ret, f);
251 }
252 else if (f.type == 'd' || f.type == 'i') {
253 ret = integral_to_string<String, false>(f, std::forward<Arg>(arg));
254 }
255 else if (f.type == 'u') {
256 ret = integral_to_string<String, true>(f, std::forward<Arg>(arg));
257 }
258 else if (f.type == 'x') {
259 ret = integral_to_hex_string<String, true>(std::forward<Arg>(arg));
260 pad_arg(ret, f);
261 }
262 else if (f.type == 'X') {
263 ret = integral_to_hex_string<String, false>(std::forward<Arg>(arg));
264 pad_arg(ret, f);
265 }
266 else if (f.type == 'p') {
267 ret = pointer_to_string<String>(std::forward<Arg>(arg));
268 pad_arg(ret, f);
269 }
270 else if (f.type == 'c') {
271 ret = char_to_string<String>(std::forward<Arg>(arg));
272 }
273 else {
274 format_assert(0);
275 }
276 return ret;
277}
278
279template<typename String, typename... Args>
280String extract_arg(field const&, size_t)
281{
282 return String();
283}
284
285
286template<typename String, typename Arg, typename... Args>
287String extract_arg(field const& f, size_t arg_n, Arg&& arg, Args&&...args)
288{
289 String ret;
290
291 if (!arg_n) {
292 ret = format_arg<String>(f, std::forward<Arg>(arg));
293 }
294 else {
295 ret = extract_arg<String>(f, arg_n - 1, std::forward<Args>(args)...);
296 }
297
298 return ret;
299}
300
301template<typename InString, typename OutString>
302field get_field(InString const& fmt, typename InString::size_type & pos, size_t& arg_n, OutString & ret)
303{
304 field f;
305 if (++pos >= fmt.size()) {
306 format_assert(0);
307 return f;
308 }
309
310 // Get literal percent out of the way
311 if (fmt[pos] == '%') {
312 ret += '%';
313 ++pos;
314 return f;
315 }
316
317parse_start:
318 while (true) {
319 if (fmt[pos] == '0') {
320 f.flags |= pad_0;
321 }
322 else if (fmt[pos] == ' ') {
323 f.flags |= pad_blank;
324 }
325 else if (fmt[pos] == '-') {
326 f.flags &= ~pad_0;
327 f.flags |= left_align;
328 }
329 else if (fmt[pos] == '+') {
330 f.flags &= ~pad_blank;
331 f.flags |= always_sign;
332 }
333 else {
334 break;
335 }
336 if (++pos >= fmt.size()) {
337 format_assert(0);
338 return f;
339 }
340 }
341
342 // Field width
343 while (fmt[pos] >= '0' && fmt[pos] <= '9') {
344 f.flags |= with_width;
345 f.width *= 10;
346 f.width += fmt[pos] - '0';
347 if (++pos >= fmt.size()) {
348 format_assert(0);
349 return f;
350 }
351 }
352 if (f.width > 10000) {
353 format_assert(0);
354 f.width = 10000;
355 }
356
357 if (fmt[pos] == '$') {
358 // Positional argument, start over
359 arg_n = f.width - 1;
360 if (++pos >= fmt.size()) {
361 format_assert(0);
362 return f;
363 }
364 goto parse_start;
365 }
366
367 // Ignore length modifier
368 while (true) {
369 auto c = fmt[pos];
370 if (c == 'h' || c == 'l' || c == 'L' || c == 'j' || c == 'z' || c == 't') {
371 if (++pos >= fmt.size()) {
372 format_assert(0);
373 return f;
374 }
375 }
376 else {
377 break;
378 }
379 }
380
381 f.type = static_cast<char>(fmt[pos++]);
382 return f;
383}
384
385template<typename String, typename Arg, int N>
386constexpr bool check_argument()
387{
388 static_assert(
389 argument<N>::template of_type<Arg>::template is_formattable_as<String>,
390 "Argument cannot be formatted by fz::sprintf()"
391 );
392
393 return argument<N>::template of_type<Arg>::template is_formattable_as<String>;
394}
395
396template<typename String, typename... Args, std::size_t... Is>
397constexpr bool check_arguments(std::index_sequence<Is...>)
398{
399 return (check_argument<String, Args, Is>() && ...);
400}
401
402template<typename InString, typename CharType = typename InString::value_type, typename OutString = std::basic_string<CharType>, typename... Args>
403OutString do_sprintf(InString const& fmt, Args&&... args)
404{
405 OutString ret;
406
407 // Find % characters
408 typename InString::size_type start = 0, pos;
409
410 size_t arg_n{};
411 while ((pos = fmt.find('%', start)) != InString::npos) {
412
413 // Copy segment preceding the %
414 ret += fmt.substr(start, pos - start);
415
416 field f = detail::get_field(fmt, pos, arg_n, ret);
417 if (f) {
418 format_assert(arg_n < sizeof...(args));
419 ret += detail::extract_arg<OutString>(f, arg_n++, std::forward<Args>(args)...);
420 }
421
422 start = pos;
423 }
424
425 // Copy remainder of string
426 ret += fmt.substr(start);
427
428 return ret;
429}
430}
432
455template<typename... Args>
456std::string sprintf(std::string_view const& fmt, Args&&... args)
457{
458 detail::check_arguments<std::string, Args...>(std::index_sequence_for<Args...>());
459
460 return detail::do_sprintf(fmt, std::forward<Args>(args)...);
461}
462
463template<typename... Args>
464std::wstring sprintf(std::wstring_view const& fmt, Args&&... args)
465{
466 detail::check_arguments<std::wstring, Args...>(std::index_sequence_for<Args...>());
467
468 return detail::do_sprintf(fmt, std::forward<Args>(args)...);
469}
470
471}
472
473#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:79
std::string sprintf(std::string_view const &fmt, Args &&... args)
A simple type-safe sprintf replacement.
Definition format.hpp:456
String types and assorted functions.