libfly  6.2.2
C++20 utility library for Linux, macOS, and Windows
formatters.hpp
1 #pragma once
2 
3 #include "fly/concepts/concepts.hpp"
4 #include "fly/fly.hpp"
5 #include "fly/types/string/concepts.hpp"
6 #include "fly/types/string/detail/classifier.hpp"
7 #include "fly/types/string/detail/format_specifier.hpp"
8 #include "fly/types/string/detail/unicode.hpp"
9 
10 #if !defined(FLY_COMPILER_SUPPORTS_FP_CHARCONV)
11 # include "fly/types/string/detail/stream_util.hpp"
12 #endif
13 
14 #include <array>
15 #include <charconv>
16 #include <cmath>
17 #include <limits>
18 #include <string>
19 #include <system_error>
20 #include <type_traits>
21 
22 #if !defined(FLY_COMPILER_SUPPORTS_FP_CHARCONV)
23 # include <iomanip>
24 # include <sstream>
25 #endif
26 
27 namespace fly {
28 
41 template <typename T, StandardCharacter CharType = char>
42 struct Formatter;
43 
44 //==================================================================================================
45 template <FormattableString T, StandardCharacter CharType>
46 struct Formatter<T, CharType> : public detail::BasicFormatSpecifier<CharType>
47 {
48  FLY_DEFINE_FORMATTER(CharType, detail::ParameterType::String)
49 
50 
58  template <typename FormatContext>
59  void format(const T &value, FormatContext &context)
60  {
61  const std::size_t min_width = FormatSpecifier::width(context, 0);
62  const std::size_t max_width = FormatSpecifier::precision(context, string_type::npos);
63 
64  const std::size_t actual_size = detail::BasicClassifier<CharType>::size(value);
65  const std::size_t value_size = std::min(max_width, actual_size);
66 
67  const std::size_t padding_size = std::max(value_size, min_width) - value_size;
68  const auto padding_char = m_fill.value_or(s_space);
69 
70  auto append_padding = [&context, padding_char](std::size_t count) {
71  for (std::size_t i = 0; i < count; ++i)
72  {
73  *context.out()++ = padding_char;
74  }
75  };
76 
77  switch (m_alignment)
78  {
79  case FormatSpecifier::Alignment::Left:
80  case FormatSpecifier::Alignment::Default:
81  append_string(value, value_size, context);
82  append_padding(padding_size);
83  break;
84 
85  case FormatSpecifier::Alignment::Right:
86  append_padding(padding_size);
87  append_string(value, value_size, context);
88  break;
89 
90  case FormatSpecifier::Alignment::Center:
91  {
92  const std::size_t left_padding = padding_size / 2;
93  const std::size_t right_padding =
94  (padding_size % 2 == 0) ? left_padding : left_padding + 1;
95 
96  append_padding(left_padding);
97  append_string(value, value_size, context);
98  append_padding(right_padding);
99  break;
100  }
101  }
102  }
103 
117  template <typename FormatContext>
118  static void append_string(const T &value, std::size_t value_size, FormatContext &context)
119  {
120  using standard_character_type = StandardCharacterType<T>;
121  using standard_view_type = std::basic_string_view<standard_character_type>;
122 
123  standard_view_type view;
124 
125  if constexpr (std::is_array_v<T> || std::is_pointer_v<T>)
126  {
127  view = standard_view_type(value, value_size);
128  }
129  else
130  {
131  view = standard_view_type(value).substr(0, value_size);
132  }
133 
134  if constexpr (fly::SameAs<CharType, standard_character_type>)
135  {
136  for (const auto &ch : view)
137  {
138  *context.out()++ = ch;
139  }
140  }
141  else
142  {
144 
145  if (auto converted = unicode::template convert_encoding<string_type>(view); converted)
146  {
147  for (const auto &ch : *converted)
148  {
149  *context.out()++ = ch;
150  }
151  }
152  }
153  }
154 
155 private:
156  using string_type = std::basic_string<CharType>;
157 
158  static constexpr const auto s_space = FLY_CHR(CharType, ' ');
159 };
160 
161 //==================================================================================================
162 template <FormattablePointer T, StandardCharacter CharType>
163 struct Formatter<T, CharType> : public detail::BasicFormatSpecifier<CharType>
164 {
165  FLY_DEFINE_FORMATTER(CharType, detail::ParameterType::Pointer)
166 
167 
175  template <typename FormatContext>
176  inline void format(T value, FormatContext &context)
177  {
178  m_alternate_form = true;
179  m_type = FormatSpecifier::Type::Hex;
180 
181  Formatter<std::uintptr_t, CharType> formatter(*this);
182  formatter.format(
183  reinterpret_cast<std::uintptr_t>(static_cast<const void *>(value)),
184  context);
185  }
186 };
187 
188 //==================================================================================================
189 template <FormattableIntegral T, StandardCharacter CharType>
190 struct Formatter<T, CharType> : public detail::BasicFormatSpecifier<CharType>
191 {
192  FLY_DEFINE_FORMATTER(CharType, detail::ParameterType::Integral)
193 
194 
202  template <typename FormatContext>
203  inline void format(T value, FormatContext &context)
204  {
205  if constexpr (std::is_signed_v<T>)
206  {
207  using U = std::make_unsigned_t<std::remove_cvref_t<T>>;
208 
209  // Compute the absolute value of the integer. Benchmarks showed this is exactly as fast
210  // as std::abs, but this also tracks whether the original value was negative without
211  // branches.
212  const T sign = value >> std::numeric_limits<T>::digits;
213  const U unsigned_value = static_cast<U>(static_cast<U>(value ^ sign) + (sign & 1));
214 
215  format(unsigned_value, static_cast<bool>(sign), context);
216  }
217  else
218  {
219  format(value, false, context);
220  }
221  }
222 
223 private:
224  using string_type = std::basic_string<CharType>;
225 
236  template <fly::UnsignedIntegral U, typename FormatContext>
237  void format(U value, bool is_negative, FormatContext &context)
238  {
239  if (m_type == FormatSpecifier::Type::Character)
240  {
241  format_as_character(value, is_negative, context);
242  return;
243  }
244 
245  std::size_t prefix_size = 0;
246 
247  if (is_negative || (m_sign == FormatSpecifier::Sign::Always) ||
248  (m_sign == FormatSpecifier::Sign::NegativeOnlyWithPositivePadding))
249  {
250  ++prefix_size;
251  }
252  if (m_alternate_form)
253  {
254  ++prefix_size;
255 
256  if ((m_type == FormatSpecifier::Type::Binary) || (m_type == FormatSpecifier::Type::Hex))
257  {
258  ++prefix_size;
259  }
260  }
261 
262  const int base = static_cast<int>(m_type);
263  const std::size_t value_size = count_digits(value, base) + prefix_size;
264 
265  const std::size_t width = FormatSpecifier::width(context, 0);
266  const std::size_t padding_size = std::max(value_size, width) - value_size;
267  const auto padding_char = m_fill.value_or(s_space);
268 
269  auto append_prefix = [this, is_negative, &context]() {
270  if (is_negative)
271  {
272  *context.out()++ = s_minus_sign;
273  }
274  else if (m_sign == FormatSpecifier::Sign::Always)
275  {
276  *context.out()++ = s_plus_sign;
277  }
278  else if (m_sign == FormatSpecifier::Sign::NegativeOnlyWithPositivePadding)
279  {
280  *context.out()++ = s_space;
281  }
282 
283  if (m_alternate_form)
284  {
285  const bool is_upper_case = m_case == FormatSpecifier::Case::Upper;
286  *context.out()++ = s_zero;
287 
288  if (m_type == FormatSpecifier::Type::Binary)
289  {
290  *context.out()++ = is_upper_case ? s_upper_b : s_lower_b;
291  }
292  else if (m_type == FormatSpecifier::Type::Hex)
293  {
294  *context.out()++ = is_upper_case ? s_upper_x : s_lower_x;
295  }
296  }
297  };
298 
299  auto append_padding = [&context](std::size_t count, CharType pad) {
300  for (std::size_t i = 0; i < count; ++i)
301  {
302  *context.out()++ = pad;
303  }
304  };
305 
306  switch (m_alignment)
307  {
308  case FormatSpecifier::Alignment::Left:
309  append_prefix();
310  append_number(value, base, context);
311  append_padding(padding_size, padding_char);
312  break;
313 
314  case FormatSpecifier::Alignment::Right:
315  append_padding(padding_size, padding_char);
316  append_prefix();
317  append_number(value, base, context);
318  break;
319 
320  case FormatSpecifier::Alignment::Center:
321  {
322  const std::size_t left_padding = padding_size / 2;
323  const std::size_t right_padding =
324  (padding_size % 2 == 0) ? left_padding : left_padding + 1;
325 
326  append_padding(left_padding, padding_char);
327  append_prefix();
328  append_number(value, base, context);
329  append_padding(right_padding, padding_char);
330  break;
331  }
332 
333  case FormatSpecifier::Alignment::Default:
334  if (m_zero_padding)
335  {
336  append_prefix();
337  append_padding(padding_size, s_zero);
338  append_number(value, base, context);
339  }
340  else
341  {
342  append_padding(padding_size, padding_char);
343  append_prefix();
344  append_number(value, base, context);
345  }
346  break;
347  }
348  }
349 
363  template <fly::UnsignedIntegral U, typename FormatContext>
364  void format_as_character(U value, bool is_negative, FormatContext &context)
365  {
366  if (is_negative || (value > static_cast<U>(std::numeric_limits<CharType>::max())))
367  {
368  return;
369  }
370 
371  const std::size_t width = FormatSpecifier::width(context, 0);
372  const std::size_t padding_size = width > 1 ? width - 1 : 0;
373  const auto padding_char = m_fill.value_or(s_space);
374 
375  auto append_padding = [&context, padding_char](std::size_t count) {
376  for (std::size_t i = 0; i < count; ++i)
377  {
378  *context.out()++ = padding_char;
379  }
380  };
381 
382  switch (m_alignment)
383  {
384  case FormatSpecifier::Alignment::Left:
385  *context.out()++ = static_cast<CharType>(value);
386  append_padding(padding_size);
387  break;
388 
389  case FormatSpecifier::Alignment::Right:
390  append_padding(padding_size);
391  *context.out()++ = static_cast<CharType>(value);
392  break;
393 
394  case FormatSpecifier::Alignment::Center:
395  {
396  const std::size_t left_padding = padding_size / 2;
397  const std::size_t right_padding =
398  (padding_size % 2 == 0) ? left_padding : left_padding + 1;
399 
400  append_padding(left_padding);
401  *context.out()++ = static_cast<CharType>(value);
402  append_padding(right_padding);
403  break;
404  }
405 
406  case FormatSpecifier::Alignment::Default:
407  append_padding(padding_size);
408  *context.out()++ = static_cast<CharType>(value);
409  break;
410  }
411  }
412 
430  template <typename U, typename FormatContext>
431  void append_number(U value, int base, FormatContext &context)
432  {
433  static thread_local std::array<char, std::numeric_limits<std::uintmax_t>::digits> s_buffer;
434 
435  char *begin = s_buffer.data();
436  char *end = begin + s_buffer.size();
437 
438  const auto result = std::to_chars(begin, end, value, base);
439 
440  if ((m_type == FormatSpecifier::Type::Hex) && (m_case == FormatSpecifier::Case::Upper))
441  {
442  for (char *it = begin; it != result.ptr; ++it)
443  {
445  }
446  }
447 
448  if constexpr (fly::SameAs<string_type, std::string>)
449  {
450  for (const char *it = begin; it != result.ptr; ++it)
451  {
452  *context.out()++ = *it;
453  }
454  }
455  else
456  {
457  using unicode = detail::BasicUnicode<char>;
458 
459  std::string_view view(
460  begin,
461  static_cast<std::size_t>(std::distance(begin, result.ptr)));
462 
463  unicode::template convert_encoding_into<string_type>(view, context.out());
464  }
465  }
466 
477  template <typename U>
478  static constexpr std::size_t count_digits(U value, int base)
479  {
480  std::size_t digits = 0;
481 
482  do
483  {
484  ++digits;
485  } while ((value /= static_cast<U>(base)) != 0);
486 
487  return digits;
488  }
489 
490  static constexpr const auto s_plus_sign = FLY_CHR(CharType, '+');
491  static constexpr const auto s_minus_sign = FLY_CHR(CharType, '-');
492  static constexpr const auto s_space = FLY_CHR(CharType, ' ');
493  static constexpr const auto s_zero = FLY_CHR(CharType, '0');
494  static constexpr const auto s_lower_b = FLY_CHR(CharType, 'b');
495  static constexpr const auto s_upper_b = FLY_CHR(CharType, 'B');
496  static constexpr const auto s_lower_x = FLY_CHR(CharType, 'x');
497  static constexpr const auto s_upper_x = FLY_CHR(CharType, 'X');
498 };
499 
500 //==================================================================================================
501 template <FormattableFloatingPoint T, StandardCharacter CharType>
502 struct Formatter<T, CharType> : public detail::BasicFormatSpecifier<CharType>
503 {
504  FLY_DEFINE_FORMATTER(CharType, detail::ParameterType::FloatingPoint)
505 
506 #if defined(FLY_COMPILER_SUPPORTS_FP_CHARCONV)
521  template <typename FormatContext>
522  void format(T value, FormatContext &context)
523  {
524  const bool is_negative = std::signbit(value);
525  value = std::abs(value);
526 
527  std::size_t prefix_size = 0;
528 
529  if (is_negative || (m_sign == FormatSpecifier::Sign::Always) ||
530  (m_sign == FormatSpecifier::Sign::NegativeOnlyWithPositivePadding))
531  {
532  ++prefix_size;
533  }
534 
535  const int precision = static_cast<int>(FormatSpecifier::precision(context, 6));
536  const FloatConversionResult result = convert_value(value, precision);
537 
538  auto append_prefix = [this, &is_negative, &context]() {
539  if (is_negative)
540  {
541  *context.out()++ = s_minus_sign;
542  }
543  else if (m_sign == FormatSpecifier::Sign::Always)
544  {
545  *context.out()++ = s_plus_sign;
546  }
547  else if (m_sign == FormatSpecifier::Sign::NegativeOnlyWithPositivePadding)
548  {
549  *context.out()++ = s_space;
550  }
551  };
552 
553  auto append_padding = [&context](std::size_t count, CharType pad) {
554  for (std::size_t i = 0; i < count; ++i)
555  {
556  *context.out()++ = pad;
557  }
558  };
559 
560  auto append_number = [this, &context, &result]() {
561  if constexpr (fly::SameAs<string_type, std::string>)
562  {
563  for (auto ch : result.m_digits)
564  {
565  *context.out()++ = ch;
566  }
567  if (result.m_append_decimal)
568  {
569  *context.out()++ = '.';
570  }
571  for (std::size_t i = 0; i < result.m_zeroes_to_append; ++i)
572  {
573  *context.out()++ = '0';
574  }
575  for (auto ch : result.m_exponent)
576  {
577  *context.out()++ = ch;
578  }
579  }
580  else
581  {
582  using unicode = detail::BasicUnicode<char>;
583 
584  unicode::template convert_encoding_into<string_type>(
585  result.m_digits,
586  context.out());
587 
588  if (result.m_append_decimal)
589  {
590  *context.out()++ = FLY_CHR(CharType, '.');
591  }
592  for (std::size_t i = 0; i < result.m_zeroes_to_append; ++i)
593  {
594  *context.out()++ = FLY_CHR(CharType, '0');
595  }
596 
597  unicode::template convert_encoding_into<string_type>(
598  result.m_exponent,
599  context.out());
600  }
601  };
602 
603  const std::size_t value_size = prefix_size + result.m_digits.size() +
604  result.m_exponent.size() + static_cast<std::size_t>(result.m_append_decimal) +
605  result.m_zeroes_to_append;
606  const std::size_t width = FormatSpecifier::width(context, 0);
607  const std::size_t padding_size = std::max(value_size, width) - value_size;
608  const auto padding_char = m_fill.value_or(s_space);
609 
610  switch (m_alignment)
611  {
612  case FormatSpecifier::Alignment::Left:
613  append_prefix();
614  append_number();
615  append_padding(padding_size, padding_char);
616  break;
617 
618  case FormatSpecifier::Alignment::Right:
619  append_padding(padding_size, padding_char);
620  append_prefix();
621  append_number();
622  break;
623 
624  case FormatSpecifier::Alignment::Center:
625  {
626  const std::size_t left_padding = padding_size / 2;
627  const std::size_t right_padding =
628  (padding_size % 2 == 0) ? left_padding : left_padding + 1;
629 
630  append_padding(left_padding, padding_char);
631  append_prefix();
632  append_number();
633  append_padding(right_padding, padding_char);
634  break;
635  }
636 
637  case FormatSpecifier::Alignment::Default:
638  if (m_zero_padding)
639  {
640  append_prefix();
641  append_padding(padding_size, s_zero);
642  append_number();
643  }
644  else
645  {
646  append_padding(padding_size, padding_char);
647  append_prefix();
648  append_number();
649  }
650  break;
651  }
652  }
653 #else
665  template <typename FormatContext>
666  void format(T value, FormatContext &context)
667  {
668  static thread_local std::stringstream s_stream;
669  detail::ScopedStreamModifiers modifiers(s_stream);
670 
671  if (m_alignment == FormatSpecifier::Alignment::Default)
672  {
673  m_alignment = FormatSpecifier::Alignment::Right;
674  }
675 
676  switch (m_sign)
677  {
678  case FormatSpecifier::Sign::Always:
679  modifiers.setf(std::ios_base::showpos);
680  break;
681 
682  case FormatSpecifier::Sign::NegativeOnlyWithPositivePadding:
683  modifiers.template locale<detail::PositivePaddingFacet<char>>();
684  modifiers.setf(std::ios_base::showpos);
685  break;
686 
687  default:
688  break;
689  }
690 
691  if (m_alternate_form)
692  {
693  modifiers.setf(std::ios_base::showpoint);
694  }
695 
696  if (m_zero_padding)
697  {
698  modifiers.setf(std::ios_base::internal, std::ios_base::adjustfield);
699  modifiers.fill(static_cast<char>(s_zero));
700  modifiers.width(static_cast<std::streamsize>(FormatSpecifier::width(context, 0)));
701  }
702 
703  modifiers.precision(static_cast<std::streamsize>(FormatSpecifier::precision(context, 6)));
704  m_precision = std::nullopt;
705  m_precision_position = std::nullopt;
706 
707  switch (m_type)
708  {
709  case FormatSpecifier::Type::HexFloat:
710  modifiers.setf(std::ios_base::fixed | std::ios_base::scientific);
711  break;
712 
713  case FormatSpecifier::Type::Scientific:
714  modifiers.setf(std::ios_base::scientific, std::ios::floatfield);
715  break;
716 
717  case FormatSpecifier::Type::Fixed:
718  // Only Apple's Clang seems to respect std::uppercase with std::fixed values. To
719  // ensure consistency, format these values as general types.
720  if (!std::isnan(value) && !std::isinf(value))
721  {
722  modifiers.setf(std::ios_base::fixed, std::ios::floatfield);
723  }
724  break;
725 
726  default:
727  break;
728  }
729 
730  if (m_case == FormatSpecifier::Case::Upper)
731  {
732  modifiers.setf(std::ios_base::uppercase);
733  }
734 
735  s_stream << value;
736 
737  Formatter<std::string_view, CharType> formatter(*this);
738  formatter.format(s_stream.str(), context);
739 
740  s_stream.str({});
741  }
742 #endif
743 
744 private:
745  using string_type = std::basic_string<CharType>;
746 
747 #if defined(FLY_COMPILER_SUPPORTS_FP_CHARCONV)
748 
752  struct FloatConversionResult
753  {
754  std::string_view m_digits;
755  std::string_view m_exponent;
756  bool m_append_decimal {false};
757  std::size_t m_zeroes_to_append {0};
758  };
759 
773  FloatConversionResult convert_value(T value, int precision)
774  {
775  static thread_local std::array<char, std::numeric_limits<T>::digits> s_buffer;
776 
777  char *begin = s_buffer.data();
778  char *end = begin + s_buffer.size();
779 
780  std::chars_format fmt = std::chars_format::general;
781  char exponent = '\0';
782 
783  switch (m_type)
784  {
785  case FormatSpecifier::Type::HexFloat:
786  fmt = std::chars_format::hex;
787  exponent = 'p';
788  break;
789  case FormatSpecifier::Type::Scientific:
790  fmt = std::chars_format::scientific;
791  exponent = 'e';
792  break;
793  case FormatSpecifier::Type::Fixed:
794  fmt = std::chars_format::fixed;
795  break;
796  default:
797  exponent = 'e';
798  break;
799  }
800 
801  const auto to_chars_result = std::to_chars(begin, end, value, fmt, precision);
802 
803  FloatConversionResult conversion_result;
804  conversion_result.m_digits =
805  std::string_view(begin, static_cast<std::size_t>(to_chars_result.ptr - begin));
806 
807  if (m_alternate_form)
808  {
809  conversion_result.m_append_decimal = true;
810 
811  for (const char *it = begin; it != to_chars_result.ptr; ++it)
812  {
813  if (*it == '.')
814  {
815  conversion_result.m_append_decimal = false;
816  }
817  else if (*it == exponent)
818  {
819  const auto position = static_cast<std::size_t>(it - begin);
820 
821  conversion_result.m_exponent = conversion_result.m_digits.substr(position);
822  conversion_result.m_digits = conversion_result.m_digits.substr(0, position);
823  }
824  }
825 
826  if (m_type == FormatSpecifier::Type::General)
827  {
828  const auto digits = conversion_result.m_digits.size() -
829  static_cast<std::size_t>(!conversion_result.m_append_decimal);
830 
831  if (static_cast<std::size_t>(precision) > digits)
832  {
833  conversion_result.m_zeroes_to_append =
834  static_cast<std::size_t>(precision) - digits;
835  }
836  }
837  }
838 
839  if (m_case == FormatSpecifier::Case::Upper)
840  {
841  for (char *it = begin; it != to_chars_result.ptr; ++it)
842  {
844  }
845  }
846 
847  return conversion_result;
848  }
849 
850 #endif // FLY_COMPILER_SUPPORTS_FP_CHARCONV
851 
852  static constexpr const auto s_plus_sign = FLY_CHR(CharType, '+');
853  static constexpr const auto s_minus_sign = FLY_CHR(CharType, '-');
854  static constexpr const auto s_space = FLY_CHR(CharType, ' ');
855  static constexpr const auto s_zero = FLY_CHR(CharType, '0');
856 };
857 
858 //==================================================================================================
859 template <FormattableBoolean T, StandardCharacter CharType>
860 struct Formatter<T, CharType> : public detail::BasicFormatSpecifier<CharType>
861 {
862  FLY_DEFINE_FORMATTER(CharType, detail::ParameterType::Boolean)
863 
864 
872  template <typename FormatContext>
873  inline void format(T value, FormatContext &context)
874  {
875  if (m_type == FormatSpecifier::Type::String)
876  {
877  Formatter<std::basic_string_view<CharType>, CharType> formatter(*this);
878  formatter.format(value ? s_true : s_false, context);
879  }
880  else
881  {
882  Formatter<unsigned, CharType> formatter(*this);
883  formatter.format(static_cast<unsigned>(value), context);
884  }
885  }
886 
887 private:
888  static constexpr const CharType *s_true = FLY_STR(CharType, "true");
889  static constexpr const CharType *s_false = FLY_STR(CharType, "false");
890 };
891 
892 } // namespace fly
static constexpr CharType to_upper(CharType ch)
Definition: classifier.hpp:237
static constexpr size_type size(T &&value)
Definition: unicode.hpp:31
Definition: stream_util.hpp:21
void width(std::streamsize size)
Definition: stream_util.hpp:187
void setf(std::ios_base::fmtflags flag)
Definition: stream_util.hpp:158
void precision(std::streamsize size)
Definition: stream_util.hpp:194
void fill(char ch)
Definition: stream_util.hpp:180
void format(const T &value, FormatContext &context)
Definition: formatters.hpp:59
static void append_string(const T &value, std::size_t value_size, FormatContext &context)
Definition: formatters.hpp:118
void format(T value, FormatContext &context)
Definition: formatters.hpp:176
Definition: formatters.hpp:42
Definition: format_specifier.hpp:116