diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 14:03:45 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-03-20 14:03:45 +0100 |
| commit | 07cb15c42c16497b0b09b75886ce3baddeaaafb3 (patch) | |
| tree | d2c4d91e6f55b6c1694f0ddeb89a457e8061c3e1 /libs/kstd | |
| parent | 8365a09e18ac043d2e0aa6a9d3e394a02fe7c6cb (diff) | |
| download | kernel-07cb15c42c16497b0b09b75886ce3baddeaaafb3.tar.xz kernel-07cb15c42c16497b0b09b75886ce3baddeaaafb3.zip | |
kstd/format: split implementation
Diffstat (limited to 'libs/kstd')
21 files changed, 772 insertions, 621 deletions
diff --git a/libs/kstd/include/kstd/bits/format/arg.hpp b/libs/kstd/include/kstd/bits/format/arg.hpp new file mode 100644 index 0000000..92e6431 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/arg.hpp @@ -0,0 +1,26 @@ +#ifndef KSTD_BITS_FORMAT_ARG_HPP +#define KSTD_BITS_FORMAT_ARG_HPP + +// IWYU pragma: private, include <kstd/format> + +#include "fwd.hpp" + +#include <cstddef> + +namespace kstd +{ + + 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; + }; + +} // namespace kstd + +#endif
\ No newline at end of file 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..5cca3ff --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/args.hpp @@ -0,0 +1,75 @@ +#ifndef KSTD_BITS_FORMAT_ARGS_HPP +#define KSTD_BITS_FORMAT_ARGS_HPP + +// IWYU pragma: private, include <kstd/format> + +#include "context.hpp" +#include "error.hpp" +#include "fwd.hpp" +#include "parse_context.hpp" + +#include <array> +#include <cstddef> +#include <span> +#include <type_traits> + +namespace kstd +{ + + namespace bits::format + { + + 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 (is_width_v<ValueType>) + { + return static_cast<std::size_t>(*static_cast<ValueType const *>(value_pointer)); + } + else + { + error("Dynamic width argument is not an integral value."); + return 0; + } + } + + } // namespace bits::format + + 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... Arguments> + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store<sizeof...(Arguments)> + { + using namespace bits::format; + + 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 new file mode 100644 index 0000000..478a48f --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -0,0 +1,61 @@ +#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP +#define KSTD_BITS_FORMAT_CONTEXT_HPP + +// IWYU pragma: private, include <kstd/format> + +#include "arg.hpp" + +#include <kstd/os/error.hpp> + +#include <concepts> +#include <cstddef> +#include <span> +#include <string_view> + +namespace kstd +{ + + namespace bits::format + { + template<typename T> + constexpr auto inline is_width_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_context + { + using writer_function = void(void *, std::string_view); + using format_args = std::span<format_arg const>; + + 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 + { + writer(user_data, string); + } + + constexpr auto push(char character) -> void + { + writer(user_data, std::string_view(&character, 1)); + } + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/error.hpp b/libs/kstd/include/kstd/bits/format/error.hpp new file mode 100644 index 0000000..f0863eb --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/error.hpp @@ -0,0 +1,24 @@ +#ifndef KSTD_BITS_FORMAT_ERROR_HPP +#define KSTD_BITS_FORMAT_ERROR_HPP + +#include "kstd/os/error.hpp" + +namespace kstd::bits::format +{ + + constexpr auto 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."); + } + } + +} // namespace kstd::bits::format + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter.hpp b/libs/kstd/include/kstd/bits/format/formatter.hpp new file mode 100644 index 0000000..bff5f55 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter.hpp @@ -0,0 +1,14 @@ +#ifndef KSTD_BITS_FORMATTER_HPP +#define KSTD_BITS_FORMATTER_HPP + +// IWYU pragma: private, include <kstd/format> + +namespace kstd +{ + + template<typename> + struct formatter; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp new file mode 100644 index 0000000..bb6cacf --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -0,0 +1,83 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" + +#include <iterator> +#include <string_view> + +namespace kstd +{ + + template<> + struct formatter<bool> + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::parse_format_specifiers(context); + + auto it = context.begin(); + auto const end = context.end(); + + if (it != end && *it != '}') + { + if (*it == 's') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for bool."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(bool value, format_context & context) const -> void + { + auto const text = value ? std::string_view{"true"} : std::string_view{"false"}; + auto final_width = 0uz; + + if (specifiers.width_mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.width_mode == bits::format::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::format::calculate_format_padding(final_width, text.size(), specifiers.align, + bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(text); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp new file mode 100644 index 0000000..9afb974 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/cstring.hpp @@ -0,0 +1,29 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP + +#include "../context.hpp" +#include "../formatter.hpp" +#include "string_view.hpp" + +#include <string_view> + +namespace kstd +{ + + 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<> + struct formatter<char *> : formatter<char const *> + { + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp new file mode 100644 index 0000000..b0caed1 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -0,0 +1,204 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP +#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" + +#include <array> +#include <concepts> +#include <cstddef> +#include <iterator> +#include <string_view> +#include <type_traits> +#include <utility> + +namespace kstd +{ + + template<std::integral T> + struct formatter<T> + { + bits::format::format_specifiers specifiers{}; + + constexpr auto static maximum_digits = 80; + + enum struct base + { + bin = 2, + oct = 8, + dec = 10, + hex = 16, + }; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::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 + { + bits::format::error("Invalid type specifier for integral type."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(T value, format_context & context) const -> void + { + auto final_width = 0uz; + if (specifiers.width_mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.width_mode == bits::format::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); + auto is_negative = false; + + if constexpr (std::is_signed_v<T>) + { + if (value < 0) + { + is_negative = true; + absolute_value = 0 - static_cast<unsigned_T>(value); + } + } + + auto const base = [type = specifiers.type] -> auto { + switch (type) + { + case 'x': + case 'X': + case 'p': + return base::hex; + case 'b': + case 'B': + return base::bin; + case 'o': + return base::oct; + default: + return base::dec; + } + }(); + + auto buffer = std::array<char, maximum_digits>{}; + auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; + auto current = buffer.rbegin(); + + if (absolute_value == 0) + { + *current = '0'; + std::advance(current, 1); + } + else + { + while (absolute_value != 0) + { + *current = digits[absolute_value % std::to_underlying(base)]; + std::advance(current, 1); + absolute_value /= std::to_underlying(base); + } + } + + 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 (specifiers.alternative_form) + { + switch (base) + { + case base::bin: + prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; + prefix_length = 2; + break; + case base::oct: + prefix_length = 1; + break; + case base::hex: + prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; + prefix_length = 2; + break; + default: + break; + } + } + + auto sign_character = '\0'; + if (is_negative) + { + sign_character = '-'; + } + else if (specifiers.sign == bits::format::sign_mode::plus) + { + sign_character = '+'; + } + else if (specifiers.sign == bits::format::sign_mode::space) + { + sign_character = ' '; + } + + auto const total_length = content_length + prefix_length + (sign_character != '\0'); + auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align, + bits::format::alignment::right); + auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none); + + if (!effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + } + + if (sign_character != '\0') + { + context.push(sign_character); + } + if (prefix_length > 0) + { + context.push(std::string_view{prefix.data(), prefix_length}); + } + + if (effective_zero_pad) + { + for (auto i = 0uz; i < padding.left; ++i) + { + context.push('0'); + } + } + + context.push(std::string_view{current.base(), content_length}); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp new file mode 100644 index 0000000..78e7f7b --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/ordering.hpp @@ -0,0 +1,111 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP +#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP + +#include "../context.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" + +#include <compare> + +namespace kstd +{ + + template<> + struct formatter<std::strong_ordering> + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::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(specifiers.alternative_form ? "==" : "equal"); + } + else if (value == std::strong_ordering::equivalent) + { + return context.push(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::strong_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::strong_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid strong ordering value!"); + } + }; + + template<> + struct formatter<std::weak_ordering> + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::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(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::weak_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::weak_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + kstd::os::panic("[kstd:format] Invalid weak ordering value!"); + } + }; + + template<> + struct formatter<std::partial_ordering> + { + bits::format::format_specifiers specifiers{}; + + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + specifiers = bits::format::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(specifiers.alternative_form ? "==" : "equivalent"); + } + else if (value == std::partial_ordering::greater) + { + return context.push(specifiers.alternative_form ? ">" : "greater"); + } + else if (value == std::partial_ordering::less) + { + return context.push(specifiers.alternative_form ? "<" : "less"); + } + else if (value == std::partial_ordering::unordered) + { + return context.push(specifiers.alternative_form ? "<=>" : "unordered"); + } + kstd::os::panic("[kstd:format] Invalid partial ordering value!"); + } + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp new file mode 100644 index 0000000..fe75a2f --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/pointer.hpp @@ -0,0 +1,42 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP +#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP + +#include "../context.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" +#include "integral.hpp" + +#include <bit> +#include <cstdint> + +namespace kstd +{ + + template<typename T> + struct formatter<T const *> : formatter<std::uintptr_t> + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto result = formatter<std::uintptr_t>::parse(context); + if (!this->specifiers.type) + { + this->specifiers.type = 'p'; + this->specifiers.alternative_form = true; + } + return result; + } + + auto format(T const * pointer, format_context & context) const -> void + { + formatter<std::uintptr_t>::format(std::bit_cast<std::uintptr_t>(pointer), context); + } + }; + + template<typename T> + struct formatter<T *> : formatter<T const *> + { + }; + +} // namespace kstd +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp new file mode 100644 index 0000000..f5b698e --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/string_view.hpp @@ -0,0 +1,41 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP +#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" + +#include <string_view> + +namespace kstd +{ + + 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 != '}') + { + bits::format::error("Invalid specifier for string_view."); + } + return it; + } + + auto format(std::string_view const & string, format_context & context) const -> void + { + context.push(string); + } + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format/fwd.hpp b/libs/kstd/include/kstd/bits/format/fwd.hpp new file mode 100644 index 0000000..6caedae --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/fwd.hpp @@ -0,0 +1,23 @@ +#ifndef KSTD_BITS_FORMAT_FWD_HPP +#define KSTD_BITS_FORMAT_FWD_HPP + +// IWYU pragma: private + +#include <cstddef> + +namespace kstd +{ + + struct format_parse_context; + struct format_context; + struct format_arg; + + template<typename> + struct formatter; + + template<std::size_t> + struct format_arg_store; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/format_parse_context.hpp b/libs/kstd/include/kstd/bits/format/parse_context.hpp index 76c3f03..063263b 100644 --- a/libs/kstd/include/kstd/bits/format_parse_context.hpp +++ b/libs/kstd/include/kstd/bits/format/parse_context.hpp @@ -3,7 +3,7 @@ // IWYU pragma: private, include <kstd/format> -#include <kstd/os/error.hpp> +#include "error.hpp" #include <cstddef> #include <string_view> @@ -11,19 +11,6 @@ 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; @@ -55,14 +42,14 @@ namespace kstd { if (m_mode == index_mode::manual) { - report_format_error("Cannot mix automatic and manual indexing."); + bits::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."); + bits::format::error("Argument index out of bounds."); } return m_next_argument_id++; } @@ -71,14 +58,14 @@ namespace kstd { if (m_mode == index_mode::automatic) { - report_format_error("Cannot mix automatic and manual indexing."); + bits::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."); + bits::format::error("Argument index out of bounds."); } } @@ -87,7 +74,7 @@ namespace kstd check_arg_id(id); if (m_is_integral && !m_is_integral[id]) { - report_format_error("Dynamic width argument must be an integral object."); + bits::format::error("Dynamic width argument must be an integral object."); } } @@ -96,7 +83,7 @@ namespace kstd 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."); + bits::format::error("Dynamic width argument must be an integral object."); } return id; } diff --git a/libs/kstd/include/kstd/bits/format_specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp index 00cca40..85581e6 100644 --- a/libs/kstd/include/kstd/bits/format_specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp @@ -3,14 +3,14 @@ // IWYU pragma: private -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" +#include "error.hpp" +#include "parse_context.hpp" #include <cstddef> #include <cstdint> #include <iterator> -namespace kstd::bits +namespace kstd::bits::format { enum struct alignment : std::uint8_t { @@ -153,7 +153,7 @@ namespace kstd::bits if (it == end || *it != '}') { - report_format_error("Expected '}' for dynamic width."); + error("Expected '}' for dynamic width."); } std::advance(it, 1); specifiers.width_value = argument_id; @@ -200,6 +200,6 @@ namespace kstd::bits } } -} // namespace kstd::bits +} // namespace kstd::bits::format #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 f16f1ee..2e7d60a 100644 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ b/libs/kstd/include/kstd/bits/format/string.hpp @@ -3,8 +3,9 @@ // IWYU pragma: private, include <kstd/format> -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" +#include "context.hpp" +#include "error.hpp" +#include "parse_context.hpp" #include <array> #include <cstddef> @@ -14,10 +15,7 @@ namespace kstd { - template<typename> - struct formatter; - - namespace bits + namespace bits::format { template<typename... Args> constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void @@ -40,7 +38,7 @@ namespace kstd if (!found) { - report_format_error("Argument index out of bounds."); + error("Argument index out of bounds."); } } @@ -64,10 +62,10 @@ namespace kstd if (!is_valid_integer) { - report_format_error("Dynamic width argument must be an integral object."); + error("Dynamic width argument must be an integral object."); } } - } // namespace bits + } // namespace bits::format template<typename... Args> struct format_string @@ -76,8 +74,10 @@ namespace kstd 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>>...}; + using namespace bits::format; + + auto const is_width_compatible = + std::array<bool, (sizeof...(Args) > 0 ? sizeof...(Args) : 1)>{is_width_v<std::remove_cvref_t<Args>>...}; auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; auto it = context.begin(); @@ -117,12 +117,12 @@ namespace kstd context.advance_to(it); } - bits::validate_argument<Args...>(argument_id, context); + validate_argument<Args...>(argument_id, context); it = context.begin(); if (it == context.end() || *it != '}') { - report_format_error("Missing closing '}' in format string."); + bits::format::error("Missing closing '}' in format string."); } } else if (*it == '}') @@ -130,7 +130,7 @@ namespace kstd ++it; if (it != context.end() && *it == '}') { - report_format_error("Unescaped '}' in format string."); + bits::format::error("Unescaped '}' in format string."); } } ++it; diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp deleted file mode 100644 index 71ee5ae..0000000 --- a/libs/kstd/include/kstd/bits/format_args.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#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 deleted file mode 100644 index 05e37ca..0000000 --- a/libs/kstd/include/kstd/bits/format_context.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP -#define KSTD_BITS_FORMAT_CONTEXT_HPP - -// 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 - { - writer(user_data, string); - } - - constexpr auto push(char character) -> void - { - writer(user_data, std::string_view(&character, 1)); - } - }; - -} // namespace kstd - -#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp deleted file mode 100644 index e46f6ad..0000000 --- a/libs/kstd/include/kstd/bits/formatter.hpp +++ /dev/null @@ -1,435 +0,0 @@ -#ifndef KSTD_BITS_FORMATTER_HPP -#define KSTD_BITS_FORMATTER_HPP - -// 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> - -#include <array> -#include <bit> -#include <compare> -#include <concepts> -#include <cstddef> -#include <cstdint> -#include <iterator> -#include <string_view> -#include <type_traits> -#include <utility> - -namespace kstd -{ - - 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_specifiers specifiers{}; - - constexpr auto static maximum_digits = 80; - - enum struct base - { - 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 - { - 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); - } - - using unsigned_T = std::make_unsigned_t<T>; - auto absolute_value = static_cast<unsigned_T>(value); - auto is_negative = false; - - if constexpr (std::is_signed_v<T>) - { - if (value < 0) - { - is_negative = true; - absolute_value = 0 - static_cast<unsigned_T>(value); - } - } - - auto const base = [type = specifiers.type] -> auto { - switch (type) - { - case 'x': - case 'X': - case 'p': - return base::hex; - case 'b': - case 'B': - return base::bin; - case 'o': - return base::oct; - default: - return base::dec; - } - }(); - - auto buffer = std::array<char, maximum_digits>{}; - auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef"; - auto current = buffer.rbegin(); - - if (absolute_value == 0) - { - *current = '0'; - std::advance(current, 1); - } - else - { - while (absolute_value != 0) - { - *current = digits[absolute_value % std::to_underlying(base)]; - std::advance(current, 1); - absolute_value /= std::to_underlying(base); - } - } - - 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 (specifiers.alternative_form) - { - switch (base) - { - case base::bin: - prefix[1] = specifiers.type == 'B' ? 'B' : 'b'; - prefix_length = 2; - break; - case base::oct: - prefix_length = 1; - break; - case base::hex: - prefix[1] = specifiers.type == 'X' ? 'X' : 'x'; - prefix_length = 2; - break; - default: - break; - } - } - - auto sign_character = '\0'; - if (is_negative) - { - sign_character = '-'; - } - else if (specifiers.sign == bits::sign_mode::plus) - { - sign_character = '+'; - } - else if (specifiers.sign == bits::sign_mode::space) - { - sign_character = ' '; - } - - auto const total_length = content_length + prefix_length + (sign_character != '\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 (!effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - } - - if (sign_character != '\0') - { - context.push(sign_character); - } - if (prefix_length > 0) - { - context.push(std::string_view{prefix.data(), prefix_length}); - } - - if (effective_zero_pad) - { - for (auto i = 0uz; i < padding.left; ++i) - { - context.push('0'); - } - } - - context.push(std::string_view{current.base(), content_length}); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - - template<typename T> - struct formatter<T const *> : formatter<std::uintptr_t> - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto result = formatter<std::uintptr_t>::parse(context); - if (!this->specifiers.type) - { - this->specifiers.type = 'p'; - this->specifiers.alternative_form = true; - } - return result; - } - - auto format(T const * pointer, format_context & context) const -> void - { - formatter<std::uintptr_t>::format(std::bit_cast<std::uintptr_t>(pointer), context); - } - }; - - template<typename T> - struct formatter<T *> : formatter<T const *> - { - }; - - template<> - struct formatter<char *> : formatter<char const *> - { - }; - - template<> - struct formatter<bool> - { - bits::format_specifiers specifiers{}; - - 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 == 's') - { - specifiers.type = *it; - std::advance(it, 1); - } - else - { - report_format_error("Invalid type specifier for bool."); - } - } - - if (it != end && *it != '}') - { - report_format_error("Missing terminating '}' in format string."); - } - - return it; - } - - auto format(bool value, format_context & context) const -> void - { - 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); - - for (auto i = 0uz; i < padding.left; ++i) - { - context.push(specifiers.fill); - } - - context.push(text); - - for (auto i = 0uz; i < padding.right; ++i) - { - context.push(specifiers.fill); - } - } - }; - - template<> - struct formatter<std::strong_ordering> - { - bits::format_specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - 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(specifiers.alternative_form ? "==" : "equal"); - } - else if (value == std::strong_ordering::equivalent) - { - return context.push(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::strong_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::strong_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid strong ordering value!"); - } - }; - - template<> - struct formatter<std::weak_ordering> - { - bits::format_specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - 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(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::weak_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::weak_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - kstd::os::panic("[kstd:format] Invalid weak ordering value!"); - } - }; - - template<> - struct formatter<std::partial_ordering> - { - bits::format_specifiers specifiers{}; - - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - 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(specifiers.alternative_form ? "==" : "equivalent"); - } - else if (value == std::partial_ordering::greater) - { - return context.push(specifiers.alternative_form ? ">" : "greater"); - } - else if (value == std::partial_ordering::less) - { - return context.push(specifiers.alternative_form ? "<" : "less"); - } - else if (value == std::partial_ordering::unordered) - { - return context.push(specifiers.alternative_form ? "<=>" : "unordered"); - } - kstd::os::panic("[kstd:format] Invalid partial ordering value!"); - } - }; - -} // namespace kstd - -#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index 0a1bcd1..76aaed8 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -1,8 +1,17 @@ #ifndef KSTD_FORMAT_HPP #define KSTD_FORMAT_HPP -#include "bits/format_context.hpp" // IWYU pragma: export -#include "bits/format_string.hpp" // IWYU pragma: export -#include "bits/formatter.hpp" // IWYU pragma: export +#include "bits/format/arg.hpp" // IWYU pragma: export +#include "bits/format/args.hpp" // IWYU pragma: export +#include "bits/format/context.hpp" // IWYU pragma: export +#include "bits/format/formatter.hpp" // IWYU pragma: export +#include "bits/format/formatter/bool.hpp" // IWYU pragma: export +#include "bits/format/formatter/cstring.hpp" // IWYU pragma: export +#include "bits/format/formatter/integral.hpp" // IWYU pragma: export +#include "bits/format/formatter/ordering.hpp" // IWYU pragma: export +#include "bits/format/formatter/pointer.hpp" // IWYU pragma: export +#include "bits/format/formatter/string_view.hpp" // IWYU pragma: export +#include "bits/format/parse_context.hpp" // IWYU pragma: export +#include "bits/format/string.hpp" // IWYU pragma: export #endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/include/kstd/os/print.hpp index 0dde6e7..b8e0732 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/format_args.hpp" +#include "kstd/bits/format/args.hpp" #include "kstd/bits/print_sink.hpp" #include <string_view> diff --git a/libs/kstd/include/kstd/print b/libs/kstd/include/kstd/print index 1ab24bd..f91cb04 100644 --- a/libs/kstd/include/kstd/print +++ b/libs/kstd/include/kstd/print @@ -1,7 +1,6 @@ #ifndef KSTD_PRINT #define KSTD_PRINT -#include "bits/format_args.hpp" #include "bits/print_sink.hpp" // IWYU pragma: export #include "os/print.hpp" |
