From e38fd2056a8cab7a3c6f3da8150fed69530bde6c Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 14:14:08 +0200 Subject: kstd: introduce output buffer for formatting --- libs/kstd/include/kstd/bits/format/context.hpp | 12 ++++----- .../include/kstd/bits/format/output_buffer.hpp | 30 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format/output_buffer.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index 478a48f..e8d0302 100644 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -4,6 +4,7 @@ // IWYU pragma: private, include #include "arg.hpp" +#include "kstd/bits/format/output_buffer.hpp" #include @@ -29,11 +30,7 @@ namespace kstd 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 & @@ -47,13 +44,16 @@ namespace kstd constexpr auto push(std::string_view string) -> void { - writer(user_data, string); + m_buffer->push(string); } constexpr auto push(char character) -> void { - writer(user_data, std::string_view(&character, 1)); + m_buffer->push(character); } + + private: + bits::format::output_buffer * m_buffer; }; } // namespace kstd diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp new file mode 100644 index 0000000..be2034f --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/output_buffer.hpp @@ -0,0 +1,30 @@ +#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP + +#include + +namespace kstd::bits::format +{ + + //! An abstract interface for formatted output buffers. + //! + //! This interface is intended to be use for functions dealing with string formatting, like the print and the format + //! family. + struct output_buffer + { + virtual ~output_buffer() = default; + + //! Push a text segment into the buffer. + //! + //! @param text The text segment to push. + virtual auto push(std::string_view text) -> void = 0; + + //! Push a single character into the buffer. + //! + //! @param character The character to push into the buffer. + virtual auto push(char character) -> void = 0; + }; + +} // namespace kstd::bits::format + +#endif \ No newline at end of file -- cgit v1.2.3 From 98fc294deb6f7c27eda3f9ed3b616572a1ab2009 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 14:37:20 +0200 Subject: kstd/format: hook up vformat_to --- .../include/kstd/bits/format/output_buffer.hpp | 2 + libs/kstd/include/kstd/bits/format/vformat.hpp | 65 ++++++++++++++++++++++ libs/kstd/include/kstd/format | 2 + 3 files changed, 69 insertions(+) create mode 100644 libs/kstd/include/kstd/bits/format/vformat.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp index be2034f..fd7a2b4 100644 --- a/libs/kstd/include/kstd/bits/format/output_buffer.hpp +++ b/libs/kstd/include/kstd/bits/format/output_buffer.hpp @@ -1,6 +1,8 @@ #ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP #define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +// IWYU pragma: private, include + #include namespace kstd::bits::format diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp new file mode 100644 index 0000000..90b74a9 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -0,0 +1,65 @@ +#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP +#define KSTD_BITS_FORMAT_VFORMAT_HPP + +// IWYU pragma: private, include + +#include +#include +#include +#include + +#include +#include + +namespace kstd +{ + + namespace bits::format + { + //! Format a string with the given arguments into the given buffer. + //! + //! External implementations of `vprint` may call this function. + //! + //! @param buffer The buffer to format into. + //! @param format The format string to use. + //! @param args The arguments for the format string. + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void; + + struct string_writer : output_buffer + { + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + auto release() -> string &&; + + private: + string m_result{}; + }; + + struct string_iterator_writer : output_buffer + { + auto push(std::string_view text) -> void override; + auto push(char character) -> void override; + + private: + string::iterator m_iter{}; + }; + + } // namespace bits::format + + //! Format a given string with the provided arguments. + //! + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return A new string containing the result of the format operation. + template + auto format(format_string...> format, ArgumentTypes &&... args) -> string + { + auto buffer = bits::format::string_writer{}; + bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + return buffer.release(); + } + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index adee5f8..d11c221 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -13,7 +13,9 @@ #include "bits/format/formatter/pointer.hpp" // IWYU pragma: export #include "bits/format/formatter/range.hpp" // IWYU pragma: export #include "bits/format/formatter/string_view.hpp" // IWYU pragma: export +#include "bits/format/output_buffer.hpp" // IWYU pragma: export #include "bits/format/parse_context.hpp" // IWYU pragma: export #include "bits/format/string.hpp" // IWYU pragma: export +#include "bits/format/vformat.hpp" // IWYU pragma: export #endif \ No newline at end of file -- cgit v1.2.3 From 45091789eb9e49856ea6c2f3606639d43f4584e7 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 14:56:39 +0200 Subject: kstd: move formatting implementation to kstd --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/include/kstd/bits/format/context.hpp | 5 + .../include/kstd/bits/format/formatter/bool.hpp | 4 +- .../kstd/bits/format/formatter/integral.hpp | 4 +- libs/kstd/include/kstd/bits/format/specifiers.hpp | 6 +- libs/kstd/include/kstd/bits/format/vformat.hpp | 2 + libs/kstd/include/kstd/vector | 2 +- libs/kstd/src/vformat.cpp | 224 +++++++++++++++++++++ 8 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 libs/kstd/src/vformat.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 240118e..f7c771b 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources("kstd" PRIVATE "src/os/error.cpp" "src/mutex.cpp" + "src/vformat.cpp" ) file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/kstd/*") diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index e8d0302..7f392a0 100644 --- a/libs/kstd/include/kstd/bits/format/context.hpp +++ b/libs/kstd/include/kstd/bits/format/context.hpp @@ -33,6 +33,11 @@ namespace kstd using format_args = std::span; format_args args{}; + format_context(bits::format::output_buffer & buffer, format_args args) + : args{args} + , m_buffer{&buffer} + {} + [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const & { if (id >= args.size()) diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp index 336e1b0..e371cec 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp @@ -51,11 +51,11 @@ namespace kstd 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) + if (specifiers.mode == bits::format::width_mode::static_value) { final_width = specifiers.width_value; } - else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); final_width = bits::format::extrat_dynamic_width(arg); diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp index 4912a44..e5a234a 100644 --- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp +++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp @@ -64,11 +64,11 @@ namespace kstd auto format(T value, format_context & context) const -> void { auto final_width = 0uz; - if (specifiers.width_mode == bits::format::width_mode::static_value) + if (specifiers.mode == bits::format::width_mode::static_value) { final_width = specifiers.width_value; } - else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id) + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) { auto const & arg = context.arg(specifiers.width_value); final_width = bits::format::extrat_dynamic_width(arg); diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp index 9bc66c7..18c6f66 100644 --- a/libs/kstd/include/kstd/bits/format/specifiers.hpp +++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp @@ -43,7 +43,7 @@ namespace kstd::bits::format bool alternative_form{}; bool zero_pad{}; - width_mode width_mode{}; + width_mode mode{}; std::size_t width_value{}; char type{}; }; @@ -133,7 +133,7 @@ namespace kstd::bits::format if (it != end && *it == '{') { - specs.width_mode = width_mode::dynamic_argument_id; + specs.mode = width_mode::dynamic_argument_id; std::advance(it, 1); auto argument_id = 0uz; @@ -160,7 +160,7 @@ namespace kstd::bits::format } else if (it != end && *it >= '0' && *it <= '9') { - specs.width_mode = width_mode::static_value; + specs.mode = width_mode::static_value; while (it != end && *it >= '0' && *it <= '9') { specs.width_value = specs.width_value * 10 + static_cast(*it - '0'); diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp index 90b74a9..d032c3a 100644 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -38,6 +38,8 @@ namespace kstd struct string_iterator_writer : output_buffer { + explicit string_iterator_writer(string::iterator iterator); + auto push(std::string_view text) -> void override; auto push(char character) -> void override; diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index e51cbac..79593c6 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -111,7 +111,7 @@ namespace kstd allocator_type const & allocator = allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v) : m_allocator{allocator} - , m_size{std::ranges::distance(first, last)} + , m_size{static_cast(std::ranges::distance(first, last))} , m_capacity{m_size} , m_data{allocate_n(m_capacity)} { diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp new file mode 100644 index 0000000..51aca84 --- /dev/null +++ b/libs/kstd/src/vformat.cpp @@ -0,0 +1,224 @@ +#include +#include + +#include +#include +#include +#include +#include + +namespace kstd::bits::format +{ + + auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void + { + auto context = kstd::format_context{buffer, args}; + auto parse_context = kstd::format_parse_context{format, args.size()}; + + auto it = parse_context.begin(); + auto end = parse_context.end(); + + while (it != end) + { + if (*it != '{' && *it != '}') + { + auto start = it; + while (it != end && *it != '{' && *it != '}') + { + std::advance(it, 1); + } + parse_context.advance_to(it); + context.push(std::string_view(start, it - start)); + continue; + } + + if (*it == '{') + { + std::advance(it, 1); + if (it != end && *it == '{') + { + context.push('{'); + std::advance(it, 1); + parse_context.advance_to(it); + continue; + } + + parse_context.advance_to(it); + auto index = 0uz; + + if (it != end && *it >= '0' && *it <= '9') + { + while (it != end && *it >= '0' && *it <= '9') + { + index = index * 10 + static_cast(*it - '0'); + std::advance(it, 1); + } + parse_context.check_arg_id(index); + } + else + { + index = parse_context.next_arg_id(); + } + + if (it != end && *it == ':') + { + std::advance(it, 1); + } + + parse_context.advance_to(it); + + if (index < args.size()) + { + auto const & arg = args[index]; + switch (arg.type) + { + case kstd::bits::format::arg_type::boolean: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.boolean, context); + break; + } + case kstd::bits::format::arg_type::character: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.character, context); + break; + } + case kstd::bits::format::arg_type::integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.integer, context); + break; + } + case kstd::bits::format::arg_type::unsigned_integer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.unsigned_integer, context); + break; + } + case kstd::bits::format::arg_type::string_view: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.string_view, context); + break; + } + case kstd::bits::format::arg_type::c_string: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.c_string, context); + break; + } + case kstd::bits::format::arg_type::pointer: + { + auto fmt = kstd::formatter{}; + auto const parsed = fmt.parse(parse_context); + parse_context.advance_to(parsed); + fmt.format(arg.value.pointer, context); + break; + } + case kstd::bits::format::arg_type::user_defined: + { + if (arg.value.user_defined.format) + { + arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context); + } + else + { + context.push("{?}"); + } + break; + } + default: + { + context.push("{fmt-err: unknown-type}"); + break; + } + } + } + else + { + context.push("{fmt-err: bound}"); + } + + it = parse_context.begin(); + + if (it != end && *it == '}') + { + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unconsumed}"); + while (it != end && *it != '}') + { + std::advance(it, 1); + } + + if (it != end) + { + std::advance(it, 1); + parse_context.advance_to(it); + } + } + } + else if (*it == '}') + { + std::advance(it, 1); + if (it != end && *it == '}') + { + context.push('}'); + std::advance(it, 1); + parse_context.advance_to(it); + } + else + { + context.push("{fmt-err: unescaped}"); + parse_context.advance_to(it); + } + } + } + } + + auto string_writer::push(std::string_view text) -> void + { + m_result.append(text); + } + + auto string_writer::push(char character) -> void + { + m_result.push_back(character); + } + + auto string_writer::release() -> string && + { + return std::move(m_result); + } + + string_iterator_writer::string_iterator_writer(string::iterator it) + : m_iter{it} + {} + + auto string_iterator_writer::push(std::string_view text) -> void + { + std::ranges::for_each(text, [this](auto c) { push(c); }); + } + + auto string_iterator_writer::push(char character) -> void + { + *m_iter++ = character; + } + +} // namespace kstd::bits::format \ No newline at end of file -- cgit v1.2.3 From a0720bbe3cdfe3174e3d064356b21f0fcd37832e Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 15:05:38 +0200 Subject: kstd/format: add kstd::format_to --- libs/kstd/include/kstd/bits/format/vformat.hpp | 42 +++++++++++++++++++++++--- libs/kstd/src/vformat.cpp | 15 --------- 2 files changed, 37 insertions(+), 20 deletions(-) (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp index d032c3a..69c7f33 100644 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include @@ -36,15 +38,30 @@ namespace kstd string m_result{}; }; - struct string_iterator_writer : output_buffer + template Output> + struct iterator_writer : output_buffer { - explicit string_iterator_writer(string::iterator iterator); + explicit iterator_writer(Output iterator) + : m_output{iterator} + {} - auto push(std::string_view text) -> void override; - auto push(char character) -> void override; + auto iterator() const -> Output + { + return m_output; + } + + auto push(std::string_view text) -> void override + { + m_output = std::ranges::copy(text, m_output); + } + + auto push(char character) -> void override + { + *m_output++ = character; + } private: - string::iterator m_iter{}; + Output m_output{}; }; } // namespace bits::format @@ -62,6 +79,21 @@ namespace kstd return buffer.release(); } + //! Format a given string with the provided arguments. + //! + //! @param iterator The iterator to write to. + //! @param format The format string. + //! @param args The arguments for the format string. + //! @return An iterator past the last element written. + template Output, typename... ArgumentTypes> + auto format_to(Output iterator, format_string...> format, + ArgumentTypes &&... args) -> Output + { + auto buffer = bits::format::iterator_writer{iterator}; + bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + return buffer.iterator(); + } + } // namespace kstd #endif \ No newline at end of file diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp index 51aca84..b7c5121 100644 --- a/libs/kstd/src/vformat.cpp +++ b/libs/kstd/src/vformat.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -207,18 +206,4 @@ namespace kstd::bits::format return std::move(m_result); } - string_iterator_writer::string_iterator_writer(string::iterator it) - : m_iter{it} - {} - - auto string_iterator_writer::push(std::string_view text) -> void - { - std::ranges::for_each(text, [this](auto c) { push(c); }); - } - - auto string_iterator_writer::push(char character) -> void - { - *m_iter++ = character; - } - } // namespace kstd::bits::format \ No newline at end of file -- cgit v1.2.3 From cd1dd2037cbe1d5f1362202d3127640406b468b8 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 15:38:08 +0200 Subject: kstd: add basic format and format_to tests --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/include/kstd/bits/format/vformat.hpp | 6 +- libs/kstd/include/kstd/string | 20 +++++ libs/kstd/tests/src/format.cpp | 116 +++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 libs/kstd/tests/src/format.cpp (limited to 'libs') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index f7c771b..ced3138 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -44,6 +44,7 @@ if(CMAKE_CROSSCOMPILING) else() add_executable("kstd_tests" "tests/src/flat_map.cpp" + "tests/src/format.cpp" "tests/src/vector.cpp" "tests/src/observer_ptr.cpp" "tests/src/os_panic.cpp" diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp index 69c7f33..4fec7dd 100644 --- a/libs/kstd/include/kstd/bits/format/vformat.hpp +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -52,7 +52,7 @@ namespace kstd auto push(std::string_view text) -> void override { - m_output = std::ranges::copy(text, m_output); + m_output = std::ranges::copy(text, m_output).out; } auto push(char character) -> void override @@ -75,7 +75,7 @@ namespace kstd auto format(format_string...> format, ArgumentTypes &&... args) -> string { auto buffer = bits::format::string_writer{}; - bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); return buffer.release(); } @@ -90,7 +90,7 @@ namespace kstd ArgumentTypes &&... args) -> Output { auto buffer = bits::format::iterator_writer{iterator}; - bits::format::vformat_to(buffer, format.str_view, std::forward(args)...); + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward(args)...).args); return buffer.iterator(); } diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string index 4ce19ce..58c4a08 100644 --- a/libs/kstd/include/kstd/string +++ b/libs/kstd/include/kstd/string @@ -347,6 +347,26 @@ namespace kstd return !(lhs == rhs); } + [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool + { + return lhs.view() == std::string_view{rhs}; + } + + [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool + { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool + { + return std::string_view{lhs} == rhs.view(); + } + + [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool + { + return !(lhs == rhs); + } + template<> struct formatter : formatter { diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp new file mode 100644 index 0000000..4915e50 --- /dev/null +++ b/libs/kstd/tests/src/format.cpp @@ -0,0 +1,116 @@ +#include +#include +#include + +#include + +#include +#include + +SCENARIO("Formatting to a new string", "[format]") +{ + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with without any arguments.") + { + auto result = kstd::format(fmt); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + auto result = kstd::format(fmt, 1, 2, 3); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::format(fmt, 1, true, -100); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::format(fmt, 2, false, -200, 4, 5, 6); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 2 false -200"); + } + } + } +} + +SCENARIO("Formatting to an output iterator", "[format]") +{ + auto buffer = std::ostringstream{}; + + GIVEN("a format string without any placeholders") + { + auto const & fmt = "This is a test"; + + WHEN("calling format with without any arguments.") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, 2, 3); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + auto const & fmt = "Here are some placeholders: {} {} {}"; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, true, -100); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 2, false, -200, 4, 5, 6); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); + } + } + } +} \ No newline at end of file -- cgit v1.2.3 From 3795115641bf5c1d1a3d60313408ba462057ba18 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 13 Apr 2026 16:18:00 +0200 Subject: kstd/format: add support for char formatting --- .../include/kstd/bits/format/formatter/char.hpp | 94 ++++++++++++++++++++++ libs/kstd/include/kstd/format | 1 + libs/kstd/tests/src/format.cpp | 8 +- 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 libs/kstd/include/kstd/bits/format/formatter/char.hpp (limited to 'libs') diff --git a/libs/kstd/include/kstd/bits/format/formatter/char.hpp b/libs/kstd/include/kstd/bits/format/formatter/char.hpp new file mode 100644 index 0000000..ddfefe5 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/formatter/char.hpp @@ -0,0 +1,94 @@ +#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP +#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP + +#include "../context.hpp" +#include "../error.hpp" +#include "../formatter.hpp" +#include "../parse_context.hpp" +#include "../specifiers.hpp" +#include "integral.hpp" + +#include + +namespace kstd +{ + + template<> + struct formatter : formatter + { + bits::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 (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none) + { + bits::format::error("Invalid format specifiers for 'char'"); + } + + if (it != end && *it != '}') + { + if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X') + { + specifiers.type = *it; + std::advance(it, 1); + } + else + { + bits::format::error("Invalid type specifier for char."); + } + } + + if (it != end && *it != '}') + { + bits::format::error("Missing terminating '}' in format string."); + } + + return it; + } + + auto format(char value, format_context & context) const -> void + { + if (specifiers.type == '\0' || specifiers.type == 'c') + { + auto final_width = 0uz; + + if (specifiers.mode == bits::format::width_mode::static_value) + { + final_width = specifiers.width_value; + } + else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id) + { + auto const & arg = context.arg(specifiers.width_value); + final_width = bits::format::extrat_dynamic_width(arg); + } + + auto padding = + bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left); + + for (auto i = 0uz; i < padding.left; ++i) + { + context.push(specifiers.fill); + } + + context.push(value); + + for (auto i = 0uz; i < padding.right; ++i) + { + context.push(specifiers.fill); + } + } + else + { + formatter::format(static_cast(value), context); + } + } + }; + +} // namespace kstd + +#endif \ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format index d11c221..047ea5c 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -7,6 +7,7 @@ #include "bits/format/formatter.hpp" // IWYU pragma: export #include "bits/format/formatter/bool.hpp" // IWYU pragma: export #include "bits/format/formatter/byte.hpp" // IWYU pragma: export +#include "bits/format/formatter/char.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 diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp index 4915e50..73c8102 100644 --- a/libs/kstd/tests/src/format.cpp +++ b/libs/kstd/tests/src/format.cpp @@ -40,21 +40,21 @@ SCENARIO("Formatting to a new string", "[format]") WHEN("calling format with the same number of arguments as there are placeholders") { - auto result = kstd::format(fmt, 1, true, -100); + auto result = kstd::format(fmt, 1, true, 'a'); THEN("the result is the formatted string") { - REQUIRE(result == "Here are some placeholders: 1 true -100"); + REQUIRE(result == "Here are some placeholders: 1 true a"); } } WHEN("calling format with too many arguments") { - auto result = kstd::format(fmt, 2, false, -200, 4, 5, 6); + auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); THEN("the result is the formatted string") { - REQUIRE(result == "Here are some placeholders: 2 false -200"); + REQUIRE(result == "Here are some placeholders: 2 false b"); } } } -- cgit v1.2.3