Add format utility
This commit is contained in:
191
src/format.hpp
Normal file
191
src/format.hpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "arena_allocator.hpp"
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <int kLen> struct StringTerm {
|
||||
explicit constexpr StringTerm(const char *s) : s(s) {}
|
||||
static constexpr int kMaxLength = kLen;
|
||||
void write(char *&buf) const {
|
||||
std::memcpy(buf, s, kLen);
|
||||
buf += kLen;
|
||||
}
|
||||
|
||||
private:
|
||||
const char *s;
|
||||
};
|
||||
template <int kLen>
|
||||
constexpr StringTerm<kLen - 1> term(const char (&array)[kLen]) {
|
||||
return StringTerm<kLen - 1>{array};
|
||||
}
|
||||
|
||||
template <class IntType> constexpr int decimal_length(IntType x) {
|
||||
static_assert(std::is_integral_v<IntType>,
|
||||
"decimal_length requires integral type");
|
||||
|
||||
if constexpr (std::is_signed_v<IntType>) {
|
||||
// Handle negative values by using unsigned equivalent
|
||||
using Unsigned = std::make_unsigned_t<IntType>;
|
||||
auto abs_x =
|
||||
static_cast<Unsigned>(x < 0 ? ~static_cast<Unsigned>(x) + 1 : x);
|
||||
int result = 0;
|
||||
do {
|
||||
++result;
|
||||
abs_x /= 10;
|
||||
} while (abs_x);
|
||||
return result;
|
||||
} else {
|
||||
int result = 0;
|
||||
do {
|
||||
++result;
|
||||
x /= 10;
|
||||
} while (x);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <std::integral IntType> struct IntTerm {
|
||||
static constexpr bool kSigned = std::is_signed_v<IntType>;
|
||||
using Unsigned = std::make_unsigned_t<IntType>;
|
||||
|
||||
explicit constexpr IntTerm(IntType v) : v(v) {}
|
||||
|
||||
static constexpr int kMaxLength =
|
||||
decimal_length(Unsigned(-1)) + (kSigned ? 1 : 0);
|
||||
|
||||
void write(char *&buf) const {
|
||||
char itoa_buf[kMaxLength];
|
||||
Unsigned x = static_cast<Unsigned>(v);
|
||||
|
||||
if constexpr (kSigned) {
|
||||
if (v < 0) {
|
||||
*buf++ = '-';
|
||||
x = ~x + 1;
|
||||
}
|
||||
}
|
||||
|
||||
int i = kMaxLength;
|
||||
do {
|
||||
itoa_buf[--i] = static_cast<char>('0' + (x % 10));
|
||||
x /= 10;
|
||||
} while (x);
|
||||
|
||||
while (i < kMaxLength) {
|
||||
*buf++ = itoa_buf[i++];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
IntType v;
|
||||
};
|
||||
|
||||
template <std::integral IntType> constexpr IntTerm<IntType> term(IntType s) {
|
||||
return IntTerm<IntType>{s};
|
||||
}
|
||||
|
||||
// Template specializations for common integer types for faster compilation
|
||||
template <> struct IntTerm<int> {
|
||||
static constexpr bool kSigned = true;
|
||||
static constexpr int kMaxLength = 11; // -2147483648 = 11 chars
|
||||
|
||||
explicit constexpr IntTerm(int v) : v(v) {}
|
||||
|
||||
void write(char *&buf) const {
|
||||
if (v < 0) {
|
||||
*buf++ = '-';
|
||||
write_unsigned(
|
||||
buf, static_cast<unsigned int>(~static_cast<unsigned int>(v) + 1));
|
||||
} else {
|
||||
write_unsigned(buf, static_cast<unsigned int>(v));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int v;
|
||||
|
||||
static void write_unsigned(char *&buf, unsigned int x) {
|
||||
char digits[10];
|
||||
int i = 0;
|
||||
do {
|
||||
digits[i++] = static_cast<char>('0' + (x % 10));
|
||||
x /= 10;
|
||||
} while (x);
|
||||
while (i > 0) {
|
||||
*buf++ = digits[--i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct IntTerm<int64_t> {
|
||||
static constexpr bool kSigned = true;
|
||||
static constexpr int kMaxLength = 20; // -9223372036854775808 = 20 chars
|
||||
|
||||
explicit constexpr IntTerm(int64_t v) : v(v) {}
|
||||
|
||||
void write(char *&buf) const {
|
||||
if (v < 0) {
|
||||
*buf++ = '-';
|
||||
write_unsigned(buf, static_cast<uint64_t>(~static_cast<uint64_t>(v) + 1));
|
||||
} else {
|
||||
write_unsigned(buf, static_cast<uint64_t>(v));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t v;
|
||||
|
||||
static void write_unsigned(char *&buf, uint64_t x) {
|
||||
char digits[19];
|
||||
int i = 0;
|
||||
do {
|
||||
digits[i++] = static_cast<char>('0' + (x % 10));
|
||||
x /= 10;
|
||||
} while (x);
|
||||
while (i > 0) {
|
||||
*buf++ = digits[--i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DoubleTerm {
|
||||
explicit constexpr DoubleTerm(double s) : s(s) {}
|
||||
static constexpr int kMaxLength = 24;
|
||||
void write(char *&buf) const;
|
||||
|
||||
private:
|
||||
double s;
|
||||
};
|
||||
|
||||
// Variable template for compile-time max length access
|
||||
template <typename T>
|
||||
inline constexpr int max_decimal_length_v = decltype(term(T{}))::kMaxLength;
|
||||
|
||||
inline constexpr DoubleTerm term(double s) { return DoubleTerm(s); }
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class... Ts>
|
||||
std::string_view static_format(ArenaAllocator &arena, Ts &&...ts) {
|
||||
constexpr int upper_bound =
|
||||
(decltype(detail::term(ts))::kMaxLength + ...) + 1;
|
||||
char *result = arena.allocate<char>(upper_bound);
|
||||
char *buf = result;
|
||||
(detail::term(ts).write(buf), ...);
|
||||
const int size = static_cast<int>(buf - result);
|
||||
return std::string_view(
|
||||
arena.realloc(result, upper_bound, upper_bound - size),
|
||||
static_cast<std::size_t>(size));
|
||||
}
|
||||
|
||||
// Runtime formatting function using C-style varargs
|
||||
// For convenience in non-performance-critical code paths
|
||||
std::string_view format(ArenaAllocator &arena, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
Reference in New Issue
Block a user