diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 12:02:28 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 12:02:28 +0100 |
| commit | 24703d5b9bc297616b8baf8957ab7e74e2f67352 (patch) | |
| tree | f30bd8e8f7f734085ae58c3002519a756ef06091 /libs/kstd | |
| parent | 1865f7a162a496592e236ffcff171e7e7bc47ee2 (diff) | |
| parent | d7147ddd7416a6cce874d290d1615527f4d7cdb9 (diff) | |
| download | teachos-24703d5b9bc297616b8baf8957ab7e74e2f67352.tar.xz teachos-24703d5b9bc297616b8baf8957ab7e74e2f67352.zip | |
Merge branch 'fmorgner/align-format-with-stdlib' into develop-BA-FS26
Diffstat (limited to 'libs/kstd')
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_args.hpp | 68 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_context.hpp | 43 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_parse_context.hpp | 122 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_specifiers.hpp | 205 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_specs.hpp | 104 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_string.hpp | 173 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/formatter.hpp | 328 | ||||
| -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 |
10 files changed, 713 insertions, 354 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..71ee5ae --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_args.hpp @@ -0,0 +1,68 @@ +#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 "kstd/bits/format_parse_context.hpp" + +#include <array> +#include <cstddef> +#include <type_traits> + +namespace kstd +{ + struct format_context; + struct format_parse_context; + + template<typename> + struct formatter; + + 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 ValueType> + auto get_size_trampoline(void const * value_pointer) -> std::size_t + { + if constexpr (bits::is_format_width_compatible_v<ValueType>) + { + return static_cast<std::size_t>(*static_cast<ValueType const *>(value_pointer)); + } + else + { + report_format_error("Dynamic width argument is not an integral value."); + return 0; + } + } + + 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>, get_size_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..05e37ca 100644 --- a/libs/kstd/include/kstd/bits/format_context.hpp +++ b/libs/kstd/include/kstd/bits/format_context.hpp @@ -3,17 +3,60 @@ // IWYU pragma: private, include <kstd/format> +#include <kstd/os/error.hpp> + +#include <concepts> +#include <cstddef> +#include <span> #include <string_view> namespace kstd { + namespace bits + { + template<typename T> + constexpr auto inline is_format_width_compatible_v = std::integral<T> && // + !std::same_as<bool, T> && // + !std::same_as<char, T> && // + !std::same_as<wchar_t, T> && // + !std::same_as<char8_t, T> && // + !std::same_as<char16_t, T> && // + !std::same_as<char32_t, T>; + } + + struct format_parse_context; + struct format_context; + + struct format_arg + { + using format_function_type = auto(void const * value, format_parse_context & parse_context, + format_context & context) -> void; + using get_size_function_type = auto(void const * value) -> std::size_t; + + void const * value_pointer; + format_function_type * format_function; + get_size_function_type * get_size_function; + }; + + using format_args = std::span<format_arg const>; + struct format_context { using writer_function = void(void *, std::string_view); writer_function * writer; void * user_data; + format_args args; + + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & + { + if (id >= args.size()) + { + kstd::os::panic("[kstd:format] argument index out of range!"); + } + return args[id]; + } constexpr auto push(std::string_view string) -> void { diff --git a/libs/kstd/include/kstd/bits/format_parse_context.hpp b/libs/kstd/include/kstd/bits/format_parse_context.hpp new file mode 100644 index 0000000..76c3f03 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_parse_context.hpp @@ -0,0 +1,122 @@ +#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP +#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP + +// 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, + bool const * is_integral = nullptr) + : m_current{format.begin()} + , m_end{format.end()} + , m_argument_count{argument_count} + , m_is_integral{is_integral} + {} + + [[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."); + } + } + + constexpr auto check_dynamic_width_id(std::size_t id) -> void + { + check_arg_id(id); + if (m_is_integral && !m_is_integral[id]) + { + report_format_error("Dynamic width argument must be an integral object."); + } + } + + constexpr auto next_dynamic_width_id() -> std::size_t + { + auto const id = next_arg_id(); + if (m_is_integral && !m_is_integral[id]) + { + report_format_error("Dynamic width argument must be an integral object."); + } + return id; + } + + 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{}; + bool const * m_is_integral{}; + }; + +} // namespace kstd + +#endif
\ No newline at end of file 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..00cca40 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp @@ -0,0 +1,205 @@ +#ifndef KSTD_BITS_FORMAT_SPECS_HPP +#define KSTD_BITS_FORMAT_SPECS_HPP + +// IWYU pragma: private + +#include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_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_dynamic_width_id(argument_id); + } + else + { + argument_id = context.next_dynamic_width_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..f16f1ee 100644 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ b/libs/kstd/include/kstd/bits/format_string.hpp @@ -3,127 +3,142 @@ // IWYU pragma: private, include <kstd/format> -#include <algorithm> +#include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" + +#include <array> #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; - auto current = string.begin(); - auto end = string.end(); + (..., [&] { + 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; + }()); - while (current != end) + if (!found) { - if (*current == '{') + report_format_error("Argument index out of bounds."); + } + } + + template<typename... Args> + constexpr auto validate_dynamic_width(std::size_t target_index) -> void + { + auto is_valid_integer = false; + auto current_index = 0uz; + + (..., [&] { + if (current_index == target_index) { - if (std::next(current) != end && *std::next(current) == '{') + using decay_type = std::remove_cvref_t<Args>; + if constexpr (std::is_integral_v<decay_type>) { - std::advance(current, 2); - continue; + is_valid_integer = true; } + } + ++current_index; + }()); - std::advance(current, 1); + if (!is_valid_integer) + { + report_format_error("Dynamic width argument must be an integral object."); + } + } + } // namespace bits - auto index = 0uz; - auto is_manual_index = false; + 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 const is_width_compatible = std::array<bool, (sizeof...(Args) > 0 ? sizeof...(Args) : 1)>{ + bits::is_format_width_compatible_v<std::remove_cvref_t<Args>>...}; + auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; + auto it = context.begin(); - if (current != end && *current >= '0' && *current <= '9') + while (it != context.end()) + { + if (*it == '{') + { + ++it; + if (it != context.end() && *it == '{') { - is_manual_index = true; - while (current != end && *current >= '0' && *current <= '9') - { - index = index * 10 + (*current - '0'); - std::advance(current, 1); - } + ++it; + context.advance_to(it); + continue; } - if (is_manual_index) + context.advance_to(it); + auto argument_id = 0uz; + + if (it != context.end() && *it >= '0' && *it <= '9') { - placeholder_count = std::max(placeholder_count, index + 1); - if (has_automatic_index) + while (it != context.end() && *it >= '0' && *it <= '9') { - 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..e46f6ad 100644 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -3,8 +3,10 @@ // 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_parse_context.hpp" +#include "kstd/bits/format_specifiers.hpp" + #include <kstd/os/error.hpp> #include <array> @@ -21,30 +23,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) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = arg.get_size_function(arg.value_pointer); + } using unsigned_T = std::make_unsigned_t<T>; auto absolute_value = static_cast<unsigned_T>(value); @@ -55,11 +125,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 +147,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 +165,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 +193,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) |
