diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 11:19:05 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 11:19:05 +0100 |
| commit | 4f942e014dab44ccb8850c5921b81d4bd777d831 (patch) | |
| tree | c496d44a061544a5240aee56616b51801455a851 /libs | |
| parent | 1865f7a162a496592e236ffcff171e7e7bc47ee2 (diff) | |
| download | teachos-4f942e014dab44ccb8850c5921b81d4bd777d831.tar.xz teachos-4f942e014dab44ccb8850c5921b81d4bd777d831.zip | |
kstd: rework formatting to be closer to std
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_args.hpp | 62 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_context.hpp | 89 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_specifiers.hpp | 204 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_specs.hpp | 104 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_string.hpp | 151 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/formatter.hpp | 325 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/print_sink.hpp | 2 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/os/print.hpp | 2 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/print | 20 |
9 files changed, 600 insertions, 359 deletions
diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp new file mode 100644 index 0000000..d236a23 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_args.hpp @@ -0,0 +1,62 @@ +#ifndef KSTD_BITS_FORMAT_ARGS_HPP +#define KSTD_BITS_FORMAT_ARGS_HPP + +// IWYU pragma: private, include <kstd/format> + +#include "kstd/bits/format_context.hpp" + +#include <array> +#include <cstddef> +#include <span> +#include <type_traits> + +namespace kstd +{ + template<typename> + struct formatter; + + struct format_arg + { + using format_function_type = auto(void const * value, format_parse_context & parse_context, + format_context & context) -> void; + + void const * value_pointer; + format_function_type * format_function; + }; + + using format_args = std::span<format_arg const>; + + template<std::size_t Count> + struct format_arg_store + { + std::array<format_arg, Count> args{}; + }; + + template<typename ValueType> + auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) + -> void + { + auto typed_value_pointer = static_cast<ValueType const *>(value_pointer); + auto fmt = formatter<std::remove_cvref_t<ValueType>>{}; + auto const it = fmt.parse(parse_context); + parse_context.advance_to(it); + fmt.format(*typed_value_pointer, context); + } + + template<typename... Arguments> + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store<sizeof...(Arguments)> + { + if constexpr (sizeof...(Arguments) == 0) + { + return format_arg_store<0>{}; + } + else + { + return format_arg_store<sizeof...(Arguments)>{std::array<format_arg, sizeof...(Arguments)>{ + format_arg{static_cast<void const *>(&args), format_trampoline<Arguments>}...}}; + } + } + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp index b5c7d21..863047d 100644 --- a/libs/kstd/include/kstd/bits/format_context.hpp +++ b/libs/kstd/include/kstd/bits/format_context.hpp @@ -3,11 +3,100 @@ // IWYU pragma: private, include <kstd/format> +#include "kstd/os/error.hpp" + +#include <cstddef> #include <string_view> namespace kstd { + constexpr auto report_format_error(char const * message) -> void + { + if consteval + { + extern void compile_time_format_error_triggered(char const *); + compile_time_format_error_triggered(message); + } + else + { + kstd::os::panic("Error while formatting a string."); + } + } + + struct format_parse_context + { + using iterator = std::string_view::const_iterator; + + constexpr format_parse_context(std::string_view format, std::size_t argument_count) + : m_current(format.begin()) + , m_end(format.end()) + , m_argument_count(argument_count) + {} + + [[nodiscard]] constexpr auto begin() const -> iterator + { + return m_current; + } + + [[nodiscard]] constexpr auto end() const -> iterator + { + return m_end; + } + + constexpr auto advance_to(iterator position) -> void + { + m_current = position; + } + + constexpr auto next_arg_id() -> std::size_t + { + if (m_mode == index_mode::manual) + { + report_format_error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::automatic; + + if (m_next_argument_id >= m_argument_count) + { + report_format_error("Argument index out of bounds."); + } + return m_next_argument_id++; + } + + constexpr auto check_arg_id(std::size_t index) -> void + { + if (m_mode == index_mode::automatic) + { + report_format_error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::manual; + + if (index >= m_argument_count) + { + report_format_error("Argument index out of bounds."); + } + } + + private: + enum class index_mode + { + unknown, + automatic, + manual, + }; + + iterator m_current{}; + iterator m_end{}; + index_mode m_mode{}; + std::size_t m_next_argument_id{}; + std::size_t m_argument_count{}; + + public: + }; + struct format_context { using writer_function = void(void *, std::string_view); diff --git a/libs/kstd/include/kstd/bits/format_specifiers.hpp b/libs/kstd/include/kstd/bits/format_specifiers.hpp new file mode 100644 index 0000000..8fb50ef --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp @@ -0,0 +1,204 @@ +#ifndef KSTD_BITS_FORMAT_SPECS_HPP +#define KSTD_BITS_FORMAT_SPECS_HPP + +// IWYU pragma: private + +#include "kstd/bits/format_context.hpp" + +#include <cstddef> +#include <cstdint> +#include <iterator> + +namespace kstd::bits +{ + enum struct alignment : std::uint8_t + { + none, + left, + right, + center, + }; + + enum struct sign_mode : std::uint8_t + { + none, + plus, + minus, + space, + }; + + enum struct width_mode : std::uint8_t + { + none, + static_value, + dynamic_argument_id + }; + + struct format_specifiers + { + char fill{' '}; + alignment align{}; + sign_mode sign{}; + bool alternative_form{}; + bool zero_pad{}; + + width_mode width_mode{}; + std::size_t width_value{}; + char type{}; + }; + + struct format_padding + { + std::size_t left{}; + std::size_t right{}; + }; + + constexpr auto parse_format_specifiers(format_parse_context & context) -> format_specifiers + { + auto specifiers = format_specifiers{}; + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it == '}') + { + return specifiers; + } + + if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^')) + { + specifiers.fill = *it; + switch (*std::next(it)) + { + case '<': + specifiers.align = alignment::left; + break; + case '>': + specifiers.align = alignment::right; + break; + case '^': + default: + specifiers.align = alignment::center; + break; + } + std::advance(it, 2); + } + else if (*it == '<' || *it == '>' || *it == '^') + { + switch (*it) + { + case '<': + specifiers.align = alignment::left; + break; + case '>': + specifiers.align = alignment::right; + break; + case '^': + default: + specifiers.align = alignment::center; + break; + } + std::advance(it, 1); + } + + if (it != end && (*it == '+' || *it == '-' || *it == ' ')) + { + switch (*it) + { + case '+': + specifiers.sign = sign_mode::plus; + break; + case '-': + specifiers.sign = sign_mode::minus; + break; + case ' ': + default: + specifiers.sign = sign_mode::space; + break; + } + std::advance(it, 1); + } + + if (it != end && *it == '#') + { + specifiers.alternative_form = true; + std::advance(it, 1); + } + + if (it != end && *it == '0') + { + specifiers.zero_pad = true; + std::advance(it, 1); + } + + if (it != end && *it == '{') + { + specifiers.width_mode = width_mode::dynamic_argument_id; + std::advance(it, 1); + auto argument_id = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0'); + std::advance(it, 1); + } + context.check_arg_id(argument_id); + } + else + { + argument_id = context.next_arg_id(); + } + + if (it == end || *it != '}') + { + report_format_error("Expected '}' for dynamic width."); + } + std::advance(it, 1); + specifiers.width_value = argument_id; + } + else if (it != end && *it >= '0' && *it <= '9') + { + specifiers.width_mode = width_mode::static_value; + while (it != end && *it >= '0' && *it <= '9') + { + specifiers.width_value = specifiers.width_value * 10 + static_cast<std::size_t>(*it - '0'); + std::advance(it, 1); + } + } + + context.advance_to(it); + return specifiers; + } + + constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length, + alignment requested_alignment, alignment default_alignment) -> format_padding + { + if (target_width <= content_length) + { + return {}; + } + + auto total_padding = target_width - content_length; + auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment; + + switch (effective_alignment) + { + case alignment::center: + { + auto left = total_padding / 2; + auto right = total_padding - left; + return {left, right}; + } + case alignment::left: + return {0, total_padding}; + case alignment::right: + default: + return {total_padding, 0}; + break; + } + } + +} // namespace kstd::bits + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_specs.hpp b/libs/kstd/include/kstd/bits/format_specs.hpp deleted file mode 100644 index 092a875..0000000 --- a/libs/kstd/include/kstd/bits/format_specs.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_SPECS_HPP -#define KSTD_BITS_FORMAT_SPECS_HPP - -// IWYU pragma: private - -#include <cstddef> -#include <iterator> -#include <string_view> - -namespace kstd::bits -{ - - struct format_specs - { - std::size_t width{}; - char fill{' '}; - char type{}; - bool align_left{}; - bool sign_plus{}; - bool sign_space{}; - bool alternative_form{}; - bool zero_pad{}; - }; - - constexpr auto parse_specs(std::string_view string, format_specs & specs) -> std::string_view - { - auto current = string.begin(); - auto end = string.end(); - - if (current == end || *current != ':') - { - return {current, end}; - } - - std::advance(current, 1); - - if (current != end && std::next(current) != end && (*std::next(current) == '<' || *std::next(current) == '>')) - { - specs.fill = *current; - specs.align_left = *std::next(current) == '<'; - std::advance(current, 2); - } - else if (current != end) - { - if (*current == '<') - { - specs.align_left = true; - std::advance(current, 1); - } - else if (*current == '>') - { - specs.align_left = false; - std::advance(current, 1); - } - } - - if (current != end) - { - if (*current == '+') - { - specs.sign_plus = true; - std::advance(current, 1); - } - else if (*current == ' ') - { - specs.sign_space = true; - std::advance(current, 1); - } - else if (*current == '-') - { - std::advance(current, 1); - } - } - - if (current != end && *current == '#') - { - specs.alternative_form = true; - std::advance(current, 1); - } - - if (current != end && *current == '0') - { - specs.zero_pad = true; - std::advance(current, 1); - } - - while (current != end && *current >= '0' && *current <= '9') - { - specs.width = specs.width * 10 + (*current - '0'); - std::advance(current, 1); - } - - if (current != end && *current != '}') - { - specs.type = *current; - std::advance(current, 1); - } - - return {current, end}; - } - -} // namespace kstd::bits - -#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp index 3a15bf0..cbdfb7d 100644 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ b/libs/kstd/include/kstd/bits/format_string.hpp @@ -3,127 +3,114 @@ // IWYU pragma: private, include <kstd/format> -#include <algorithm> +#include "kstd/bits/format_context.hpp" + #include <cstddef> -#include <iterator> #include <string_view> +#include <type_traits> namespace kstd { + template<typename> + struct formatter; + namespace bits { - auto invalid_format_string(char const *) -> void; - - consteval auto validate_format_string(std::string_view string, std::size_t argument_count) -> void + template<typename... Args> + constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void { - auto next_automatic_index = 0uz; - auto placeholder_count = 0uz; - auto has_manual_index = false; - auto has_automatic_index = false; + auto found = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index && !found) + { + using decay_type = std::remove_cvref_t<Args>; + auto fmt = formatter<decay_type>{}; + + auto it = fmt.parse(context); + context.advance_to(it); + found = true; + } + ++current_index; + }()); + + if (!found) + { + report_format_error("Argument index out of bounds."); + } + } + } // namespace bits - auto current = string.begin(); - auto end = string.end(); + template<typename... Args> + struct format_string + { + template<std::size_t Size> + consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT + : str_view{str} + { + auto context = format_parse_context{str_view, sizeof...(Args)}; + auto it = context.begin(); - while (current != end) + while (it != context.end()) { - if (*current == '{') + if (*it == '{') { - if (std::next(current) != end && *std::next(current) == '{') + ++it; + if (it != context.end() && *it == '{') { - std::advance(current, 2); + ++it; + context.advance_to(it); continue; } - std::advance(current, 1); - - auto index = 0uz; - auto is_manual_index = false; + context.advance_to(it); + auto argument_id = 0uz; - if (current != end && *current >= '0' && *current <= '9') + if (it != context.end() && *it >= '0' && *it <= '9') { - is_manual_index = true; - while (current != end && *current >= '0' && *current <= '9') + while (it != context.end() && *it >= '0' && *it <= '9') { - index = index * 10 + (*current - '0'); - std::advance(current, 1); - } - } - - if (is_manual_index) - { - placeholder_count = std::max(placeholder_count, index + 1); - if (has_automatic_index) - { - invalid_format_string("Cannot mix automatic and manual indexing."); - } - has_manual_index = true; - if (index >= argument_count) - { - invalid_format_string("Argument index out of range"); + argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0'); + ++it; } + context.check_arg_id(argument_id); + context.advance_to(it); } else { - if (has_manual_index) - { - invalid_format_string("Cannot mix automatic and manual indexing."); - } - has_automatic_index = true; - ++placeholder_count; - if (next_automatic_index >= argument_count) - { - invalid_format_string("Not enough arguments provided for format string."); - } - index = next_automatic_index++; + argument_id = context.next_arg_id(); } - while (current != end && *current != '}') + if (it != context.end() && *it == ':') { - std::advance(current, 1); + ++it; + context.advance_to(it); } - if (current == end) + bits::validate_argument<Args...>(argument_id, context); + + it = context.begin(); + if (it == context.end() || *it != '}') { - invalid_format_string("Unexpected end of format string."); + report_format_error("Missing closing '}' in format string."); } - std::advance(current, 1); } - else if (*current == '}') + else if (*it == '}') { - if (std::next(current) != end && *std::next(current) == '}') + ++it; + if (it != context.end() && *it == '}') { - std::advance(current, 2); - continue; + report_format_error("Unescaped '}' in format string."); } - invalid_format_string("Unexpected '}' in format string."); - } - else - { - std::advance(current, 1); } - } - if (argument_count < placeholder_count) - { - invalid_format_string("Not enough arguments provided for format string."); - } - else if (argument_count > placeholder_count) - { - invalid_format_string("Too many arguments provided for format string."); + ++it; + context.advance_to(it); } } - } // namespace bits - - template<typename... Args> - struct format_string - { - std::string_view str; - consteval format_string(char const * format) - : str{format} - { - bits::validate_format_string(str, sizeof...(Args)); - } + std::string_view str_view; }; } // namespace kstd diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp index 7c9c31d..e9a45e0 100644 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -3,8 +3,9 @@ // IWYU pragma: private, include <kstd/format> -#include <kstd/bits/format_context.hpp> -#include <kstd/bits/format_specs.hpp> +#include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_specifiers.hpp" + #include <kstd/os/error.hpp> #include <array> @@ -21,30 +22,98 @@ namespace kstd { - template<typename T> + template<typename> struct formatter; + template<> + struct formatter<std::string_view> + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto it = context.begin(); + + if (it != context.end() && *it == 's') + { + ++it; + } + + if (it != context.end() && *it != '}') + { + report_format_error("Invalid specifier for string_view."); + } + return it; + } + + auto format(std::string_view const & string, format_context & context) const -> void + { + context.push(string); + } + }; + + template<> + struct formatter<char const *> : formatter<std::string_view> + { + auto format(char const * string, format_context & context) const -> void + { + formatter<std::string_view>::format(string ? std::string_view{string} : "(null)", context); + } + }; + template<std::integral T> struct formatter<T> { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; + + constexpr auto static maximum_digits = 80; - constexpr auto parse(std::string_view context) -> std::string_view + enum struct base { - return bits::parse_specs(context, specs); + bin = 2, + oct = 8, + dec = 10, + hex = 16, + }; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + report_format_error("Invalid type specifier for integral type."); + } + } + + if (it != end && *it != '}') + { + report_format_error("Missing terminating '}' in format string."); + } + + return it; } auto format(T value, format_context & context) const -> void { - enum struct base + auto final_width = 0uz; + if (specifiers.width_mode == bits::width_mode::static_value) { - bin = 2, - oct = 8, - dec = 10, - hex = 16, - }; - - constexpr auto static maximum_digits = 80; + final_width = specifiers.width_value; + } + else if (specifiers.width_mode == bits::width_mode::dynamic_argument_id) + { + // TODO: implement argument references so we can read the dynamic width argument. + final_width = 0; + } using unsigned_T = std::make_unsigned_t<T>; auto absolute_value = static_cast<unsigned_T>(value); @@ -55,11 +124,11 @@ namespace kstd if (value < 0) { is_negative = true; - absolute_value = 0 - value; + absolute_value = 0 - static_cast<unsigned_T>(value); } } - auto const base = [type = specs.type] -> auto { + auto const base = [type = specifiers.type] -> auto { switch (type) { case 'x': @@ -77,7 +146,7 @@ namespace kstd }(); auto buffer = std::array<char, maximum_digits>{}; - auto digits = (specs.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; + auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; auto current = buffer.rbegin(); if (absolute_value == 0) @@ -95,21 +164,22 @@ namespace kstd } } + auto content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current)); auto prefix = std::array<char, 2>{'0', '\0'}; auto prefix_length = 0uz; - if (specs.alternative_form) + if (specifiers.alternative_form) { switch (base) { case base::bin: - prefix[1] = specs.type == 'B' ? 'B' : 'b'; + prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; prefix_length = 2; break; case base::oct: prefix_length = 1; break; case base::hex: - prefix[1] = specs.type == 'X' ? 'X' : 'x'; + prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; prefix_length = 2; break; default: @@ -122,53 +192,50 @@ namespace kstd { sign_character = '-'; } - else if (specs.sign_plus) + else if (specifiers.sign == bits::sign_mode::plus) { sign_character = '+'; } - else if (specs.sign_space) + else if (specifiers.sign == bits::sign_mode::space) { sign_character = ' '; } - auto const content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current)); auto const total_length = content_length + prefix_length + (sign_character != '\0'); - auto const padding_length = (specs.width > total_length) ? (specs.width - total_length) : 0; + auto const padding = + bits::calculate_format_padding(final_width, total_length, specifiers.align, bits::alignment::right); + auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::alignment::none); - if (!specs.align_left && !specs.zero_pad) + if (!effective_zero_pad) { - for (auto i = 0uz; i < padding_length; ++i) + for (auto i = 0uz; i < padding.left; ++i) { - context.push(specs.fill); + context.push(specifiers.fill); } } - if (sign_character) + if (sign_character != '\0') { context.push(sign_character); } - - if (prefix_length) + if (prefix_length > 0) { - context.push({prefix.data(), prefix_length}); + context.push(std::string_view{prefix.data(), prefix_length}); } - if (!specs.align_left && specs.zero_pad) + if (effective_zero_pad) { - for (auto i = 0uz; i < padding_length; ++i) + for (auto i = 0uz; i < padding.left; ++i) { context.push('0'); } } - context.push({current.base(), content_length}); + context.push(std::string_view{current.base(), content_length}); - if (specs.align_left) + for (auto i = 0uz; i < padding.right; ++i) { - for (auto i = 0uz; i < padding_length; ++i) - { - context.push(specs.fill); - } + context.push(specifiers.fill); } } }; @@ -176,13 +243,13 @@ namespace kstd template<typename T> struct formatter<T const *> : formatter<std::uintptr_t> { - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { auto result = formatter<std::uintptr_t>::parse(context); - if (!this->specs.type) + if (!this->specifiers.type) { - this->specs.type = 'p'; - this->specs.alternative_form = true; + this->specifiers.type = 'p'; + this->specifiers.alternative_form = true; } return result; } @@ -199,87 +266,64 @@ namespace kstd }; template<> - struct formatter<std::string_view> + struct formatter<char *> : formatter<char const *> { - bits::format_specs specs{}; + }; - constexpr auto parse(std::string_view context) -> std::string_view - { - return bits::parse_specs(context, specs); - } + template<> + struct formatter<bool> + { + bits::format_specifiers specifiers{}; - auto format(std::string_view string, format_context & context) const -> void + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - auto const content_length = string.size(); - auto const padding_length = (specs.width > content_length) ? (specs.width - content_length) : 0; + specifiers = bits::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); - if (!specs.align_left) + if (it != end && *it != '}') { - for (auto i = 0uz; i < padding_length; ++i) + if (*it == 's') { - context.push(specs.fill); + specifiers.type = *it; + std::advance(it, 1); } - } - - context.push(string); - - if (specs.align_left) - { - for (auto i = 0uz; i < padding_length; ++i) + else { - context.push(specs.fill); + report_format_error("Invalid type specifier for bool."); } } - } - }; - template<> - struct formatter<char const *> - { - bits::format_specs specs{}; + if (it != end && *it != '}') + { + report_format_error("Missing terminating '}' in format string."); + } - constexpr auto parse(std::string_view context) -> std::string_view - { - return bits::parse_specs(context, specs); + return it; } - auto format(char const * string, format_context & context) const -> void + auto format(bool value, format_context & context) const -> void { - if (string) - { - formatter<std::string_view>{specs}.format(string, context); - } - else + auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; + auto final_width = 0uz; + if (specifiers.width_mode == bits::width_mode::static_value) { - formatter<std::string_view>{specs}.format("(null)", context); + final_width = specifiers.width_value; } - } - }; - template<> - struct formatter<char *> : formatter<char const *> - { - }; + auto padding = bits::calculate_format_padding(final_width, text.size(), specifiers.align, bits::alignment::left); - template<> - struct formatter<bool> - { - bits::format_specs specs{}; - - constexpr auto parse(std::string_view context) -> std::string_view - { - return bits::parse_specs(context, specs); - } - - auto format(bool value, format_context & context) const -> void - { - if (specs.type == 's' || specs.type == '\0') + for (auto i = 0uz; i < padding.left; ++i) { - context.push(value ? "true" : "false"); + context.push(specifiers.fill); } - else + + context.push(text); + + for (auto i = 0uz; i < padding.right; ++i) { - formatter<int>{specs}.format(static_cast<int>(value), context); + |
