diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 12:01:22 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 12:01:22 +0100 |
| commit | d7147ddd7416a6cce874d290d1615527f4d7cdb9 (patch) | |
| tree | f30bd8e8f7f734085ae58c3002519a756ef06091 /libs | |
| parent | 4f942e014dab44ccb8850c5921b81d4bd777d831 (diff) | |
| download | teachos-d7147ddd7416a6cce874d290d1615527f4d7cdb9.tar.xz teachos-d7147ddd7416a6cce874d290d1615527f4d7cdb9.zip | |
kstd/format: implement dynamic width support
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_args.hpp | 32 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_context.hpp | 114 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_parse_context.hpp | 122 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_specifiers.hpp | 5 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_string.hpp | 30 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/formatter.hpp | 11 |
6 files changed, 216 insertions, 98 deletions
diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp index d236a23..71ee5ae 100644 --- a/libs/kstd/include/kstd/bits/format_args.hpp +++ b/libs/kstd/include/kstd/bits/format_args.hpp @@ -4,28 +4,20 @@ // IWYU pragma: private, include <kstd/format> #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" #include <array> #include <cstddef> -#include <span> #include <type_traits> namespace kstd { + struct format_context; + struct format_parse_context; + 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 { @@ -43,6 +35,20 @@ namespace kstd 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)> { @@ -53,7 +59,7 @@ namespace kstd else { return format_arg_store<sizeof...(Arguments)>{std::array<format_arg, sizeof...(Arguments)>{ - format_arg{static_cast<void const *>(&args), format_trampoline<Arguments>}...}}; + format_arg{static_cast<void const *>(&args), format_trampoline<Arguments>, get_size_trampoline<Arguments>}...}}; } } diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp index 863047d..05e37ca 100644 --- a/libs/kstd/include/kstd/bits/format_context.hpp +++ b/libs/kstd/include/kstd/bits/format_context.hpp @@ -3,106 +3,60 @@ // IWYU pragma: private, include <kstd/format> -#include "kstd/os/error.hpp" +#include <kstd/os/error.hpp> +#include <concepts> #include <cstddef> +#include <span> #include <string_view> namespace kstd { - constexpr auto report_format_error(char const * message) -> void + namespace bits { - 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."); - } + 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 - { - 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; + struct format_parse_context; + struct format_context; - 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."); - } - } + 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; - 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: + 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 index 8fb50ef..00cca40 100644 --- a/libs/kstd/include/kstd/bits/format_specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" #include <cstddef> #include <cstdint> @@ -143,11 +144,11 @@ namespace kstd::bits argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0'); std::advance(it, 1); } - context.check_arg_id(argument_id); + context.check_dynamic_width_id(argument_id); } else { - argument_id = context.next_arg_id(); + argument_id = context.next_dynamic_width_id(); } if (it == end || *it != '}') diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp index cbdfb7d..f16f1ee 100644 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ b/libs/kstd/include/kstd/bits/format_string.hpp @@ -4,7 +4,9 @@ // IWYU pragma: private, include <kstd/format> #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" +#include <array> #include <cstddef> #include <string_view> #include <type_traits> @@ -41,6 +43,30 @@ namespace kstd 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) + { + using decay_type = std::remove_cvref_t<Args>; + if constexpr (std::is_integral_v<decay_type>) + { + is_valid_integer = true; + } + } + ++current_index; + }()); + + if (!is_valid_integer) + { + report_format_error("Dynamic width argument must be an integral object."); + } + } } // namespace bits template<typename... Args> @@ -50,7 +76,9 @@ namespace kstd consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT : str_view{str} { - auto context = format_parse_context{str_view, sizeof...(Args)}; + 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(); while (it != context.end()) diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp index e9a45e0..e46f6ad 100644 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private, include <kstd/format> #include "kstd/bits/format_context.hpp" +#include "kstd/bits/format_parse_context.hpp" #include "kstd/bits/format_specifiers.hpp" #include <kstd/os/error.hpp> @@ -111,8 +112,8 @@ namespace kstd } 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; + 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>; @@ -307,10 +308,16 @@ namespace kstd { 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) { 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); + } auto padding = bits::calculate_format_padding(final_width, text.size(), specifiers.align, bits::alignment::left); |
