aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-03-20 12:02:28 +0100
committerFelix Morgner <felix.morgner@ost.ch>2026-03-20 12:02:28 +0100
commit24703d5b9bc297616b8baf8957ab7e74e2f67352 (patch)
treef30bd8e8f7f734085ae58c3002519a756ef06091 /libs
parent1865f7a162a496592e236ffcff171e7e7bc47ee2 (diff)
parentd7147ddd7416a6cce874d290d1615527f4d7cdb9 (diff)
downloadteachos-24703d5b9bc297616b8baf8957ab7e74e2f67352.tar.xz
teachos-24703d5b9bc297616b8baf8957ab7e74e2f67352.zip
Merge branch 'fmorgner/align-format-with-stdlib' into develop-BA-FS26
Diffstat (limited to 'libs')
-rw-r--r--libs/kstd/include/kstd/bits/format_args.hpp68
-rw-r--r--libs/kstd/include/kstd/bits/format_context.hpp43
-rw-r--r--libs/kstd/include/kstd/bits/format_parse_context.hpp122
-rw-r--r--libs/kstd/include/kstd/bits/format_specifiers.hpp205
-rw-r--r--libs/kstd/include/kstd/bits/format_specs.hpp104
-rw-r--r--libs/kstd/include/kstd/bits/format_string.hpp173
-rw-r--r--libs/kstd/include/kstd/bits/formatter.hpp328
-rw-r--r--libs/kstd/include/kstd/bits/print_sink.hpp2
-rw-r--r--libs/kstd/include/kstd/os/print.hpp2
-rw-r--r--libs/kstd/include/kstd/print20
10 files changed, 713 insertions, 354 deletions
diff --git a/libs/kstd/include/kstd/bits/format_args.hpp b/libs/kstd/include/kstd/bits/format_args.hpp
new file mode 100644
index 0000000..71ee5ae
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format_args.hpp
@@ -0,0 +1,68 @@
+#ifndef KSTD_BITS_FORMAT_ARGS_HPP
+#define KSTD_BITS_FORMAT_ARGS_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
+
+#include <array>
+#include <cstddef>
+#include <type_traits>
+
+namespace kstd
+{
+ struct format_context;
+ struct format_parse_context;
+
+ template<typename>
+ struct formatter;
+
+ template<std::size_t Count>
+ struct format_arg_store
+ {
+ std::array<format_arg, Count> args{};
+ };
+
+ template<typename ValueType>
+ auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context)
+ -> void
+ {
+ auto typed_value_pointer = static_cast<ValueType const *>(value_pointer);
+ auto fmt = formatter<std::remove_cvref_t<ValueType>>{};
+ auto const it = fmt.parse(parse_context);
+ parse_context.advance_to(it);
+ 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)>
+ {
+ if constexpr (sizeof...(Arguments) == 0)
+ {
+ return format_arg_store<0>{};
+ }
+ else
+ {
+ return format_arg_store<sizeof...(Arguments)>{std::array<format_arg, sizeof...(Arguments)>{
+ format_arg{static_cast<void const *>(&args), format_trampoline<Arguments>, get_size_trampoline<Arguments>}...}};
+ }
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp
index b5c7d21..05e37ca 100644
--- a/libs/kstd/include/kstd/bits/format_context.hpp
+++ b/libs/kstd/include/kstd/bits/format_context.hpp
@@ -3,17 +3,60 @@
// IWYU pragma: private, include <kstd/format>
+#include <kstd/os/error.hpp>
+
+#include <concepts>
+#include <cstddef>
+#include <span>
#include <string_view>
namespace kstd
{
+ namespace bits
+ {
+ 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;
+ struct format_context;
+
+ 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;
+
+ 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
new file mode 100644
index 0000000..00cca40
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp
@@ -0,0 +1,205 @@
+#ifndef KSTD_BITS_FORMAT_SPECS_HPP
+#define KSTD_BITS_FORMAT_SPECS_HPP
+
+// IWYU pragma: private
+
+#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
+
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+
+namespace kstd::bits
+{
+ enum struct alignment : std::uint8_t
+ {
+ none,
+ left,
+ right,
+ center,
+ };
+
+ enum struct sign_mode : std::uint8_t
+ {
+ none,
+ plus,
+ minus,
+ space,
+ };
+
+ enum struct width_mode : std::uint8_t
+ {
+ none,
+ static_value,
+ dynamic_argument_id
+ };
+
+ struct format_specifiers
+ {
+ char fill{' '};
+ alignment align{};
+ sign_mode sign{};
+ bool alternative_form{};
+ bool zero_pad{};
+
+ width_mode width_mode{};
+ std::size_t width_value{};
+ char type{};
+ };
+
+ struct format_padding
+ {
+ std::size_t left{};
+ std::size_t right{};
+ };
+
+ constexpr auto parse_format_specifiers(format_parse_context & context) -> format_specifiers
+ {
+ auto specifiers = format_specifiers{};
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it == '}')
+ {
+ return specifiers;
+ }
+
+ if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^'))
+ {
+ specifiers.fill = *it;
+ switch (*std::next(it))
+ {
+ case '<':
+ specifiers.align = alignment::left;
+ break;
+ case '>':
+ specifiers.align = alignment::right;
+ break;
+ case '^':
+ default:
+ specifiers.align = alignment::center;
+ break;
+ }
+ std::advance(it, 2);
+ }
+ else if (*it == '<' || *it == '>' || *it == '^')
+ {
+ switch (*it)
+ {
+ case '<':
+ specifiers.align = alignment::left;
+ break;
+ case '>':
+ specifiers.align = alignment::right;
+ break;
+ case '^':
+ default:
+ specifiers.align = alignment::center;
+ break;
+ }
+ std::advance(it, 1);
+ }
+
+ if (it != end && (*it == '+' || *it == '-' || *it == ' '))
+ {
+ switch (*it)
+ {
+ case '+':
+ specifiers.sign = sign_mode::plus;
+ break;
+ case '-':
+ specifiers.sign = sign_mode::minus;
+ break;
+ case ' ':
+ default:
+ specifiers.sign = sign_mode::space;
+ break;
+ }
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '#')
+ {
+ specifiers.alternative_form = true;
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '0')
+ {
+ specifiers.zero_pad = true;
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '{')
+ {
+ specifiers.width_mode = width_mode::dynamic_argument_id;
+ std::advance(it, 1);
+ auto argument_id = 0uz;
+
+ if (it != end && *it >= '0' && *it <= '9')
+ {
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ context.check_dynamic_width_id(argument_id);
+ }
+ else
+ {
+ argument_id = context.next_dynamic_width_id();
+ }
+
+ if (it == end || *it != '}')
+ {
+ report_format_error("Expected '}' for dynamic width.");
+ }
+ std::advance(it, 1);
+ specifiers.width_value = argument_id;
+ }
+ else if (it != end && *it >= '0' && *it <= '9')
+ {
+ specifiers.width_mode = width_mode::static_value;
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ specifiers.width_value = specifiers.width_value * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ }
+
+ context.advance_to(it);
+ return specifiers;
+ }
+
+ constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length,
+ alignment requested_alignment, alignment default_alignment) -> format_padding
+ {
+ if (target_width <= content_length)
+ {
+ return {};
+ }
+
+ auto total_padding = target_width - content_length;
+ auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment;
+
+ switch (effective_alignment)
+ {
+ case alignment::center:
+ {
+ auto left = total_padding / 2;
+ auto right = total_padding - left;
+ return {left, right};
+ }
+ case alignment::left:
+ return {0, total_padding};
+ case alignment::right:
+ default:
+ return {total_padding, 0};
+ break;
+ }
+ }
+
+} // namespace kstd::bits
+
+#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
deleted file mode 100644
index 092a875..0000000
--- a/libs/kstd/include/kstd/bits/format_specs.hpp
+++ /dev/null
@@ -1,104 +0,0 @@
-#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
index 3a15bf0..f16f1ee 100644
--- a/libs/kstd/include/kstd/bits/format_string.hpp
+++ b/libs/kstd/include/kstd/bits/format_string.hpp
@@ -3,127 +3,142 @@
// IWYU pragma: private, include <kstd/format>
-#include <algorithm>
+#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
+
+#include <array>
#include <cstddef>
-#include <iterator>
#include <string_view>
+#include <type_traits>
namespace kstd
{
+ template<typename>
+ struct formatter;
+
namespace bits
{
- auto invalid_format_string(char const *) -> void;
-
- consteval auto validate_format_string(std::string_view string, std::size_t argument_count) -> void
+ template<typename... Args>
+ constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void
{
- auto next_automatic_index = 0uz;
- auto placeholder_count = 0uz;
- auto has_manual_index = false;
- auto has_automatic_index = false;
+ auto found = false;
+ auto current_index = 0uz;
- auto current = string.begin();
- auto end = string.end();
+ (..., [&] {
+ if (current_index == target_index && !found)
+ {
+ using decay_type = std::remove_cvref_t<Args>;
+ auto fmt = formatter<decay_type>{};
+
+ auto it = fmt.parse(context);
+ context.advance_to(it);
+ found = true;
+ }
+ ++current_index;
+ }());
- while (current != end)
+ if (!found)
{
- if (*current == '{')
+ 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)
{
- if (std::next(current) != end && *std::next(current) == '{')
+ using decay_type = std::remove_cvref_t<Args>;
+ if constexpr (std::is_integral_v<decay_type>)
{
- std::advance(current, 2);
- continue;
+ is_valid_integer = true;
}
+ }
+ ++current_index;
+ }());
- std::advance(current, 1);
+ if (!is_valid_integer)
+ {
+ report_format_error("Dynamic width argument must be an integral object.");
+ }
+ }
+ } // namespace bits
- auto index = 0uz;
- auto is_manual_index = false;
+ template<typename... Args>
+ struct format_string
+ {
+ template<std::size_t Size>
+ consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT
+ : str_view{str}
+ {
+ 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();
- if (current != end && *current >= '0' && *current <= '9')
+ while (it != context.end())
+ {
+ if (*it == '{')
+ {
+ ++it;
+ if (it != context.end() && *it == '{')
{
- is_manual_index = true;
- while (current != end && *current >= '0' && *current <= '9')
- {
- index = index * 10 + (*current - '0');
- std::advance(current, 1);
- }
+ ++it;
+ context.advance_to(it);
+ continue;
}
- if (is_manual_index)
+ context.advance_to(it);
+ auto argument_id = 0uz;
+
+ if (it != context.end() && *it >= '0' && *it <= '9')
{
- placeholder_count = std::max(placeholder_count, index + 1);
- if (has_automatic_index)
+ while (it != context.end() && *it >= '0' && *it <= '9')
{
- 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");
+ argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
+ ++it;
}
+ context.check_arg_id(argument_id);
+ context.advance_to(it);
}
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++;
+ argument_id = context.next_arg_id();
}
- while (current != end && *current != '}')
+ if (it != context.end() && *it == ':')
{
- std::advance(current, 1);
+ ++it;
+ context.advance_to(it);
}
- if (current == end)
+ bits::validate_argument<Args...>(argument_id, context);
+
+ it = context.begin();
+ if (it == context.end() || *it != '}')
{
- invalid_format_string("Unexpected end of format string.");
+ report_format_error("Missing closing '}' in format string.");
}
- std::advance(current, 1);
}
- else if (*current == '}')
+ else if (*it == '}')
{
- if (std::next(current) != end && *std::next(current) == '}')
+ ++it;
+ if (it != context.end() && *it == '}')
{
- std::advance(current, 2);
- continue;
+ report_format_error("Unescaped '}' in format string.");
}
- 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.");
+ ++it;
+ context.advance_to(it);
}
}
- } // 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));
- }
+ std::string_view str_view;
};
} // namespace kstd
diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp
index 7c9c31d..e46f6ad 100644
--- a/libs/kstd/include/kstd/bits/formatter.hpp
+++ b/libs/kstd/include/kstd/bits/formatter.hpp
@@ -3,8 +3,10 @@
// IWYU pragma: private, include <kstd/format>
-#include <kstd/bits/format_context.hpp>
-#include <kstd/bits/format_specs.hpp>
+#include "kstd/bits/format_context.hpp"
+#include "kstd/bits/format_parse_context.hpp"
+#include "kstd/bits/format_specifiers.hpp"
+
#include <kstd/os/error.hpp>
#include <array>
@@ -21,30 +23,98 @@
namespace kstd
{
- template<typename T>
+ template<typename>
struct formatter;
+ template<>
+ struct formatter<std::string_view>
+ {
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ auto it = context.begin();
+
+ if (it != context.end() && *it == 's')
+ {
+ ++it;
+ }
+
+ if (it != context.end() && *it != '}')
+ {
+ report_format_error("Invalid specifier for string_view.");
+ }
+ return it;
+ }
+
+ auto format(std::string_view const & string, format_context & context) const -> void
+ {
+ context.push(string);
+ }
+ };
+
+ template<>
+ struct formatter<char const *> : formatter<std::string_view>
+ {
+ auto format(char const * string, format_context & context) const -> void
+ {
+ formatter<std::string_view>::format(string ? std::string_view{string} : "(null)", context);
+ }
+ };
+
template<std::integral T>
struct formatter<T>
{
- bits::format_specs specs{};
+ bits::format_specifiers specifiers{};
+
+ constexpr auto static maximum_digits = 80;
- constexpr auto parse(std::string_view context) -> std::string_view
+ enum struct base
{
- return bits::parse_specs(context, specs);
+ bin = 2,
+ oct = 8,
+ dec = 10,
+ hex = 16,
+ };
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ report_format_error("Invalid type specifier for integral type.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ report_format_error("Missing terminating '}' in format string.");
+ }
+
+ return it;
}
auto format(T value, format_context & context) const -> void
{
- enum struct base
+ auto final_width = 0uz;
+ if (specifiers.width_mode == bits::width_mode::static_value)
{
- bin = 2,
- oct = 8,
- dec = 10,
- hex = 16,
- };
-
- constexpr auto static maximum_digits = 80;
+ 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);
+ }
using unsigned_T = std::make_unsigned_t<T>;
auto absolute_value = static_cast<unsigned_T>(value);
@@ -55,11 +125,11 @@ namespace kstd
if (value < 0)
{
is_negative = true;
- absolute_value = 0 - value;
+ absolute_value = 0 - static_cast<unsigned_T>(value);
}
}
- auto const base = [type = specs.type] -> auto {
+ auto const base = [type = specifiers.type] -> auto {
switch (type)
{
case 'x':
@@ -77,7 +147,7 @@ namespace kstd
}();
auto buffer = std::array<char, maximum_digits>{};
- auto digits = (specs.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef";
+ auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef";
auto current = buffer.rbegin();
if (absolute_value == 0)
@@ -95,21 +165,22 @@ namespace kstd
}
}
+ auto content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current));
auto prefix = std::array<char, 2>{'0', '\0'};
auto prefix_length = 0uz;
- if (specs.alternative_form)
+ if (specifiers.alternative_form)
{
switch (base)
{
case base::bin:
- prefix[1] = specs.type == 'B' ? 'B' : 'b';
+ prefix[1] = specifiers.type == 'B' ? 'B' : 'b';
prefix_length = 2;
break;
case base::oct:
prefix_length = 1;
break;
case base::hex:
- prefix[1] = specs.type == 'X' ? 'X' : 'x';
+ prefix[1] = specifiers.type == 'X' ? 'X' : 'x';
prefix_length = 2;
break;
default:
@@ -122,53 +193,50 @@ namespace kstd
{
sign_character = '-';
}
- else if (specs.sign_plus)
+ else if (specifiers.sign == bits::sign_mode::plus)
{
sign_character = '+';
}
- else if (specs.sign_space)
+ else if (specifiers.sign == bits::sign_mode::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;
+ auto const padding =
+ bits::calculate_format_padding(final_width, total_length, specifiers.align, bits::alignment::right);
+ auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::alignment::none);
- if (!specs.align_left && !specs.zero_pad)
+ if (!effective_zero_pad)
{
- for (auto i = 0uz; i < padding_length; ++i)
+ for (auto i = 0uz; i < padding.left; ++i)
{
- context.push(specs.fill);
+ context.push(specifiers.fill);
}
}
- if (sign_character)
+ if (sign_character != '\0')
{
context.push(sign_character);
}
-
- if (prefix_length)
+ if (prefix_length > 0)
{
- context.push({prefix.data(), prefix_length});
+ context.push(std::string_view{prefix.data(), prefix_length});
}
- if (!specs.align_left && specs.zero_pad)
+ if (effective_zero_pad)
{
- for (auto i = 0uz; i < padding_length; ++i)
+ for (auto i = 0uz; i < padding.left; ++i)
{
context.push('0');
}
}