diff options
| -rw-r--r-- | kernel/kstd/print.cpp | 215 | ||||
| -rw-r--r-- | libs/kstd/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format/context.hpp | 17 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format/formatter/bool.hpp | 4 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format/formatter/char.hpp | 94 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format/formatter/integral.hpp | 4 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format/output_buffer.hpp | 32 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format/specifiers.hpp | 6 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format/vformat.hpp | 99 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/format | 3 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/string | 20 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/vector | 2 | ||||
| -rw-r--r-- | libs/kstd/src/vformat.cpp | 209 | ||||
| -rw-r--r-- | libs/kstd/tests/src/format.cpp | 116 |
14 files changed, 614 insertions, 209 deletions
diff --git a/kernel/kstd/print.cpp b/kernel/kstd/print.cpp index beee2e5..7854027 100644 --- a/kernel/kstd/print.cpp +++ b/kernel/kstd/print.cpp @@ -1,12 +1,13 @@ #include "kapi/cio.hpp" +#include <kstd/bits/format/output_buffer.hpp> #include <kstd/format> #include <kstd/os/print.hpp> #include <kstd/print> +#include <algorithm> #include <array> #include <cstddef> -#include <iterator> #include <string_view> namespace kstd::os @@ -14,7 +15,7 @@ namespace kstd::os namespace { - struct write_buffer + struct write_buffer final : kstd::bits::format::output_buffer { using output_stream = kapi::cio::output_stream; @@ -29,35 +30,36 @@ namespace kstd::os : m_stream{stream} {} - ~write_buffer() noexcept + ~write_buffer() noexcept final { flush(); } - auto flush() noexcept -> void + auto push(std::string_view text) -> void final { - if (m_position > 0) + std::ranges::for_each(text, [this](auto c) { this->push(c); }); + } + + auto push(char character) -> void final + { + if (m_position >= size) { - std::string_view chunk{m_buffer.data(), m_position}; - kapi::cio::write(m_stream, chunk); - m_position = 0; + flush(); } + m_buffer.at(m_position++) = character; } - auto static callback(void * object, std::string_view text) -> void + private: + auto flush() noexcept -> void { - auto * self = static_cast<write_buffer *>(object); - for (char const character : text) + if (m_position > 0) { - if (self->m_position >= size) - { - self->flush(); - } - self->m_buffer.at(self->m_position++) = character; + std::string_view chunk{m_buffer.data(), m_position}; + kapi::cio::write(m_stream, chunk); + m_position = 0; } } - private: output_stream m_stream; std::array<char, size> m_buffer{}; std::size_t m_position{}; @@ -69,184 +71,7 @@ namespace kstd::os { auto writer = write_buffer{(sink == print_sink::stderr) ? kapi::cio::output_stream::stderr : kapi::cio::output_stream::stdout}; - auto context = kstd::format_context{.writer = write_buffer::callback, .user_data = &writer, .args = 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<std::size_t>(*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<bool>{}; - 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<char>{}; - 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<long long>{}; - 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<unsigned long long>{}; - 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<std::string_view>{}; - 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<char const *>{}; - 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<void const *>{}; - 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); - } - } - } + kstd::bits::format::vformat_to(writer, format, args); } } // namespace kstd::os diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 240118e..ced3138 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/*") @@ -43,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/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp index 478a48f..7f392a0 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 <kstd/format> #include "arg.hpp" +#include "kstd/bits/format/output_buffer.hpp" #include <kstd/os/error.hpp> @@ -29,13 +30,14 @@ namespace kstd 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{}; + 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()) @@ -47,13 +49,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/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/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 <iterator> + +namespace kstd +{ + + template<> + struct formatter<char> : formatter<unsigned char> + { + 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<unsigned char>::format(static_cast<unsigned char>(value), context); + } + } + }; + +} // 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 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/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp new file mode 100644 index 0000000..fd7a2b4 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/output_buffer.hpp @@ -0,0 +1,32 @@ +#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP +#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP + +// IWYU pragma: private, include <kstd/format> + +#include <string_view> + +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 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<std::size_t>(*it - '0'); 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..4fec7dd --- /dev/null +++ b/libs/kstd/include/kstd/bits/format/vformat.hpp @@ -0,0 +1,99 @@ +#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP +#define KSTD_BITS_FORMAT_VFORMAT_HPP + +// IWYU pragma: private, include <kstd/format> + +#include <kstd/bits/format/args.hpp> +#include <kstd/bits/format/output_buffer.hpp> +#include <kstd/bits/format/string.hpp> +#include <kstd/string> + +#include <algorithm> +#include <iterator> +#include <string_view> +#include <type_traits> + +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{}; + }; + + template<std::output_iterator<char> Output> + struct iterator_writer : output_buffer + { + explicit iterator_writer(Output iterator) + : m_output{iterator} + {} + + auto iterator() const -> Output + { + return m_output; + } + + auto push(std::string_view text) -> void override + { + m_output = std::ranges::copy(text, m_output).out; + } + + auto push(char character) -> void override + { + *m_output++ = character; + } + + private: + Output m_output{}; + }; + + } // 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<typename... ArgumentTypes> + auto format(format_string<std::type_identity_t<ArgumentTypes>...> format, ArgumentTypes &&... args) -> string + { + auto buffer = bits::format::string_writer{}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args); + 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<std::output_iterator<char> Output, typename... ArgumentTypes> + auto format_to(Output iterator, format_string<std::type_identity_t<ArgumentTypes>...> format, + ArgumentTypes &&... args) -> Output + { + auto buffer = bits::format::iterator_writer{iterator}; + bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args); + return buffer.iterator(); + } + +} // 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..047ea5c 100644 --- a/libs/kstd/include/kstd/format +++ b/libs/kstd/include/kstd/format @@ -7,13 +7,16 @@ #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 #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 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<string> : formatter<std::string_view> { 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<allocator_type>) : m_allocator{allocator} - , m_size{std::ranges::distance(first, last)} + , m_size{static_cast<std::size_t>(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..b7c5121 --- /dev/null +++ b/libs/kstd/src/vformat.cpp @@ -0,0 +1,209 @@ +#include <kstd/format> +#include <kstd/string> + +#include <cstddef> +#include <iterator> +#include <string_view> +#include <utility> + +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<std::size_t>(*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<bool>{}; + 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<char>{}; + 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<long long>{}; + 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<unsigned long long>{}; + 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<std::string_view>{}; + 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<char const *>{}; + 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<void const *>{}; + 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); + } + +} // namespace kstd::bits::format
\ No newline at end of file diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp new file mode 100644 index 0000000..73c8102 --- /dev/null +++ b/libs/kstd/tests/src/format.cpp @@ -0,0 +1,116 @@ +#include <kstd/flat_map> +#include <kstd/format> +#include <kstd/tests/os_panic.hpp> + +#include <catch2/catch_test_macros.hpp> + +#include <iterator> +#include <sstream> + +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("cal |
