diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2025-12-18 12:44:09 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2025-12-18 12:44:09 +0100 |
| commit | 6733428bee53a316ae925cc6378a793be467e86f (patch) | |
| tree | 805374a5260f857e7a52636268ed1943ed029716 /libs | |
| parent | 4fdf7c2656eeff67bcb424c9a3d76abe78ad2f91 (diff) | |
| download | teachos-6733428bee53a316ae925cc6378a793be467e86f.tar.xz teachos-6733428bee53a316ae925cc6378a793be467e86f.zip | |
kstd: begin basic formatted output implementation
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/kstd/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_context.hpp | 31 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_specs.hpp | 104 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/format_string.hpp | 131 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/bits/formatter.hpp | 193 | ||||
| -rw-r--r-- | libs/kstd/include/kstd/format | 8 |
6 files changed, 472 insertions, 0 deletions
diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index d83e704..0814a5b 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -18,6 +18,10 @@ target_sources("kstd" PUBLIC FILE_SET HEADERS BASE_DIRS "include" FILES + "include/kstd/bits/format_context.hpp" + "include/kstd/bits/format_specs.hpp" + "include/kstd/bits/format_string.hpp" + "include/kstd/bits/formatter.hpp" "include/kstd/bits/os.hpp" "include/kstd/bits/shared_ptr.hpp" "include/kstd/bits/unique_ptr.hpp" @@ -25,6 +29,7 @@ target_sources("kstd" PUBLIC "include/kstd/ext/bitfield_enum" "include/kstd/asm_ptr" + "include/kstd/format" "include/kstd/memory" "include/kstd/mutex" "include/kstd/stack" 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..69f0629 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_context.hpp @@ -0,0 +1,31 @@ +#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP +#define KSTD_BITS_FORMAT_CONTEXT_HPP + +// IWYU pragma: private, include <kstd/format> + +#include <string_view> + +namespace kstd +{ + + struct format_context + { + using writer_function = void(void *, std::string_view); + + writer_function writer; + void * user_data; + + 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_specs.hpp b/libs/kstd/include/kstd/bits/format_specs.hpp new file mode 100644 index 0000000..092a875 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_specs.hpp @@ -0,0 +1,104 @@ +#ifndef KSTD_BITS_FORMAT_SPECS_HPP +#define KSTD_BITS_FORMAT_SPECS_HPP + +// IWYU pragma: private + +#include <cstddef> +#include <iterator> +#include <string_view> + +namespace kstd::bits +{ + + struct format_specs + { + std::size_t width{}; + char fill{' '}; + char type{}; + bool align_left{}; + bool sign_plus{}; + bool sign_space{}; + bool alternative_form{}; + bool zero_pad{}; + }; + + constexpr auto parse_specs(std::string_view string, format_specs & specs) -> std::string_view + { + auto current = string.begin(); + auto end = string.end(); + + if (current == end || *current != ':') + { + return {current, end}; + } + + std::advance(current, 1); + + if (current != end && std::next(current) != end && (*std::next(current) == '<' || *std::next(current) == '>')) + { + specs.fill = *current; + specs.align_left = *std::next(current) == '<'; + std::advance(current, 2); + } + else if (current != end) + { + if (*current == '<') + { + specs.align_left = true; + std::advance(current, 1); + } + else if (*current == '>') + { + specs.align_left = false; + std::advance(current, 1); + } + } + + if (current != end) + { + if (*current == '+') + { + specs.sign_plus = true; + std::advance(current, 1); + } + else if (*current == ' ') + { + specs.sign_space = true; + std::advance(current, 1); + } + else if (*current == '-') + { + std::advance(current, 1); + } + } + + if (current != end && *current == '#') + { + specs.alternative_form = true; + std::advance(current, 1); + } + + if (current != end && *current == '0') + { + specs.zero_pad = true; + std::advance(current, 1); + } + + while (current != end && *current >= '0' && *current <= '9') + { + specs.width = specs.width * 10 + (*current - '0'); + std::advance(current, 1); + } + + if (current != end && *current != '}') + { + specs.type = *current; + std::advance(current, 1); + } + + return {current, end}; + } + +} // 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 new file mode 100644 index 0000000..3a15bf0 --- /dev/null +++ b/libs/kstd/include/kstd/bits/format_string.hpp @@ -0,0 +1,131 @@ +#ifndef KSTD_BITS_FORMAT_STRING_HPP +#define KSTD_BITS_FORMAT_STRING_HPP + +// IWYU pragma: private, include <kstd/format> + +#include <algorithm> +#include <cstddef> +#include <iterator> +#include <string_view> + +namespace kstd +{ + + namespace bits + { + auto invalid_format_string(char const *) -> void; + + consteval auto validate_format_string(std::string_view string, std::size_t argument_count) -> void + { + auto next_automatic_index = 0uz; + auto placeholder_count = 0uz; + auto has_manual_index = false; + auto has_automatic_index = false; + + auto current = string.begin(); + auto end = string.end(); + + while (current != end) + { + if (*current == '{') + { + if (std::next(current) != end && *std::next(current) == '{') + { + std::advance(current, 2); + continue; + } + + std::advance(current, 1); + + auto index = 0uz; + auto is_manual_index = false; + + if (current != end && *current >= '0' && *current <= '9') + { + is_manual_index = true; + while (current != end && *current >= '0' && *current <= '9') + { + index = index * 10 + (*current - '0'); + std::advance(current, 1); + } + } + + if (is_manual_index) + { + placeholder_count = std::max(placeholder_count, index + 1); + if (has_automatic_index) + { + invalid_format_string("Cannot mix automatic and manual indexing."); + } + has_manual_index = true; + if (index >= argument_count) + { + invalid_format_string("Argument index out of range"); + } + } + else + { + if (has_manual_index) + { + invalid_format_string("Cannot mix automatic and manual indexing."); + } + has_automatic_index = true; + ++placeholder_count; + if (next_automatic_index >= argument_count) + { + invalid_format_string("Not enough arguments provided for format string."); + } + index = next_automatic_index++; + } + + while (current != end && *current != '}') + { + std::advance(current, 1); + } + + if (current == end) + { + invalid_format_string("Unexpected end of format string."); + } + std::advance(current, 1); + } + else if (*current == '}') + { + if (std::next(current) != end && *std::next(current) == '}') + { + std::advance(current, 2); + continue; + } + invalid_format_string("Unexpected '}' in format string."); + } + else + { + std::advance(current, 1); + } + } + if (argument_count < placeholder_count) + { + invalid_format_string("Not enough arguments provided for format string."); + } + else if (argument_count > placeholder_count) + { + invalid_format_string("Too many arguments provided for format string."); + } + } + } // namespace bits + + template<typename... Args> + struct format_string + { + std::string_view str; + + consteval format_string(char const * format) + : str{format} + { + bits::validate_format_string(str, sizeof...(Args)); + } + }; + +} // 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 new file mode 100644 index 0000000..ce29b59 --- /dev/null +++ b/libs/kstd/include/kstd/bits/formatter.hpp @@ -0,0 +1,193 @@ +#ifndef KSTD_BITS_FORMATTER_HPP +#define KSTD_BITS_FORMATTER_HPP + +// IWYU pragma: private, include <kstd/format> + +#include <kstd/bits/format_context.hpp> +#include <kstd/bits/format_specs.hpp> + +#include <array> +#include <concepts> +#include <cstddef> +#include <iterator> +#include <string_view> +#include <type_traits> + +namespace kstd +{ + + template<typename T> + struct formatter; + + template<std::integral T> + struct formatter<T> + { + bits::format_specs specs; + + constexpr auto parse(std::string_view context) -> std::string_view + { + return bits::parse_specs(context, specs); + } + + auto format(T value, format_context & context) -> void + { + using unsigned_T = std::make_unsigned<T>; + auto absolute_value = static_cast<unsigned_T>(value); + auto is_negative = false; + + if constexpr (std::is_signed_v<T>) + { + if (value < 0) + { + is_negative = true; + absolute_value = 0 - value; + } + } + + auto const base = [type = specs.type] -> auto { + switch (type) + { + case 'x': + case 'X': + case 'p': + return 16; + case 'b': + case 'B': + return 2; + case 'o': + return 8; + default: + return 10; + } + }(); + + auto buffer = std::array<char, 80>{}; + auto digits = (base == 16) ? "0123456789abcdef" : "0123456789"; + auto current = buffer.rbegin(); + + if (absolute_value == 0) + { + *std::next(current) = '0'; + } + else + { + while (absolute_value != 0) + { + *std::next(current) = digits[absolute_value % base]; + absolute_value /= base; + } + } + + auto prefix = std::array<char, 2>{'0', '\0'}; + auto prefix_length = 0uz; + if (specs.alternative_form && value != 0) + { + if (base == 16) + { + prefix[1] = specs.type == 'X' ? 'X' : 'x'; + prefix_length = 2; + } + else if (base == 8) + { + prefix_length = 1; + } + else if (base == 2) + { + prefix[1] = specs.type == 'B' ? 'B' : 'b'; + prefix_length = 2; + } + } + + auto sign_character = '\0'; + if (is_negative) + { + sign_character = '-'; + } + else if (specs.sign_plus) + { + sign_character = '+'; + } + else if (specs.sign_space) + { + sign_character = ' '; + } + + auto const content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current)); + auto const total_length = content_length + prefix_length + (sign_character != '\0'); + auto const padding_length = (specs.width > total_length) ? (specs.width - total_length) : 0; + + if (!specs.align_left && !specs.zero_pad) + { + for (auto i = 0uz; i < padding_length; ++i) + { + context.push(specs.fill); + } + } + + if (sign_character) + { + context.push(sign_character); + } + + if (prefix_length) + { + context.push({prefix.data(), prefix_length}); + } + + if (!specs.align_left && specs.zero_pad) + { + for (auto i = 0uz; i < padding_length; ++i) + { + context.push('0'); + } + } + + context.push({current.base(), content_length}); + + if (specs.align_left) + { + for (auto i = 0uz; i < padding_length; ++i) + { + context.push(specs.fill); + } + } + } + }; + + struct format_arg + { + void const * value; + auto (*format)(void const *, std::string_view, format_context &) -> std::string_view; + }; + + template<typename T> + auto format_dispatcher(void const * val, std::string_view spec, format_context & ctx) -> std::string_view + { + formatter<T> f; + auto const remainder = f.parse(spec); + f.format(*static_cast<T const *>(val), ctx); + return remainder; + } + + class format_args + { + format_arg const * args_; + std::size_t count_; + + public: + constexpr format_args(format_arg const * args, std::size_t count) + : args_(args) + , count_(count) + {} + + [[nodiscard]] constexpr auto get(std::size_t idx) const -> format_arg + { + if (idx >= count_) + return {.value = nullptr, .format = nullptr}; + return args_[idx]; + } + }; + +} // namespace kstd + +#endif
\ No newline at end of file diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format new file mode 100644 index 0000000..0a1bcd1 --- /dev/null +++ b/libs/kstd/include/kstd/format @@ -0,0 +1,8 @@ +#ifndef KSTD_FORMAT_HPP +#define KSTD_FORMAT_HPP + +#include "bits/format_context.hpp" // IWYU pragma: export +#include "bits/format_string.hpp" // IWYU pragma: export +#include "bits/formatter.hpp" // IWYU pragma: export + +#endif
\ No newline at end of file |
