MSTL 1.4.0
A Modern C++ Library with extended functionality, web components, and utility libraries
载入中...
搜索中...
未找到
format.hpp
1#ifndef MSTL_CORE_STRING_FORMAT_HPP__
2#define MSTL_CORE_STRING_FORMAT_HPP__
3#include "to_string.hpp"
5
6enum class FORMAT_ALIGN {
7 LEFT = '<',
8 RIGHT = '>',
9 INTERNAL = '=',
10 CENTER = '^'
11};
12
13enum class FORMAT_TYPE {
14 BINARY = 'b',
15 OCTAL = 'o',
16 DECIMAL = 'd',
17 HEX_LOW = 'x',
18 HEX_UP = 'X',
19 FLOAT_FIX = 'f',
20 FLOAT_EXP = 'e',
21 FLOAT_GEN = 'g'
22};
23
24struct MSTL_API format_options {
25 char fill = ' ';
26 int width = 0;
27 int precision = -1;
28 FORMAT_ALIGN alignment = FORMAT_ALIGN::RIGHT;
29 char sign_mode = 0; // 0: default, '+': always show, '-': only nega, ' ': posi space
30 bool show_base = false;
31 FORMAT_TYPE type = FORMAT_TYPE::DECIMAL;
32 bool zero_pad = false;
33};
34
35
37
38constexpr FORMAT_ALIGN to_number_alignment(const char c) {
39 switch (c) {
40 case '<': return FORMAT_ALIGN::LEFT;
41 case '>': return FORMAT_ALIGN::RIGHT;
42 case '^': return FORMAT_ALIGN::CENTER;
43 case '=': return FORMAT_ALIGN::INTERNAL;
44 default: return FORMAT_ALIGN::RIGHT;
45 }
46}
47
48constexpr FORMAT_TYPE to_number_type(const char c) {
49 switch (c) {
50 case 'b': return FORMAT_TYPE::BINARY;
51 case 'o': return FORMAT_TYPE::OCTAL;
52 case 'd': return FORMAT_TYPE::DECIMAL;
53 case 'x': return FORMAT_TYPE::HEX_LOW;
54 case 'X': return FORMAT_TYPE::HEX_UP;
55 case 'f': return FORMAT_TYPE::FLOAT_FIX;
56 case 'e': return FORMAT_TYPE::FLOAT_EXP;
57 case 'g': return FORMAT_TYPE::FLOAT_GEN;
58 default: return FORMAT_TYPE::DECIMAL;
59 }
60}
61
62MSTL_CONSTEXPR20 format_options parse_number_format(const string_view& fmt_str) {
63 format_options options;
64 size_t pos = 0;
65
66 if (fmt_str.empty()) return options;
67
68 bool found_align = false;
69 if (pos + 1 < fmt_str.size()) {
70 const char second_char = fmt_str[pos + 1];
71 const char first_char = fmt_str[pos];
72
73 if ((second_char == '<' || second_char == '>' ||
74 second_char == '^' || second_char == '=') &&
75 (first_char != '+' && first_char != '-' && first_char != ' ')) {
76 options.fill = first_char;
77 options.alignment = to_number_alignment(second_char);
78 pos += 2;
79 found_align = true;
80 }
81 }
82
83 if (!found_align && pos < fmt_str.size()) {
84 const char c = fmt_str[pos];
85 if (c == '<' || c == '>' || c == '^' || c == '=') {
86 options.alignment = to_number_alignment(c);
87 ++pos;
88 found_align = true;
89 }
90 }
91
92 if (pos < fmt_str.size()) {
93 const char c = fmt_str[pos];
94 if (c == '+' || c == '-' || c == ' ') {
95 options.sign_mode = c;
96 ++pos;
97 }
98 }
99
100 if (!found_align && pos < fmt_str.size()) {
101 const char c = fmt_str[pos];
102 if (c == '<' || c == '>' || c == '^' || c == '=') {
103 options.alignment = to_number_alignment(c);
104 ++pos;
105 }
106 }
107
108 if (pos < fmt_str.size() && fmt_str[pos] == '#') {
109 options.show_base = true;
110 ++pos;
111 }
112
113 if (pos < fmt_str.size() && fmt_str[pos] == '0' &&
114 options.fill == ' ' && options.alignment == FORMAT_ALIGN::RIGHT) {
115 options.zero_pad = true;
116 options.fill = '0';
117 options.alignment = FORMAT_ALIGN::INTERNAL;
118 ++pos;
119 }
120
121 if (pos < fmt_str.size() && _MSTL is_digit(fmt_str[pos])) {
122 int width = 0;
123 while (pos < fmt_str.size() && _MSTL is_digit(fmt_str[pos])) {
124 width = width * 10 + (fmt_str[pos] - '0');
125 ++pos;
126 }
127 options.width = width;
128 }
129
130 if (pos < fmt_str.size() && fmt_str[pos] == '.') {
131 ++pos;
132 int precision = 0;
133 while (pos < fmt_str.size() && _MSTL is_digit(fmt_str[pos])) {
134 precision = precision * 10 + (fmt_str[pos] - '0');
135 ++pos;
136 }
137 options.precision = precision;
138 }
139
140 if (pos < fmt_str.size()) {
141 options.type = to_number_type(fmt_str[pos]);
142 ++pos;
143 }
144
145 return options;
146}
147
149
150
151template <typename Number, typename = void>
152struct formatter;
153
154
155template <typename T>
156struct formatter<T, enable_if_t<is_floating_point_v<T>>> {
157 MSTL_CONSTEXPR20 string operator ()(const T& value, const format_options& options) const {
158 double val = static_cast<double>(value);
159 const bool is_negative = val < 0;
160 if (is_negative) val = -val;
161
162 string prefix;
163 if (is_negative) {
164 prefix += "-";
165 } else if (options.sign_mode == '+') {
166 prefix += "+";
167 } else if (options.sign_mode == ' ') {
168 prefix += " ";
169 }
170
171 string number_str;
172 const int precision = options.precision >= 0 ? options.precision : 6;
173
174 switch (options.type) {
175 case FORMAT_TYPE::FLOAT_FIX: {
176 number_str = _MSTL to_string_fixed(val, precision);
177 break;
178 }
179 case FORMAT_TYPE::FLOAT_EXP: {
180 number_str = _MSTL to_string_scientific(val, precision);
181 break;
182 }
183 case FORMAT_TYPE::FLOAT_GEN:
184 default: {
185 number_str = _MSTL to_string_general(val, precision);
186 break;
187 }
188 }
189
190 if (!number_str.empty() && number_str[0] == '-') {
191 number_str = number_str.substr(1);
192 }
193
194 string result = prefix + number_str;
195 if (options.width <= 0 || result.size() >= static_cast<size_t>(options.width)) {
196 return result;
197 }
198
199 const size_t fill_count = options.width - result.size();
200 const string fill_str(fill_count, options.fill);
201
202 switch (options.alignment) {
203 case FORMAT_ALIGN::LEFT:
204 return result + fill_str;
205 case FORMAT_ALIGN::RIGHT:
206 return fill_str + result;
207 case FORMAT_ALIGN::INTERNAL:
208 return prefix + fill_str + number_str;
209 case FORMAT_ALIGN::CENTER: {
210 const size_t left_fill = fill_count / 2;
211 const size_t right_fill = fill_count - left_fill;
212 return string(left_fill, options.fill) + result + string(right_fill, options.fill);
213 }
214 default:
215 return fill_str + result;
216 }
217 }
218};
219
220template <typename T>
221struct formatter<T, enable_if_t<is_integral_v<T> && is_signed_v<T>>> {
222 MSTL_CONSTEXPR20 string operator ()(const T& value, const format_options& options) const {
223 const int64_t val = static_cast<int64_t>(value);
224 const bool is_negative = val < 0;
225 uint64_t abs_value = is_negative ? static_cast<uint64_t>(-val) : static_cast<uint64_t>(val);
226
227 const char* digits;
228 int base;
229 switch (options.type) {
230 case FORMAT_TYPE::BINARY: {
231 digits = "01";
232 base = 2;
233 break;
234 } case FORMAT_TYPE::OCTAL: {
235 digits = "01234567";
236 base = 8;
237 break;
238 } case FORMAT_TYPE::HEX_LOW: {
239 digits = "0123456789abcdef";
240 base = 16;
241 break;
242 } case FORMAT_TYPE::HEX_UP: {
243 digits = "0123456789ABCDEF";
244 base = 16;
245 break;
246 } case FORMAT_TYPE::DECIMAL: default: {
247 digits = "0123456789";
248 base = 10;
249 break;
250 }
251 }
252
253 string number_str;
254 if (abs_value == 0) {
255 number_str = "0";
256 } else {
257 while (abs_value > 0) {
258 number_str = digits[abs_value % base] + number_str;
259 abs_value /= base;
260 }
261 }
262
263 string prefix;
264 if (is_negative) {
265 prefix += "-";
266 } else if (options.sign_mode == '+') {
267 prefix += "+";
268 } else if (options.sign_mode == ' ') {
269 prefix += " ";
270 }
271
272 if (options.show_base && base != 10) {
273 switch (options.type) {
274 case FORMAT_TYPE::BINARY: prefix += "0b"; break;
275 case FORMAT_TYPE::OCTAL: prefix += "0o"; break;
276 case FORMAT_TYPE::HEX_LOW: prefix += "0x"; break;
277 case FORMAT_TYPE::HEX_UP: prefix += "0X"; break;
278 default: break;
279 }
280 }
281
282 string result = prefix + number_str;
283 if (options.width <= 0 || result.size() >= static_cast<size_t>(options.width)) {
284 return result;
285 }
286
287 const size_t fill_count = options.width - result.size();
288 const string fill_str(fill_count, options.fill);
289
290 switch (options.alignment) {
291 case FORMAT_ALIGN::LEFT:
292 return result + fill_str;
293 case FORMAT_ALIGN::RIGHT:
294 return fill_str + result;
295 case FORMAT_ALIGN::INTERNAL:
296 return prefix + fill_str + number_str;
297 case FORMAT_ALIGN::CENTER: {
298 const size_t left_fill = fill_count / 2;
299 const size_t right_fill = fill_count - left_fill;
300 return string(left_fill, options.fill) + result + string(right_fill, options.fill);
301 }
302 default:
303 return fill_str + result;
304 }
305 }
306};
307
308template <typename T>
309struct formatter<T, enable_if_t<is_integral_v<T> && is_unsigned_v<T>>> {
310 MSTL_CONSTEXPR20 string operator ()(const T& value, const format_options& options) const {
311 string digits;
312 int base;
313 bool uppercase = false;
314
315 switch (options.type) {
316 case FORMAT_TYPE::HEX_LOW: {
317 base = 16;
318 uppercase = false;
319 break;
320 } case FORMAT_TYPE::HEX_UP: {
321 base = 16;
322 uppercase = true;
323 break;
324 } case FORMAT_TYPE::OCTAL: {
325 base = 8;
326 break;
327 } case FORMAT_TYPE::BINARY: {
328 base = 2;
329 break;
330 } case FORMAT_TYPE::DECIMAL: default: {
331 base = 10;
332 break;
333 }
334 }
335
336 if (base == 10) {
337 digits = _INNER __uint_to_string<char>(static_cast<const unpackage_t<T>&>(value));
338 } else {
339 digits = _INNER __uint_to_string_base(static_cast<const unpackage_t<T>&>(value), base, uppercase);
340 }
341
342 string base_prefix = "";
343 if (options.show_base) {
344 switch (base) {
345 case 16: {
346 base_prefix = uppercase ? "0X" : "0x";
347 break;
348 } case 8: {
349 base_prefix = "0";
350 break;
351 } case 2: {
352 base_prefix = uppercase ? "0B" : "0b";
353 break;
354 } default: break;
355 }
356 }
357
358 string number_str = base_prefix + digits;
359
360 if (options.width <= 0 || number_str.size() >= static_cast<size_t>(options.width)) {
361 return number_str;
362 }
363
364 const size_t fill_count = options.width - number_str.size();
365
366 switch (options.alignment) {
367 case FORMAT_ALIGN::LEFT:
368 return number_str + string(fill_count, options.fill);
369 case FORMAT_ALIGN::RIGHT:
370 return string(fill_count, options.fill) + number_str;
371 case FORMAT_ALIGN::INTERNAL: {
372 if (!base_prefix.empty()) {
373 return base_prefix + string(fill_count, options.fill) + digits;
374 }
375 return string(fill_count, options.fill) + number_str;
376 }
377 case FORMAT_ALIGN::CENTER: {
378 const size_t left_fill = fill_count / 2;
379 const size_t right_fill = fill_count - left_fill;
380 return string(left_fill, options.fill) + number_str + string(right_fill, options.fill);
381 }
382 default:
383 return string(fill_count, options.fill) + number_str;
384 }
385 }
386};
387
388template <>
389struct formatter<string> {
390 MSTL_CONSTEXPR20 string operator ()(const string& value, const format_options& options) const {
391 if (options.width <= 0 || value.size() >= static_cast<size_t>(options.width)) {
392 return value;
393 }
394
395 const size_t fill_count = options.width - value.size();
396 const string fill_str(fill_count, options.fill);
397
398 switch (options.alignment) {
399 case FORMAT_ALIGN::LEFT:
400 return value + fill_str;
401 case FORMAT_ALIGN::RIGHT:
402 return fill_str + value;
403 case FORMAT_ALIGN::CENTER: {
404 const size_t left_fill = fill_count / 2;
405 const size_t right_fill = fill_count - left_fill;
406 return string(left_fill, options.fill) + value + string(right_fill, options.fill);
407 }
408 case FORMAT_ALIGN::INTERNAL:
409 default:
410 return fill_str + value;
411 }
412 }
413};
414
415template <>
416struct formatter<const char*> {
417 MSTL_CONSTEXPR20 string operator ()(const char* value, const format_options& options) const {
418 return formatter<string>()(string(value), options);
419 }
420};
421
422template <>
423struct formatter<string_view> {
424 MSTL_CONSTEXPR20 string operator ()(const string_view value, const format_options& options) const {
425 return formatter<string>()(string(value), options);
426 }
427};
428
429template <>
430struct formatter<char*> {
431 MSTL_CONSTEXPR20 string operator ()(char* value, const format_options& options) const {
432 return formatter<string>()(string(value), options);
433 }
434};
435
436template <>
437struct formatter<char> {
438 MSTL_CONSTEXPR20 string operator ()(const char value, const format_options& options) const {
439 return formatter<string>()(string(1, value), options);
440 }
441};
442
443
445MSTL_CONSTEXPR20 string format_impl(const string_view fmt, size_t& pos) {
446 string result;
447 while (pos < fmt.size()) {
448 if (fmt[pos] == '{') {
449 if (pos + 1 < fmt.size() && fmt[pos + 1] == '{') {
450 result += '{';
451 pos += 2;
452 } else {
453 throw_exception(value_exception("Not enough arguments for format string"));
454 }
455 } else if (fmt[pos] == '}') {
456 if (pos + 1 < fmt.size() && fmt[pos + 1] == '}') {
457 result += '}';
458 pos += 2;
459 } else {
460 throw_exception(value_exception("Unmatched '}' in format string"));
461 }
462 } else {
463 result += fmt[pos];
464 ++pos;
465 }
466 }
467 return result;
468}
469
470template <typename First, typename... Rest>
471MSTL_CONSTEXPR20 string format_impl(const string_view fmt, size_t& pos, First&& first, Rest&&... rest) {
472 string result;
473 while (pos < fmt.size()) {
474 if (fmt[pos] == '{') {
475 if (pos + 1 < fmt.size() && fmt[pos + 1] == '{') {
476 result += '{';
477 pos += 2;
478 continue;
479 }
480 ++pos;
481 size_t end_pos = pos;
482 int depth = 1;
483 while (end_pos < fmt.size() && depth > 0) {
484 if (fmt[end_pos] == '{') {
485 ++depth;
486 } else if (fmt[end_pos] == '}') {
487 --depth;
488 }
489 if (depth > 0) ++end_pos;
490 }
491 if (depth != 0) {
492 throw_exception(value_exception("Unmatched '{' in format string"));
493 }
494
495 const string_view spec_str = fmt.substr(pos, end_pos - pos);
496 pos = end_pos + 1;
497 format_options opts;
498 if (spec_str.empty()) {
499 opts = _INNER parse_number_format("");
500 } else if (spec_str[0] == ':') {
501 opts = _INNER parse_number_format(spec_str.substr(1));
502 } else {
503 opts = _INNER parse_number_format(spec_str);
504 }
505 result += formatter<decay_t<First>>()(_MSTL forward<First>(first), opts);
506 result += _INNER format_impl(fmt, pos, _MSTL forward<Rest>(rest)...);
507 return result;
508 } else if (fmt[pos] == '}') {
509 if (pos + 1 < fmt.size() && fmt[pos + 1] == '}') {
510 result += '}';
511 pos += 2;
512 } else {
513 throw_exception(value_exception("Unmatched '}' in format string"));
514 }
515 } else {
516 result += fmt[pos];
517 ++pos;
518 }
519 }
520 return result;
521}
523
524
525template <typename... Args, enable_if_t<(sizeof...(Args) > 0), int> = 0>
526MSTL_NODISCARD MSTL_CONSTEXPR20 string format(const string_view fmt, Args&&... args) {
527 size_t pos = 0;
528 return _INNER format_impl(fmt, pos, _MSTL forward<Args>(args)...);
529}
530
532#endif // MSTL_CORE_STRING_FORMAT_HPP__
MSTL_NODISCARD constexpr T && forward(remove_reference_t< T > &x) noexcept
完美转发左值
typename unpackage< T >::type unpackage_t
unpackage的便捷别名
MSTL_CONST_FUNCTION MSTL_CONSTEXPR14 bool is_digit(const CharT c) noexcept
检查字符是否为数字
long long int64_t
64位有符号整数类型
unsigned long long uint64_t
64位无符号整数类型
#define _MSTL
全局命名空间MSTL前缀
#define MSTL_END_INNER__
结束inner命名空间
#define _INNER
inner命名空间前缀
#define MSTL_END_NAMESPACE__
结束全局命名空间MSTL
#define MSTL_BEGIN_NAMESPACE__
开始全局命名空间MSTL
#define MSTL_BEGIN_INNER__
开始inner命名空间
MSTL_CONST_FUNCTION constexpr bool is_negative(const T x) noexcept
检查浮点数是否为负数
constexpr void fill(Iterator first, Iterator last, const T &value)
填充范围元素
typename enable_if< Test, T >::type enable_if_t
enable_if的便捷别名
变量处理异常