Add format utility

This commit is contained in:
2025-08-28 14:01:43 -04:00
parent 6fb57619c5
commit bc0d5a7422
5 changed files with 1306 additions and 1 deletions

191
src/format.hpp Normal file
View 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)));