aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-03-20 11:19:05 +0100
committerFelix Morgner <felix.morgner@ost.ch>2026-03-20 11:19:05 +0100
commit4f942e014dab44ccb8850c5921b81d4bd777d831 (patch)
treec496d44a061544a5240aee56616b51801455a851
parent1865f7a162a496592e236ffcff171e7e7bc47ee2 (diff)
downloadteachos-4f942e014dab44ccb8850c5921b81d4bd777d831.tar.xz
teachos-4f942e014dab44ccb8850c5921b81d4bd777d831.zip
kstd: rework formatting to be closer to std
-rw-r--r--kapi/include/kapi/memory/address.hpp8
-rw-r--r--kernel/kstd/print.cpp137
-rw-r--r--libs/kstd/include/kstd/bits/format_args.hpp62
-rw-r--r--libs/kstd/include/kstd/bits/format_context.hpp89
-rw-r--r--libs/kstd/include/kstd/bits/format_specifiers.hpp204
-rw-r--r--libs/kstd/include/kstd/bits/format_specs.hpp104
-rw-r--r--libs/kstd/include/kstd/bits/format_string.hpp151
-rw-r--r--libs/kstd/include/kstd/bits/formatter.hpp325
-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
11 files changed, 692 insertions, 412 deletions
diff --git a/kapi/include/kapi/memory/address.hpp b/kapi/include/kapi/memory/address.hpp
index 587aeac..13bdf4c 100644
--- a/kapi/include/kapi/memory/address.hpp
+++ b/kapi/include/kapi/memory/address.hpp
@@ -229,13 +229,13 @@ namespace kstd
{
constexpr auto static suffix = Type == kapi::memory::address_type::linear ? "%lin" : "%phy";
- constexpr auto parse(std::string_view context) -> std::string_view
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
{
auto result = formatter<std::uintptr_t>::parse(context);
- if (!this->specs.type)
+ if (!this->specifiers.type)
{
- this->specs.type = 'p';
- this->specs.alternative_form = true;
+ this->specifiers.type = 'p';
+ this->specifiers.alternative_form = true;
}
return result;
}
diff --git a/kernel/kstd/print.cpp b/kernel/kstd/print.cpp
index c7d26ba..44f80a7 100644
--- a/kernel/kstd/print.cpp
+++ b/kernel/kstd/print.cpp
@@ -70,74 +70,113 @@ 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};
+ auto parse_context = kstd::format_parse_context{format, args.size()};
- auto current = format.begin();
- auto end = format.end();
- auto next_automatic_index = 0uz;
+ auto it = parse_context.begin();
+ auto end = parse_context.end();
- while (current != end)
+ while (it != end)
{
- if (*current != '{')
+ if (*it != '{' && *it != '}')
{
- auto start = current;
- while (current != end && *current != '{')
+ auto start = it;
+ while (it != end && *it != '{' && *it != '}')
{
- std::advance(current, 1);
+ std::advance(it, 1);
}
- context.push(std::string_view(start, current - start));
+ parse_context.advance_to(it);
+ context.push(std::string_view(start, it - start));
continue;
}
- if (std::next(current) != end && *(std::next(current)) == '{')
+ if (*it == '{')
{
- context.push('{');
- std::advance(current, 2);
- continue;
- }
+ std::advance(it, 1);
+ if (it != end && *it == '{')
+ {
+ context.push('{');
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ continue;
+ }
- std::advance(current, 1);
+ parse_context.advance_to(it);
+ auto index = 0uz;
- auto index = 0uz;
- if (current != end && *current >= '0' && *current <= '9')
- {
- while (current != end && *current >= '0' && *current <= '9')
+ if (it != end && *it >= '0' && *it <= '9')
{
- index = index * 10 + static_cast<std::size_t>(*current - '0');
- std::advance(current, 1);
+ 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();
}
- }
- else
- {
- index = next_automatic_index++;
- }
- auto remaining_fmt = std::string_view{current, static_cast<std::size_t>(std::distance(current, end))};
+ if (it != end && *it == ':')
+ {
+ std::advance(it, 1);
+ }
- auto const arg = args.get(index);
- if (arg.format)
- {
- auto const after_specs = arg.format(arg.value, remaining_fmt, context);
- auto const consumed = remaining_fmt.size() - after_specs.size();
- std::advance(current, consumed);
- }
- else
- {
- context.push("{?}");
- while (current != end && *current != '}')
- std::advance(current, 1);
- }
+ parse_context.advance_to(it);
- if (current != end && *current == '}')
- {
- std::advance(current, 1);
+ if (index < args.size())
+ {
+ auto const & arg = args[index];
+ if (arg.format_function)
+ {
+ arg.format_function(arg.value_pointer, parse_context, context);
+ }
+ else
+ {
+ context.push("{?}");
+ }
+ }
+ 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
+ else if (*it == '}')
{
- context.push("{fmt-err}");
- while (current != end && *current != '}')
- std::advance(current, 1);
- if (current != end)
- std::advance(current, 1);
+ 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);
+ }
}
}
}
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..d236a23
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format_args.hpp
@@ -0,0 +1,62 @@
+#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 <array>
+#include <cstddef>
+#include <span>
+#include <type_traits>
+
+namespace kstd
+{
+ 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
+ {
+ 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... 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>}...}};
+ }
+ }
+
+} // 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..863047d 100644
--- a/libs/kstd/include/kstd/bits/format_context.hpp
+++ b/libs/kstd/include/kstd/bits/format_context.hpp
@@ -3,11 +3,100 @@
// 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)
+ : 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;
+
+ 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.");
+ }
+ }
+
+ 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:
+ };
+
struct format_context
{
using writer_function = void(void *, std::string_view);
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..8fb50ef
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format_specifiers.hpp
@@ -0,0 +1,204 @@
+#ifndef KSTD_BITS_FORMAT_SPECS_HPP
+#define KSTD_BITS_FORMAT_SPECS_HPP
+
+// IWYU pragma: private
+
+#include "kstd/bits/format_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_arg_id(argument_id);
+ }
+ else
+ {
+ argument_id = context.next_arg_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..cbdfb7d 100644
--- a/libs/kstd/include/kstd/bits/format_string.hpp
+++ b/libs/kstd/include/kstd/bits/format_string.hpp
@@ -3,127 +3,114 @@
// IWYU pragma: private, include <kstd/format>
-#include <algorithm>
+#include "kstd/bits/format_context.hpp"
+
#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;
+
+ (..., [&] {
+ 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;
+ }());
+
+ if (!found)
+ {
+ report_format_error("Argument index out of bounds.");
+ }
+ }
+ } // namespace bits
- auto current = string.begin();
- auto end = string.end();
+ 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 context = format_parse_context{str_view, sizeof...(Args)};
+ auto it = context.begin();
- while (current != end)
+ while (it != context.end())
{
- if (*current == '{')
+ if (*it == '{')
{
- if (std::next(current) != end && *std::next(current) == '{')
+ ++it;
+ if (it != context.end() && *it == '{')
{
- std::advance(current, 2);
+ ++it;
+ context.advance_to(it);
continue;
}
- std::advance(current, 1);
-
- auto index = 0uz;
- auto is_manual_index = false;
+ context.advance_to(it);
+ auto argument_id = 0uz;
- if (current != end && *current >= '0' && *current <= '9')
+ if (it != context.end() && *it >= '0' && *it <= '9')
{
- is_manual_index = true;
- while (current != end && *current >= '0' && *current <= '9')
+ while (it != context.end() && *it >= '0' && *it <= '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");
+ 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..e9a45e0 100644
--- a/libs/kstd/include/kstd/bits/formatter.hpp
+++ b/libs/kstd/include/kstd/bits/formatter.hpp
@@ -3,8 +3,9 @@
// 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_specifiers.hpp"
+
#include <kstd/os/error.hpp>
#include <array>
@@ -21,30 +22,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)
+ {
+ // TODO: implement argument references so we can read the dynamic width argument.
+ final_width = 0;
+ }
using unsigned_T = std::make_unsigned_t<T>;
auto absolute_value = static_cast<unsigned_T>(value);
@@ -55,11 +124,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 +146,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 +164,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 +192,50 @@ namespace kstd