aboutsummaryrefslogtreecommitdiff
path: root/libs/kstd/kstd/bits/format/string.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/kstd/kstd/bits/format/string.hpp')
-rw-r--r--libs/kstd/kstd/bits/format/string.hpp148
1 files changed, 148 insertions, 0 deletions
diff --git a/libs/kstd/kstd/bits/format/string.hpp b/libs/kstd/kstd/bits/format/string.hpp
new file mode 100644
index 0000000..e7e4088
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/string.hpp
@@ -0,0 +1,148 @@
+#ifndef KSTD_BITS_FORMAT_STRING_HPP
+#define KSTD_BITS_FORMAT_STRING_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <array>
+#include <cstddef>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ template<typename... Args>
+ constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void
+ {
+ auto found = false;
+ auto current_index = 0uz;
+
+ (..., [&] {
+ if (current_index == target_index && !found)
+ {
+ using decay_type = std::remove_cvref_t<Args>;
+ static_assert(std::is_default_constructible_v<formatter<decay_type>>, "Missing formatter specialization.");
+ auto fmt = formatter<decay_type>{};
+
+ auto it = fmt.parse(context);
+ context.advance_to(it);
+ found = true;
+ }
+ ++current_index;
+ }());
+
+ if (!found)
+ {
+ 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)
+ {
+ error("Dynamic width argument must be an integral object.");
+ }
+ }
+ } // namespace bits::format
+
+ template<typename... Args>
+ struct format_string
+ {
+ template<std::size_t Size>
+ consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT
+ : str_view{str}
+ {
+ using namespace bits::format;
+
+ auto const is_width_compatible =
+ std::array<bool, (sizeof...(Args) > 0 ? sizeof...(Args) : 1)>{is_width_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())
+ {
+ if (*it == '{')
+ {
+ ++it;
+ if (it != context.end() && *it == '{')
+ {
+ ++it;
+ context.advance_to(it);
+ continue;
+ }
+
+ context.advance_to(it);
+ auto argument_id = 0uz;
+
+ if (it != context.end() && *it >= '0' && *it <= '9')
+ {
+ while (it != context.end() && *it >= '0' && *it <= '9')
+ {
+ argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
+ ++it;
+ }
+ context.check_arg_id(argument_id);
+ context.advance_to(it);
+ }
+ else
+ {
+ argument_id = context.next_arg_id();
+ }
+
+ if (it != context.end() && *it == ':')
+ {
+ ++it;
+ context.advance_to(it);
+ }
+
+ validate_argument<Args...>(argument_id, context);
+
+ it = context.begin();
+ if (it == context.end() || *it != '}')
+ {
+ bits::format::error("Missing closing '}' in format string.");
+ }
+ }
+ else if (*it == '}')
+ {
+ ++it;
+ if (it != context.end() && *it == '}')
+ {
+ bits::format::error("Unescaped '}' in format string.");
+ }
+ }
+ ++it;
+ context.advance_to(it);
+ }
+ }
+
+ std::string_view str_view;
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file