From 4f942e014dab44ccb8850c5921b81d4bd777d831 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 11:19:05 +0100 Subject: kstd: rework formatting to be closer to std --- kapi/include/kapi/memory/address.hpp | 8 +- kernel/kstd/print.cpp | 137 +++++---- libs/kstd/include/kstd/bits/format_args.hpp | 62 +++++ libs/kstd/include/kstd/bits/format_context.hpp | 89 ++++++ libs/kstd/include/kstd/bits/format_specifiers.hpp | 204 ++++++++++++++ libs/kstd/include/kstd/bits/format_specs.hpp | 104 ------- libs/kstd/include/kstd/bits/format_string.hpp | 151 +++++----- libs/kstd/include/kstd/bits/formatter.hpp | 325 +++++++++++----------- libs/kstd/include/kstd/bits/print_sink.hpp | 2 - libs/kstd/include/kstd/os/print.hpp | 2 +- libs/kstd/include/kstd/print | 20 +- 11 files changed, 692 insertions(+), 412 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format_args.hpp create mode 100644 libs/kstd/include/kstd/bits/format_specifiers.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_specs.hpp diff --git a/kapi/include/kapi/memory/address.hpp b/kapi/include/kapi/memory/address.hpp index 587aeac..13bdf4c 100644 --- a/kapi/include/kapi/memory/address.hpp +++ b/kapi/include/kapi/memory/address.hpp @@ -229,13 +229,13 @@ namespace kstd { constexpr auto static suffix = Type == kapi::memory::address_type::linear ? "%lin" : "%phy"; - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { auto result = formatter::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; } diff --git a/kernel/kstd/print.cpp b/kernel/kstd/print.cpp index c7d26ba..44f80a7 100644 --- a/kernel/kstd/print.cpp +++ b/kernel/kstd/print.cpp @@ -70,74 +70,113 @@ namespace kstd::os auto writer = write_buffer{(sink == print_sink::stderr) ? kapi::cio::output_stream::stderr : kapi::cio::output_stream::stdout}; auto context = kstd::format_context{.writer = write_buffer::callback, .user_data = &writer}; + auto parse_context = kstd::format_parse_context{format, args.size()}; - auto current = format.begin(); - auto end = format.end(); - auto next_automatic_index = 0uz; + auto it = parse_context.begin(); + auto end = parse_context.end(); - while (current != end) + while (it != end) { - if (*current != '{') + if (*it != '{' && *it != '}') { - auto start = current; - while (current != end && *current != '{') + auto start = it; + while (it != end && *it != '{' && *it != '}') { - std::advance(current, 1); + std::advance(it, 1); } - context.push(std::string_view(start, current - start)); + parse_context.advance_to(it); + context.push(std::string_view(start, it - start)); continue; } - if (std::next(current) != end && *(std::next(current)) == '{') + if (*it == '{') { - context.push('{'); - std::advance(current, 2); - continue; - } + std::advance(it, 1); + if (it != end && *it == '{') + { + context.push('{'); + std::advance(it, 1); + parse_context.advance_to(it); + continue; + } - std::advance(current, 1); + parse_context.advance_to(it); + auto index = 0uz; - auto index = 0uz; - if (current != end && *current >= '0' && *current <= '9') - { - while (current != end && *current >= '0' && *current <= '9') + if (it != end && *it >= '0' && *it <= '9') { - index = index * 10 + static_cast(*current - '0'); - std::advance(current, 1); + while (it != end && *it >= '0' && *it <= '9') + { + index = index * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + parse_context.check_arg_id(index); + } + else + { + index = parse_context.next_arg_id(); } - } - else - { - index = next_automatic_index++; - } - auto remaining_fmt = std::string_view{current, static_cast(std::distance(current, end))}; + if (it != end && *it == ':') + { + std::advance(it, 1); + } - auto const arg = args.get(index); - if (arg.format) - { - auto const after_specs = arg.format(arg.value, remaining_fmt, context); - auto const consumed = remaining_fmt.size() - after_specs.size(); - std::advance(current, consumed); - } - else - { - context.push("{?}"); - while (current != end && *current != '}') - std::advance(current, 1); - } + parse_context.advance_to(it); - if (current != end && *current == '}') - { - std::advance(current, 1); + if (index < args.size()) + { + auto const & arg = args[index]; + if (arg.format_function) + { + arg.format_function(arg.value_pointer, parse_context, context); + } + else + { + context.push("{?}"); + } + } + else + { + context.push("{fmt-err: bound}"); + } + + it = parse_context.begin(); + + if (it != end && *it == '}') + { + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unconsumed}"); + while (it != end && *it != '}') + { + std::advance(it, 1); + } + + if (it != end) + { + std::advance(it, 1); + parse_context.advance_to(it); + } + } } - else + else if (*it == '}') { - context.push("{fmt-err}"); - while (current != end && *current != '}') - std::advance(current, 1); - if (current != end) - std::advance(current, 1); + std::advance(it, 1); + if (it != end && *it == '}') + { + context.push('}'); + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unescaped}"); + parse_context.advance_to(it); + } } } } 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 + +#include "kstd/bits/format_context.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + template + 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; + + template + struct format_arg_store + { + std::array args{}; + }; + + template + auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context) + -> void + { + auto typed_value_pointer = static_cast(value_pointer); + auto fmt = formatter>{}; + auto const it = fmt.parse(parse_context); + parse_context.advance_to(it); + fmt.format(*typed_value_pointer, context); + } + + template + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store + { + if constexpr (sizeof...(Arguments) == 0) + { + return format_arg_store<0>{}; + } + else + { + return format_arg_store{std::array{ + format_arg{static_cast(&args), format_trampoline}...}}; + } + } + +} // 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 +#include "kstd/os/error.hpp" + +#include #include 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 +#include +#include + +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(*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(*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 -#include -#include - -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 -#include +#include "kstd/bits/format_context.hpp" + #include -#include #include +#include namespace kstd { + template + 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 + 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; + auto fmt = formatter{}; + + 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 + struct format_string + { + template + 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(*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(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 - 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 -#include -#include +#include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_specifiers.hpp" + #include #include @@ -21,30 +22,98 @@ namespace kstd { - template + template struct formatter; + template<> + struct formatter + { + 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 : formatter + { + auto format(char const * string, format_context & context) const -> void + { + formatter::format(string ? std::string_view{string} : "(null)", context); + } + }; + template struct formatter { - 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; auto absolute_value = static_cast(value); @@ -55,11 +124,11 @@ namespace kstd if (value < 0) { is_negative = true; - absolute_value = 0 - value; + absolute_value = 0 - static_cast(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{}; - 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::distance(buffer.rbegin(), current)); auto prefix = std::array{'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::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 struct formatter : formatter { - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { auto result = formatter::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 + struct formatter : formatter { - bits::format_specs specs{}; + }; - constexpr auto parse(std::string_view context) -> std::string_view - { - return bits::parse_specs(context, specs); - } + template<> + struct formatter + { + 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 - { - 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{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{specs}.format("(null)", context); + final_width = specifiers.width_value; } - } - }; - template<> - struct formatter : formatter - { - }; + auto padding = bits::calculate_format_padding(final_width, text.size(), specifiers.align, bits::alignment::left); - template<> - struct formatter - { - 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{specs}.format(static_cast(value), context); + context.push(specifiers.fill); } } }; @@ -287,30 +331,31 @@ namespace kstd template<> struct formatter { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - return bits::parse_specs(context, specs); + specifiers = bits::parse_format_specifiers(context); + return context.begin(); } auto format(std::strong_ordering value, format_context & context) const -> void { if (value == std::strong_ordering::equal) { - return context.push(specs.alternative_form ? "==" : "equal"); + return context.push(specifiers.alternative_form ? "==" : "equal"); } else if (value == std::strong_ordering::equivalent) { - return context.push(specs.alternative_form ? "==" : "equivalent"); + return context.push(specifiers.alternative_form ? "==" : "equivalent"); } else if (value == std::strong_ordering::greater) { - return context.push(specs.alternative_form ? ">" : "greater"); + return context.push(specifiers.alternative_form ? ">" : "greater"); } else if (value == std::strong_ordering::less) { - return context.push(specs.alternative_form ? "<" : "less"); + return context.push(specifiers.alternative_form ? "<" : "less"); } kstd::os::panic("[kstd:format] Invalid strong ordering value!"); } @@ -319,26 +364,27 @@ namespace kstd template<> struct formatter { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - return bits::parse_specs(context, specs); + specifiers = bits::parse_format_specifiers(context); + return context.begin(); } auto format(std::weak_ordering value, format_context & context) const -> void { if (value == std::weak_ordering::equivalent) { - return context.push(specs.alternative_form ? "==" : "equivalent"); + return context.push(specifiers.alternative_form ? "==" : "equivalent"); } else if (value == std::weak_ordering::greater) { - return context.push(specs.alternative_form ? ">" : "greater"); + return context.push(specifiers.alternative_form ? ">" : "greater"); } else if (value == std::weak_ordering::less) { - return context.push(specs.alternative_form ? "<" : "less"); + return context.push(specifiers.alternative_form ? "<" : "less"); } kstd::os::panic("[kstd:format] Invalid weak ordering value!"); } @@ -347,71 +393,36 @@ namespace kstd template<> struct formatter { - bits::format_specs specs{}; + bits::format_specifiers specifiers{}; - constexpr auto parse(std::string_view context) -> std::string_view + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator { - return bits::parse_specs(context, specs); + specifiers = bits::parse_format_specifiers(context); + return context.begin(); } auto format(std::partial_ordering value, format_context & context) const -> void { if (value == std::partial_ordering::equivalent) { - return context.push(specs.alternative_form ? "==" : "equivalent"); + return context.push(specifiers.alternative_form ? "==" : "equivalent"); } else if (value == std::partial_ordering::greater) { - return context.push(specs.alternative_form ? ">" : "greater"); + return context.push(specifiers.alternative_form ? ">" : "greater"); } else if (value == std::partial_ordering::less) { - return context.push(specs.alternative_form ? "<" : "less"); + return context.push(specifiers.alternative_form ? "<" : "less"); } else if (value == std::partial_ordering::unordered) { - return context.push(specs.alternative_form ? "<=>" : "unordered"); + return context.push(specifiers.alternative_form ? "<=>" : "unordered"); } kstd::os::panic("[kstd:format] Invalid partial ordering value!"); } }; - struct format_arg - { - using formatting_function = std::string_view(void const *, std::string_view, format_context &); - - void const * value; - formatting_function * format; - }; - - struct format_args - { - constexpr format_args(format_arg const * args, std::size_t number_of_args) - : m_args(args) - , m_number_of_args(number_of_args) - {} - - [[nodiscard]] constexpr auto get(std::size_t index) const -> format_arg - { - if (index >= m_number_of_args) - return {.value = nullptr, .format = nullptr}; - return m_args[index]; - } - - private: - format_arg const * m_args; - std::size_t m_number_of_args; - }; - - template - auto format_dispatcher(void const * value, std::string_view format_spec, format_context & context) -> std::string_view - { - auto formatter_for_T = formatter{}; - auto const remainder = formatter_for_T.parse(format_spec); - formatter_for_T.format(*static_cast(value), context); - return remainder; - } - } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/print_sink.hpp b/libs/kstd/include/kstd/bits/print_sink.hpp index 0e0955c..af765e0 100644 --- a/libs/kstd/include/kstd/bits/print_sink.hpp +++ b/libs/kstd/include/kstd/bits/print_sink.hpp @@ -3,8 +3,6 @@ // IWYU pragma: private, include -#include - namespace kstd { diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/include/kstd/os/print.hpp index f189042..0dde6e7 100644 --- a/libs/kstd/include/kstd/os/print.hpp +++ b/libs/kstd/include/kstd/os/print.hpp @@ -1,7 +1,7 @@ #ifndef KSTD_OS_PRINT_HPP #define KSTD_OS_PRINT_HPP -#include "kstd/bits/formatter.hpp" +#include "kstd/bits/format_args.hpp" #include "kstd/bits/print_sink.hpp" #include diff --git a/libs/kstd/include/kstd/print b/libs/kstd/include/kstd/print index ffafda9..1ab24bd 100644 --- a/libs/kstd/include/kstd/print +++ b/libs/kstd/include/kstd/print @@ -1,12 +1,12 @@ #ifndef KSTD_PRINT #define KSTD_PRINT +#include "bits/format_args.hpp" #include "bits/print_sink.hpp" // IWYU pragma: export #include "os/print.hpp" #include -#include #include namespace kstd @@ -19,13 +19,10 @@ namespace kstd //! @param args The arguments to use to place in the format string's placeholders. template // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) - auto print(kstd::format_string...> format, Args &&... args) -> void + auto print(kstd::format_string...> format, Args const &... args) -> void { - auto arguments = std::array{ - kstd::format_arg{&args, kstd::format_dispatcher>} - ... - }; - os::vprint(print_sink::stdout, format.str, kstd::format_args{arguments.data(), sizeof...(Args)}); + auto const arg_store = kstd::make_format_args(args...); + os::vprint(print_sink::stdout, format.str_view, arg_store.args); } //! @qualifier kernel-defined @@ -37,11 +34,8 @@ namespace kstd // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) auto print(print_sink sink, kstd::format_string...> format, Args &&... args) -> void { - auto arguments = std::array{ - kstd::format_arg{&args, kstd::format_dispatcher>} - ... - }; - os::vprint(sink, format.str, kstd::format_args{arguments.data(), sizeof...(Args)}); + auto const arg_store = kstd::make_format_args(args...); + os::vprint(sink, format.str_view, arg_store.args); } //! @qualifier kernel-defined @@ -54,7 +48,7 @@ namespace kstd // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) auto println(kstd::format_string...> format, Args &&... args) -> void { - print(format, std::forward(args)...); + print(print_sink::stdout, format, std::forward(args)...); print(print_sink::stdout, "\n"); } -- cgit v1.2.3