3 #include "fly/concepts/concepts.hpp"
4 #include "fly/types/string/concepts.hpp"
5 #include "fly/types/string/detail/classifier.hpp"
6 #include "fly/types/string/detail/format_parameter_type.hpp"
7 #include "fly/types/string/detail/format_parse_context.hpp"
8 #include "fly/types/string/lexer.hpp"
9 #include "fly/types/string/literals.hpp"
15 #include <type_traits>
17 namespace fly::detail {
114 template <fly::StandardCharacter CharType>
135 enum class Alignment : std::uint8_t
143 enum class Sign : std::uint8_t
148 NegativeOnlyWithPositivePadding,
153 enum class Type : std::uint8_t
169 enum class Case : std::uint8_t
197 template <
typename FormatContext>
198 std::size_t
width(FormatContext &context, std::size_t fallback)
const;
213 template <
typename FormatContext>
214 std::size_t
precision(FormatContext &context, std::size_t fallback)
const;
222 template <fly::DerivedFrom<BasicFormatSpecifier> FormatterType>
226 formatter.m_position = m_position;
228 formatter.m_fill = m_fill;
229 formatter.m_alignment = m_alignment;
231 formatter.m_sign = m_sign;
232 formatter.m_alternate_form = m_alternate_form;
233 formatter.m_zero_padding = m_zero_padding;
235 formatter.m_width = m_width;
236 formatter.m_width_position = m_width_position;
238 formatter.m_precision = m_precision;
239 formatter.m_precision_position = m_precision_position;
241 formatter.m_locale_specific_form = m_locale_specific_form;
243 formatter.m_type = m_type;
244 formatter.m_case = m_case;
246 formatter.m_was_parsed_as_standard_formatter = m_was_parsed_as_standard_formatter;
254 template <
typename T>
259 std::size_t m_position {0};
261 std::optional<CharType> m_fill {std::nullopt};
262 Alignment m_alignment {Alignment::Default};
264 Sign m_sign {Sign::Default};
265 bool m_alternate_form {
false};
266 bool m_zero_padding {
false};
268 std::optional<std::size_t> m_width {std::nullopt};
269 std::optional<std::size_t> m_width_position {std::nullopt};
271 std::optional<std::size_t> m_precision {std::nullopt};
272 std::optional<std::size_t> m_precision_position {std::nullopt};
274 bool m_locale_specific_form {
false};
276 Type m_type {Type::None};
277 Case m_case {Case::Lower};
279 std::optional<ParameterType> m_parameter_type {std::nullopt};
281 std::size_t m_parse_index {0};
282 std::size_t m_size {0};
284 bool m_was_parsed_as_standard_formatter {
false};
295 constexpr
void parse_fill_and_alignment(FormatParseContext &context);
302 constexpr
void parse_sign(FormatParseContext &context);
318 constexpr
void parse_alternate_form_and_zero_padding(FormatParseContext &context);
327 constexpr
void parse_width(FormatParseContext &context);
340 constexpr
void parse_precision(FormatParseContext &context);
347 constexpr std::optional<BasicFormatSpecifier>
348 parse_nested_specifier(FormatParseContext &context);
358 constexpr
void parse_locale_specific_form(FormatParseContext &context);
369 constexpr
void parse_type(FormatParseContext &context);
377 constexpr
void infer_type(FormatParseContext &context);
385 constexpr
void validate(FormatParseContext &context);
394 constexpr
void validate_type(FormatParseContext &context, ParameterType parameter_type);
407 template <
typename FormatContext>
408 static std::optional<std::size_t> resolve(FormatContext &context, std::size_t position);
418 constexpr std::optional<ParameterType> resolve_parameter_type(FormatParseContext &context);
427 static constexpr std::optional<Type> type_of(CharType ch);
432 constexpr
bool is_numeric()
const;
434 static constexpr std::array<std::pair<CharType, Type>, 17> s_type_map {{
435 {FLY_CHR(CharType,
'c'), Type::Character},
436 {FLY_CHR(CharType,
's'), Type::String},
437 {FLY_CHR(CharType,
'p'), Type::Pointer},
438 {FLY_CHR(CharType,
'b'), Type::Binary},
439 {FLY_CHR(CharType,
'B'), Type::Binary},
440 {FLY_CHR(CharType,
'o'), Type::Octal},
441 {FLY_CHR(CharType,
'd'), Type::Decimal},
442 {FLY_CHR(CharType,
'x'), Type::Hex},
443 {FLY_CHR(CharType,
'X'), Type::Hex},
444 {FLY_CHR(CharType,
'a'), Type::HexFloat},
445 {FLY_CHR(CharType,
'A'), Type::HexFloat},
446 {FLY_CHR(CharType,
'e'), Type::Scientific},
447 {FLY_CHR(CharType,
'E'), Type::Scientific},
448 {FLY_CHR(CharType,
'f'), Type::Fixed},
449 {FLY_CHR(CharType,
'F'), Type::Fixed},
450 {FLY_CHR(CharType,
'g'), Type::General},
451 {FLY_CHR(CharType,
'G'), Type::General},
454 static constexpr
const auto s_left_brace = FLY_CHR(CharType,
'{');
455 static constexpr
const auto s_right_brace = FLY_CHR(CharType,
'}');
456 static constexpr
const auto s_less_than_sign = FLY_CHR(CharType,
'<');
457 static constexpr
const auto s_greater_than_sign = FLY_CHR(CharType,
'>');
458 static constexpr
const auto s_caret = FLY_CHR(CharType,
'^');
459 static constexpr
const auto s_plus_sign = FLY_CHR(CharType,
'+');
460 static constexpr
const auto s_minus_sign = FLY_CHR(CharType,
'-');
461 static constexpr
const auto s_space = FLY_CHR(CharType,
' ');
462 static constexpr
const auto s_number_sign = FLY_CHR(CharType,
'#');
463 static constexpr
const auto s_zero = FLY_CHR(CharType,
'0');
464 static constexpr
const auto s_letter_l = FLY_CHR(CharType,
'L');
465 static constexpr
const auto s_decimal = FLY_CHR(CharType,
'.');
474 #define FLY_DEFINE_FORMATTER(CharType, parameter_type) \
475 using FormatSpecifier = fly::detail::BasicFormatSpecifier<CharType>; \
476 using FormatSpecifier::m_position; \
477 using FormatSpecifier::m_fill; \
478 using FormatSpecifier::m_alignment; \
479 using FormatSpecifier::m_sign; \
480 using FormatSpecifier::m_alternate_form; \
481 using FormatSpecifier::m_zero_padding; \
482 using FormatSpecifier::m_width; \
483 using FormatSpecifier::m_width_position; \
484 using FormatSpecifier::m_precision; \
485 using FormatSpecifier::m_precision_position; \
486 using FormatSpecifier::m_locale_specific_form; \
487 using FormatSpecifier::m_type; \
488 using FormatSpecifier::m_case; \
489 using FormatSpecifier::m_parameter_type; \
493 m_parameter_type = parameter_type; \
496 explicit Formatter(FormatSpecifier specifier) noexcept : FormatSpecifier(std::move(specifier)) \
501 template <fly::StandardCharacter CharType>
503 m_position(context.next_position())
509 template <fly::StandardCharacter CharType>
512 m_was_parsed_as_standard_formatter =
true;
514 parse_fill_and_alignment(context);
516 parse_alternate_form_and_zero_padding(context);
517 parse_width(context);
518 parse_precision(context);
519 parse_locale_specific_form(context);
524 if (!context.
has_error() && !context.
lexer().consume_if(s_right_brace))
526 context.
on_error(
"Detected unclosed replacement field - must end with }");
531 template <fly::StandardCharacter CharType>
534 if (
auto next = context.lexer().peek(1); next)
536 if ((*next == s_less_than_sign) || (*next == s_greater_than_sign) || (*next == s_caret))
538 m_fill = context.lexer().consume().value();
542 if (context.lexer().consume_if(s_less_than_sign))
544 m_alignment = Alignment::Left;
546 else if (context.lexer().consume_if(s_greater_than_sign))
548 m_alignment = Alignment::Right;
550 else if (context.lexer().consume_if(s_caret))
552 m_alignment = Alignment::Center;
557 template <fly::StandardCharacter CharType>
558 constexpr
void BasicFormatSpecifier<CharType>::parse_sign(FormatParseContext &context)
560 if (context.lexer().consume_if(s_plus_sign))
562 m_sign = Sign::Always;
564 else if (context.lexer().consume_if(s_minus_sign))
566 m_sign = Sign::NegativeOnly;
568 else if (context.lexer().consume_if(s_space))
570 m_sign = Sign::NegativeOnlyWithPositivePadding;
575 template <fly::StandardCharacter CharType>
577 BasicFormatSpecifier<CharType>::parse_alternate_form_and_zero_padding(FormatParseContext &context)
579 if (context.lexer().consume_if(s_number_sign))
581 m_alternate_form =
true;
584 if (context.lexer().consume_if(s_zero) && (m_alignment == Alignment::Default))
586 m_zero_padding =
true;
591 template <fly::StandardCharacter CharType>
592 constexpr
void BasicFormatSpecifier<CharType>::parse_width(FormatParseContext &context)
594 if (
auto width = context.lexer().consume_number(); width)
596 m_width =
static_cast<std::size_t
>(*width);
598 else if (context.lexer().consume_if(s_left_brace))
600 if (
auto nested = parse_nested_specifier(context); nested)
602 m_width_position = nested->m_position;
608 template <fly::StandardCharacter CharType>
609 constexpr
void BasicFormatSpecifier<CharType>::parse_precision(FormatParseContext &context)
611 if (context.lexer().consume_if(s_decimal))
613 if (
auto precision = context.lexer().consume_number(); precision)
615 m_precision =
static_cast<std::size_t
>(*precision);
617 else if (context.lexer().consume_if(s_left_brace))
619 if (
auto nested = parse_nested_specifier(context); nested)
621 m_precision_position = nested->m_position;
627 "Expected a non-negative precision or nested replacement field after decimal");
633 template <fly::StandardCharacter CharType>
634 constexpr
auto BasicFormatSpecifier<CharType>::parse_nested_specifier(FormatParseContext &context)
635 -> std::optional<BasicFormatSpecifier>
638 const auto starting_position = context.lexer().position() - 1;
640 BasicFormatSpecifier specifier {};
641 specifier.m_position = context.next_position();
643 if (
auto parameter_type = context.parameter_type(specifier.m_position); parameter_type)
645 specifier.infer_type(context);
648 if (!context.lexer().consume_if(s_right_brace))
650 context.on_error(
"Detected unclosed replacement field - must end with }");
654 specifier.m_size = context.lexer().position() - starting_position;
659 template <fly::StandardCharacter CharType>
661 BasicFormatSpecifier<CharType>::parse_locale_specific_form(FormatParseContext &context)
663 if (context.lexer().consume_if(s_letter_l))
665 m_locale_specific_form =
true;
670 template <fly::StandardCharacter CharType>
671 constexpr
void BasicFormatSpecifier<CharType>::parse_type(FormatParseContext &context)
673 if (
auto ch = context.lexer().peek(); ch)
675 if (
auto type = type_of(ch.value()); type)
677 m_type = type.value();
678 context.lexer().consume();
682 m_case = Case::Upper;
689 template <fly::StandardCharacter CharType>
690 constexpr
void BasicFormatSpecifier<CharType>::infer_type(FormatParseContext &context)
692 auto parameter_type = resolve_parameter_type(context);
694 if (parameter_type == ParameterType::Character)
696 m_type = Type::Character;
698 else if (parameter_type == ParameterType::String)
700 m_type = Type::String;
702 else if (parameter_type == ParameterType::Pointer)
704 m_type = Type::Pointer;
706 else if (parameter_type == ParameterType::Integral)
708 m_type = Type::Decimal;
710 else if (parameter_type == ParameterType::FloatingPoint)
712 m_type = Type::General;
714 else if (parameter_type == ParameterType::Boolean)
716 m_type = Type::String;
721 template <fly::StandardCharacter CharType>
722 constexpr
void BasicFormatSpecifier<CharType>::validate(FormatParseContext &context)
724 auto parameter_type = resolve_parameter_type(context);
727 if (m_fill && ((m_fill == s_left_brace) || (m_fill == s_right_brace)))
729 context.on_error(
"Characters { and } are not allowed as fill characters");
731 else if (m_fill && (
static_cast<std::make_unsigned_t<CharType>
>(*m_fill) >= 0x80))
733 context.on_error(
"Non-ascii characters are not allowed as fill characters");
737 if ((m_sign != Sign::Default) && !is_numeric())
739 context.on_error(
"Sign may only be used with numeric presentation types");
743 if (m_alternate_form && (!is_numeric() || (m_type == Type::Decimal)))
746 "Alternate form may only be used with non-decimal numeric presentation types");
750 if (m_zero_padding && !is_numeric())
752 context.on_error(
"Zero-padding may only be used with numeric presentation types");
756 if (m_width && (*m_width == 0))
758 context.on_error(
"Width must be a positive (non-zero) value");
760 else if (m_width_position)
762 if (context.parameter_type(*m_width_position) != ParameterType::Integral)
764 context.on_error(
"Position of width parameter must be an integral type");
769 if (m_precision || m_precision_position)
771 if ((parameter_type != ParameterType::String) &&
772 (parameter_type != ParameterType::FloatingPoint))
774 context.on_error(
"Precision may only be used for string and floating-point types");
776 else if (m_precision_position)
778 if (context.parameter_type(*m_precision_position) != ParameterType::Integral)
780 context.on_error(
"Position of precision parameter must be an integral type");
786 if (m_locale_specific_form &&
787 ((parameter_type != ParameterType::Integral) &&
788 (parameter_type != ParameterType::FloatingPoint) &&
789 (parameter_type != ParameterType::Boolean)))
791 context.on_error(
"Locale-specific form may only be used for numeric and boolean types");
795 if (parameter_type && (m_type != Type::None))
797 validate_type(context, *parameter_type);
802 template <fly::StandardCharacter CharType>
803 constexpr
void BasicFormatSpecifier<CharType>::validate_type(
804 FormatParseContext &context,
805 ParameterType parameter_type)
807 if (parameter_type == ParameterType::Character)
809 if ((m_type != Type::Character) && (m_type != Type::Binary) && (m_type != Type::Octal) &&
810 (m_type != Type::Decimal) && (m_type != Type::Hex))
812 context.on_error(
"Character types must be formatted with {} or {:cbBodxX}");
815 else if (parameter_type == ParameterType::String)
817 if (m_type != Type::String)
819 context.on_error(
"String types must be formatted with {} or {:s}");
822 else if (parameter_type == ParameterType::Pointer)
824 if (m_type != Type::Pointer)
826 context.on_error(
"Pointer types must be formatted with {} or {:p}");
829 else if (parameter_type == ParameterType::Integral)
831 if ((m_type != Type::Character) && (m_type != Type::Binary) && (m_type != Type::Octal) &&
832 (m_type != Type::Decimal) && (m_type != Type::Hex))
834 context.on_error(
"Integral types must be formatted with {} or one of {:cbBodxX}");
837 else if (parameter_type == ParameterType::FloatingPoint)
839 if ((m_type != Type::HexFloat) && (m_type != Type::Scientific) && (m_type != Type::Fixed) &&
840 (m_type != Type::General))
843 "Floating-point types must be formatted with {} or one of {:aAeEfFgG}");
846 else if (parameter_type == ParameterType::Boolean)
848 if ((m_type != Type::Character) && (m_type != Type::String) && (m_type != Type::Binary) &&
849 (m_type != Type::Octal) && (m_type != Type::Decimal) && (m_type != Type::Hex))
851 context.on_error(
"Boolean types must be formatted with {} or one of {:csbBodxX}");
857 template <fly::StandardCharacter CharType>
858 template <
typename FormatContext>
862 if (m_width_position)
864 return resolve(context, *m_width_position).value_or(fallback);
867 return m_width.value_or(fallback);
871 template <fly::StandardCharacter CharType>
872 template <
typename FormatContext>
876 if (m_precision_position)
878 return resolve(context, *m_precision_position).value_or(fallback);
881 return m_precision.value_or(fallback);
885 template <fly::StandardCharacter CharType>
886 template <
typename FormatContext>
887 inline std::optional<std::size_t>
890 return context.arg(position).visit([](
auto value) -> std::optional<std::size_t> {
891 using T = std::remove_cvref_t<decltype(value)>;
892 std::optional<std::size_t> resolved;
894 if constexpr (fly::UnsignedIntegral<T>)
896 resolved =
static_cast<std::size_t
>(value);
898 else if constexpr (fly::SignedIntegral<T>)
902 resolved =
static_cast<std::size_t
>(value);
911 template <fly::StandardCharacter CharType>
912 constexpr std::optional<ParameterType>
913 BasicFormatSpecifier<CharType>::resolve_parameter_type(FormatParseContext &context)
915 if (m_parameter_type)
917 return *m_parameter_type;
920 return context.parameter_type(m_position);
924 template <fly::StandardCharacter CharType>
925 constexpr
auto BasicFormatSpecifier<CharType>::type_of(CharType ch) -> std::optional<Type>
927 auto it = std::find_if(s_type_map.begin(), s_type_map.end(), [&ch](
const auto &item) {
928 return item.first == ch;
931 if (it == s_type_map.end())
940 template <fly::StandardCharacter CharType>
941 constexpr
bool BasicFormatSpecifier<CharType>::is_numeric()
const
950 case Type::Scientific:
961 template <
typename T>
966 return (specifier1.m_position == specifier2.m_position) &&
967 (specifier1.m_fill == specifier2.m_fill) &&
968 (specifier1.m_alignment == specifier2.m_alignment) &&
969 (specifier1.m_sign == specifier2.m_sign) &&
970 (specifier1.m_alternate_form == specifier2.m_alternate_form) &&
971 (specifier1.m_zero_padding == specifier2.m_zero_padding) &&
972 (specifier1.m_width == specifier2.m_width) &&
973 (specifier1.m_width_position == specifier2.m_width_position) &&
974 (specifier1.m_precision == specifier2.m_precision) &&
975 (specifier1.m_precision_position == specifier2.m_precision_position) &&
976 (specifier1.m_locale_specific_form == specifier2.m_locale_specific_form) &&
977 (specifier1.m_type == specifier2.m_type) && (specifier1.m_case == specifier2.m_case);
Definition: string.hpp:51
static constexpr bool is_upper(CharType ch)
Definition: classifier.hpp:223
Definition: format_parse_context.hpp:22
constexpr bool has_error() const
Definition: format_parse_context.hpp:193
void on_error(const char *error)
Definition: format_parse_context.hpp:186
constexpr fly::BasicLexer< CharType > & lexer()
Definition: format_parse_context.hpp:172