aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-03-20 12:01:22 +0100
committerFelix Morgner <felix.morgner@ost.ch>2026-03-20 12:01:22 +0100
commitd7147ddd7416a6cce874d290d1615527f4d7cdb9 (patch)
treef30bd8e8f7f734085ae58c3002519a756ef06091 /libs
parent4f942e014dab44ccb8850c5921b81d4bd777d831 (diff)
downloadteachos-d7147ddd7416a6cce874d290d1615527f4d7cdb9.tar.xz
teachos-d7147ddd7416a6cce874d290d1615527f4d7cdb9.zip
kstd/format: implement dynamic width support
Diffstat (limited to 'libs')
-rw-r--r--libs/kstd/include/kstd/bits/format_args.hpp32
-rw-r--r--libs/kstd/include/kstd/bits/format_context.hpp114
-rw-r--r--libs/kstd/include/kstd/bits/format_parse_context.hpp122
-rw-r--r--libs/kstd/include/kstd/bits/format_specifiers.hpp5
-rw-r--r--libs/kstd/include/kstd/bits/format_string.hpp30
-rw-r--r--libs/kstd/include/kstd/bits/formatter.hpp11
6 files changed, 216 insertions, 98 deletions
diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp
index d236a23..71ee5ae 100644
--- a/libs/kstd/include/kstd/bits/format_args.hpp
+++ b/libs/kstd/include/kstd/bits/format_args.hpp
@@ -4,28 +4,20 @@
// IWYU pragma: private, include <kstd/format>
#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
#include <array>
#include <cstddef>
-#include <span>
#include <type_traits>
namespace kstd
{
+ struct format_context;
+ struct format_parse_context;
+
template<typename>
struct formatter;
- struct format_arg
- {
- using format_function_type = auto(void const * value, format_parse_context & parse_context,
- format_context & context) -> void;
-
- void const * value_pointer;
- format_function_type * format_function;
- };
-
- using format_args = std::span<format_arg const>;
-
template<std::size_t Count>
struct format_arg_store
{
@@ -43,6 +35,20 @@ namespace kstd
fmt.format(*typed_value_pointer, context);
}
+ template<typename ValueType>
+ auto get_size_trampoline(void const * value_pointer) -> std::size_t
+ {
+ if constexpr (bits::is_format_width_compatible_v<ValueType>)
+ {
+ return static_cast<std::size_t>(*static_cast<ValueType const *>(value_pointer));
+ }
+ else
+ {
+ report_format_error("Dynamic width argument is not an integral value.");
+ return 0;
+ }
+ }
+
template<typename... Arguments>
[[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store<sizeof...(Arguments)>
{
@@ -53,7 +59,7 @@ namespace kstd
else
{
return format_arg_store<sizeof...(Arguments)>{std::array<format_arg, sizeof...(Arguments)>{
- format_arg{static_cast<void const *>(&args), format_trampoline<Arguments>}...}};
+ format_arg{static_cast<void const *>(&args), format_trampoline<Arguments>, get_size_trampoline<Arguments>}...}};
}
}
diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp
index 863047d..05e37ca 100644
--- a/libs/kstd/include/kstd/bits/format_context.hpp
+++ b/libs/kstd/include/kstd/bits/format_context.hpp
@@ -3,106 +3,60 @@
// IWYU pragma: private, include <kstd/format>
-#include "kstd/os/error.hpp"
+#include <kstd/os/error.hpp>
+#include <concepts>
#include <cstddef>
+#include <span>
#include <string_view>
namespace kstd
{
- constexpr auto report_format_error(char const * message) -> void
+ namespace bits
{
- 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.");
- }
+ template<typename T>
+ constexpr auto inline is_format_width_compatible_v = std::integral<T> && //
+ !std::same_as<bool, T> && //
+ !std::same_as<char, T> && //
+ !std::same_as<wchar_t, T> && //
+ !std::same_as<char8_t, T> && //
+ !std::same_as<char16_t, T> && //
+ !std::same_as<char32_t, T>;
}
- struct format_parse_context
- {
- using iterator = std::string_view::const_iterator;
-
- constexpr format_parse_context(std::string_view format, std::size_t argument_count)
- : m_current(format.begin())
- , m_end(format.end())
- , m_argument_count(argument_count)
- {}
-
- [[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;
+ struct format_parse_context;
+ struct format_context;
- 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.");
- }
- }
+ 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;
- 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{};
-
- public:
+ void const * value_pointer;
+ format_function_type * format_function;
+ get_size_function_type * get_size_function;
};
+ using format_args = std::span<format_arg const>;
+
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
{
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..76c3f03
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format_parse_context.hpp
@@ -0,0 +1,122 @@
+#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP
+#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/os/error.hpp>
+
+#include <cstddef>
+#include <string_view>
+
+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
index 8fb50ef..00cca40 100644
--- a/libs/kstd/include/kstd/bits/format_specifiers.hpp
+++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp
@@ -4,6 +4,7 @@
// IWYU pragma: private
#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
#include <cstddef>
#include <cstdint>
@@ -143,11 +144,11 @@ namespace kstd::bits
argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
std::advance(it, 1);
}
- context.check_arg_id(argument_id);
+ context.check_dynamic_width_id(argument_id);
}
else
{
- argument_id = context.next_arg_id();
+ argument_id = context.next_dynamic_width_id();
}
if (it == end || *it != '}')
diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp
index cbdfb7d..f16f1ee 100644
--- a/libs/kstd/include/kstd/bits/format_string.hpp
+++ b/libs/kstd/include/kstd/bits/format_string.hpp
@@ -4,7 +4,9 @@
// IWYU pragma: private, include <kstd/format>
#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
+#include <array>
#include <cstddef>
#include <string_view>
#include <type_traits>
@@ -41,6 +43,30 @@ namespace kstd
report_format_error("Argument index out of bounds.");
}
}
+
+ template<typename... Args>
+ 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<Args>;
+ if constexpr (std::is_integral_v<decay_type>)
+ {
+ is_valid_integer = true;
+ }
+ }
+ ++current_index;
+ }());
+
+ if (!is_valid_integer)
+ {
+ report_format_error("Dynamic width argument must be an integral object.");
+ }
+ }
} // namespace bits
template<typename... Args>
@@ -50,7 +76,9 @@ namespace kstd
consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT
: str_view{str}
{
- auto context = format_parse_context{str_view, sizeof...(Args)};
+ auto const is_width_compatible = std::array<bool, (sizeof...(Args) > 0 ? sizeof...(Args) : 1)>{
+ bits::is_format_width_compatible_v<std::remove_cvref_t<Args>>...};
+ auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()};
auto it = context.begin();
while (it != context.end())
diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp
index e9a45e0..e46f6ad 100644
--- a/libs/kstd/include/kstd/bits/formatter.hpp
+++ b/libs/kstd/include/kstd/bits/formatter.hpp
@@ -4,6 +4,7 @@
// IWYU pragma: private, include <kstd/format>
#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
#include "kstd/bits/format_specifiers.hpp"
#include <kstd/os/error.hpp>
@@ -111,8 +112,8 @@ namespace kstd
}
else if (specifiers.width_mode == bits::width_mode::dynamic_argument_id)
{
- // TODO: implement argument references so we can read the dynamic width argument.
- final_width = 0;
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = arg.get_size_function(arg.value_pointer);
}
using unsigned_T = std::make_unsigned_t<T>;
@@ -307,10 +308,16 @@ namespace kstd
{
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);