aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2025-12-18 12:44:09 +0100
committerFelix Morgner <felix.morgner@ost.ch>2025-12-18 12:44:09 +0100
commit6733428bee53a316ae925cc6378a793be467e86f (patch)
tree805374a5260f857e7a52636268ed1943ed029716 /libs
parent4fdf7c2656eeff67bcb424c9a3d76abe78ad2f91 (diff)
downloadteachos-6733428bee53a316ae925cc6378a793be467e86f.tar.xz
teachos-6733428bee53a316ae925cc6378a793be467e86f.zip
kstd: begin basic formatted output implementation
Diffstat (limited to 'libs')
-rw-r--r--libs/kstd/CMakeLists.txt5
-rw-r--r--libs/kstd/include/kstd/bits/format_context.hpp31
-rw-r--r--libs/kstd/include/kstd/bits/format_specs.hpp104
-rw-r--r--libs/kstd/include/kstd/bits/format_string.hpp131
-rw-r--r--libs/kstd/include/kstd/bits/formatter.hpp193
-rw-r--r--libs/kstd/include/kstd/format8
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