From 07cb15c42c16497b0b09b75886ce3baddeaaafb3 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 20 Mar 2026 14:03:45 +0100 Subject: kstd/format: split implementation --- libs/kstd/include/kstd/bits/format/arg.hpp | 26 ++ libs/kstd/include/kstd/bits/format/args.hpp | 75 ++++ libs/kstd/include/kstd/bits/format/context.hpp | 61 +++ libs/kstd/include/kstd/bits/format/error.hpp | 24 ++ libs/kstd/include/kstd/bits/format/formatter.hpp | 14 + .../include/kstd/bits/format/formatter/bool.hpp | 83 ++++ .../include/kstd/bits/format/formatter/cstring.hpp | 29 ++ .../kstd/bits/format/formatter/integral.hpp | 204 ++++++++++ .../kstd/bits/format/formatter/ordering.hpp | 111 ++++++ .../include/kstd/bits/format/formatter/pointer.hpp | 42 ++ .../kstd/bits/format/formatter/string_view.hpp | 41 ++ libs/kstd/include/kstd/bits/format/fwd.hpp | 23 ++ .../include/kstd/bits/format/parse_context.hpp | 109 ++++++ libs/kstd/include/kstd/bits/format/specifiers.hpp | 205 ++++++++++ libs/kstd/include/kstd/bits/format/string.hpp | 146 +++++++ libs/kstd/include/kstd/bits/format_args.hpp | 68 ---- libs/kstd/include/kstd/bits/format_context.hpp | 74 ---- .../include/kstd/bits/format_parse_context.hpp | 122 ------ libs/kstd/include/kstd/bits/format_specifiers.hpp | 205 ---------- libs/kstd/include/kstd/bits/format_string.hpp | 146 ------- libs/kstd/include/kstd/bits/formatter.hpp | 435 --------------------- libs/kstd/include/kstd/format | 15 +- libs/kstd/include/kstd/os/print.hpp | 2 +- libs/kstd/include/kstd/print | 1 - 24 files changed, 1206 insertions(+), 1055 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format/arg.hpp create mode 100644 libs/kstd/include/kstd/bits/format/args.hpp create mode 100644 libs/kstd/include/kstd/bits/format/context.hpp create mode 100644 libs/kstd/include/kstd/bits/format/error.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/bool.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/cstring.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/integral.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/ordering.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/pointer.hpp create mode 100644 libs/kstd/include/kstd/bits/format/formatter/string_view.hpp create mode 100644 libs/kstd/include/kstd/bits/format/fwd.hpp create mode 100644 libs/kstd/include/kstd/bits/format/parse_context.hpp create mode 100644 libs/kstd/include/kstd/bits/format/specifiers.hpp create mode 100644 libs/kstd/include/kstd/bits/format/string.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_args.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_parse_context.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_specifiers.hpp delete mode 100644 libs/kstd/include/kstd/bits/format_string.hpp delete mode 100644 libs/kstd/include/kstd/bits/formatter.hpp (limited to 'libs') 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 + +#include "fwd.hpp" + +#include + +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 + +#include "context.hpp" +#include "error.hpp" +#include "fwd.hpp" +#include "parse_context.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + + 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 + auto get_size_trampoline(void const * value_pointer) -> std::size_t + { + if constexpr (is_width_v) + { + return static_cast(*static_cast(value_pointer)); + } + else + { + error("Dynamic width argument is not an integral value."); + return 0; + } + } + + } // namespace bits::format + + using format_args = std::span; + + template + struct format_arg_store + { + std::array args{}; + }; + + template + [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store + { + using namespace bits::format; + + if constexpr (sizeof...(Arguments) == 0) + { + return format_arg_store<0>{}; + } + else + { + return format_arg_store{std::array{ + format_arg{static_cast(&args), format_trampoline, get_size_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 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 + +#include "arg.hpp" + +#include + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto inline is_width_v = std::integral && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as && // + !std::same_as; + } + + struct format_context + { + using writer_function = void(void *, std::string_view); + using format_args = std::span; + + 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 + +namespace kstd +{ + + template + 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 +#include + +namespace kstd +{ + + template<> + struct formatter + { + 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 + +namespace kstd +{ + + 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 : formatter + { + }; + +} // 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 +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + struct formatter + { + 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; + auto absolute_value = static_cast(value); + auto is_negative = false; + + if constexpr (std::is_signed_v) + { + if (value < 0) + { + is_negative = true; + absolute_value = 0 - static_cast(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{}; + 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::distance(buffer.rbegin(), current)); + auto prefix = std::array{'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 + +namespace kstd +{ + + template<> + struct formatter + { + 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 + { + 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 + { + 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 +#include + +namespace kstd +{ + + template + struct formatter : formatter + { + constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator + { + auto result = formatter::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::format(std::bit_cast(pointer), context); + } + }; + + template + struct formatter : formatter + { + }; + +} // 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 + +namespace kstd +{ + + 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 != '}') + { + 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 + +namespace kstd +{ + + struct format_parse_context; + struct format_context; + struct format_arg; + + template + struct formatter; + + template + 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 new file mode 100644 index 0000000..063263b --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/parse_context.hpp @@ -0,0 +1,109 @@ +#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP +#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP + +// IWYU pragma: private, include + +#include "error.hpp" + +#include +#include + +namespace kstd +{ + + 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) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::automatic; + + if (m_next_argument_id >= m_argument_count) + { + bits::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) + { + bits::format::error("Cannot mix automatic and manual indexing."); + } + + m_mode = index_mode::manual; + + if (index >= m_argument_count) + { + bits::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]) + { + bits::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]) + { + bits::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..85581e6 --- /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 "error.hpp" +#include "parse_context.hpp" + +#include +#include +#include + +namespace kstd::bits::format +{ + 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_dynamic_width_id(argument_id); + } + else + { + argument_id = context.next_dynamic_width_id(); + } + + if (it == end || *it != '}') + { + 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::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 new file mode 100644 index 0000000..2e7d60a --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/string.hpp @@ -0,0 +1,146 @@ +#ifndef KSTD_BITS_FORMAT_STRING_HPP +#define KSTD_BITS_FORMAT_STRING_HPP + +// IWYU pragma: private, include + +#include "context.hpp" +#include "error.hpp" +#include "parse_context.hpp" + +#include +#include +#include +#include + +namespace kstd +{ + + namespace bits::format + { + template + constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void + { + 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) + { + error("Argument index out of bounds."); + } + } + + template + 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; + if constexpr (std::is_integral_v) + { + is_valid_integer = true; + } + } + ++current_index; + }()); + + if (!is_valid_integer) + { + error("Dynamic width argument must be an integral object."); + } + } + } // namespace bits::format + + template + struct format_string + { + template + consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT + : str_view{str} + { + using namespace bits::format; + + auto const is_width_compatible = + std::array 0 ? sizeof...(Args) : 1)>{is_width_v>...}; + auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; + auto it = context.begin(); + + while (it != context.end()) + { + if (*it == '{') + { + ++it; + if (it != context.end() && *it == '{') + { + ++it; + context.advance_to(it); + continue; + } + + context.advance_to(it); + auto argument_id = 0uz; + + if (it != context.end() && *it >= '0' && *it <= '9') + { + while (it != context.end() && *it >= '0' && *it <= '9') + { + argument_id = argument_id * 10 + static_cast(*it - '0'); + ++it; + } + context.check_arg_id(argument_id); + context.advance_to(it); + } + else + { + argument_id = context.next_arg_id(); + } + + if (it != context.end() && *it == ':') + { + ++it; + context.advance_to(it); + } + + validate_argument(argument_id, context); + + it = context.begin(); + if (it == context.end() || *it != '}') + { + bits::format::error("Missing closing '}' in format string."); + } + } + else if (*it == '}') + { + ++it; + if (it != context.end() && *it == '}') + { + bits::format::error("Unescaped '}' in format string."); + } + } + ++it; + context.advance_to(it); + } + } + + std::string_view str_view; + }; + +} // 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 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 - -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" - -#include -#include -#include - -namespace kstd -{ - struct format_context; - struct format_parse_context; - - template - struct formatter; - - 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 - auto get_size_trampoline(void const * value_pointer) -> std::size_t - { - if constexpr (bits::is_format_width_compatible_v) - { - return static_cast(*static_cast(value_pointer)); - } - else - { - report_format_error("Dynamic width argument is not an integral value."); - return 0; - } - } - - 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, get_size_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 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 - -#include - -#include -#include -#include -#include - -namespace kstd -{ - - namespace bits - { - template - constexpr auto inline is_format_width_compatible_v = std::integral && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as && // - !std::same_as; - } - - 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; - - 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/format_parse_context.hpp b/libs/kstd/include/kstd/bits/format_parse_context.hpp deleted file mode 100644 index 76c3f03..0000000 --- a/libs/kstd/include/kstd/bits/format_parse_context.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP -#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP - -// IWYU pragma: private, include - -#include - -#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, - 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 deleted file mode 100644 index 00cca40..0000000 --- a/libs/kstd/include/kstd/bits/format_specifiers.hpp +++ /dev/null @@ -1,205 +0,0 @@ -#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 -#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_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(*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_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp deleted file mode 100644 index f16f1ee..0000000 --- a/libs/kstd/include/kstd/bits/format_string.hpp +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef KSTD_BITS_FORMAT_STRING_HPP -#define KSTD_BITS_FORMAT_STRING_HPP - -// IWYU pragma: private, include - -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" - -#include -#include -#include -#include - -namespace kstd -{ - - template - struct formatter; - - namespace bits - { - template - constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void - { - 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."); - } - } - - template - 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; - if constexpr (std::is_integral_v) - { - is_valid_integer = true; - } - } - ++current_index; - }()); - - if (!is_valid_integer) - { - report_format_error("Dynamic width argument must be an integral object."); - } - } - } // namespace bits - - template - struct format_string - { - template - consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT - : str_view{str} - { - auto const is_width_compatible = std::array 0 ? sizeof...(Args) : 1)>{ - bits::is_format_width_compatible_v>...}; - auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()}; - auto it = context.begin(); - - while (it != context.end()) - { - if (*it == '{') - { - ++it; - if (it != context.end() && *it == '{') - { - ++it; - context.advance_to(it); - continue; - } - - context.advance_to(it); - auto argument_id = 0uz; - - if (it != context.end() && *it >= '0' && *it <= '9') - { - while (it != context.end() && *it >= '0' && *it <= '9') - { - argument_id = argument_id * 10 + static_cast(*it - '0'); - ++it; - } - context.check_arg_id(argument_id); - context.advance_to(it); - } - else - { - argument_id = context.next_arg_id(); - } - - if (it != context.end() && *it == ':') - { - ++it; - context.advance_to(it); - } - - bits::validate_argument(argument_id, context); - - it = context.begin(); - if (it == context.end() || *it != '}') - { - report_format_error("Missing closing '}' in format string."); - } - } - else if (*it == '}') - { - ++it; - if (it != context.end() && *it == '}') - { - report_format_error("Unescaped '}' in format string."); - } - } - ++it; - context.advance_to(it); - } - } - - std::string_view str_view; - }; - -} // 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 - -#include "kstd/bits/format_context.hpp" -#include "kstd/bits/format_parse_context.hpp" -#include "kstd/bits/format_specifiers.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace kstd -{ - - 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_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; - auto absolute_value = static_cast(value); - auto is_negative = false; - - if constexpr (std::is_signed_v) - { - if (value < 0) - { - is_negative = true; - absolute_value = 0 - static_cast(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{}; - 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::distance(buffer.rbegin(), current)); - auto prefix = std::array{'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 - struct formatter : formatter - { - constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator - { - auto result = formatter::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::format(std::bit_cast(pointer), context); - } - }; - - template - struct formatter : formatter - { - }; - - template<> - struct formatter : formatter - { - }; - - template<> - struct formatter - { - 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 - { - 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 - { - 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_for