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
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
147// Converts integral type to hex string with desired string type
148template<typename String, bool Lowercase, typename Arg>
149String 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
175template<typename String, typename Arg>
176String 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
187template<typename String, typename Arg>
188String 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
200template<typename String>
201void 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
213template<typename String, typename Arg>
214String 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
259template<typename String, typename... Args>
260String extract_arg(field const&, size_t)
261{
262 return String();
263}
264
265
266template<typename String, typename Arg, typename... Args>
267String 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
281template<typename InString, typename OutString, typename... Args>
282field 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
297parse_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
365template<typename InString, typename CharType = typename InString::value_type, typename OutString = std::basic_string<CharType>, typename... Args>
366OutString 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
417template<typename... Args>
418std::string sprintf(std::string_view const& fmt, Args&&... args)
419{
420 return detail::do_sprintf(fmt, std::forward<Args>(args)...);
421}
422
423template<typename... Args>
424std::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
Functions to encode/decode strings.
type
Definition: logger.hpp:16
The namespace used by libfilezilla.
Definition: apply.hpp:17
std::string sprintf(std::string_view const &fmt, Args &&... args)
A simple type-safe sprintf replacement.
Definition: format.hpp:418
String types and assorted functions.