Files
fennec/include/fennec/format/formatter.h
Medusa Slockbower cf909624df - More Documentation
- Vulkan Configuration Implementations
 - Fixed build errors on GCC and Clang
2026-07-18 23:48:00 -04:00

303 lines
8.4 KiB
C++

// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 - 2026 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \file fennec/format/formatter.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///
#ifndef FENNEC_FORMAT_FORMATTER_H
#define FENNEC_FORMAT_FORMATTER_H
#include <fennec/format/charconv.h>
#include <fennec/string/string.h>
#include <fennec/format/format_arg.h>
namespace fennec
{
// base template =======================================================================================================
///
/// \brief Formatter struct, used to turn values into formatted strings
/// \tparam T The type to format
template<typename T>
struct formatter {
///
/// \brief default format function
///
/// \details Fails an assertion, the formatter is not implemented for the provided type
/// \returns empty string
string operator()(const format_arg&, const T&) const {
static_assert(false, "Formatter not implemented for the provided type.");
return string("");
}
};
// strings =============================================================================================================
// TODO: String formatting
///
/// \brief formatter of a character array
/// \tparam N the number of characters
template<size_t N>
struct formatter<char[N]> {
///
/// \brief format function
/// \param str the string argument
/// \returns the formatted version of \emph{str}
string operator()(const format_arg&, const char (&str)[N]) const {
return string(str);
}
};
///
/// \brief formatter of a character array
/// \tparam N the number of characters
template<size_t N>
struct formatter<const char[N]> {
///
/// \brief format function
/// \param str the string argument
/// \returns the formatted version of \emph{str}
string operator()(const format_arg&, const char (&str)[N]) const {
return string(str);
}
};
///
/// \brief formatter of a C-Style string
template<>
struct formatter<cstring> {
///
/// \brief format function
/// \param str the string argument
/// \returns the formatted version of \emph{str}
string operator()(const format_arg&, const cstring& str) const {
return str;
}
};
///
/// \brief formatter of a string
template<>
struct formatter<string> {
///
/// \brief format function
/// \param str the string argument
/// \returns the formatted version of \emph{str}
string operator()(const format_arg&, const string& str) const {
return str;
}
};
// decimal types =======================================================================================================
///
/// \brief formatter of integral types
template<typename IntT> requires(is_integral_v<IntT> and not is_bool_v<IntT>)
struct formatter<IntT> {
///
/// \brief format function
/// \param fmt the format specification
/// \param x the integral argument
/// \returns the formatted version of \emph{x}
string operator()(const format_arg& fmt, IntT x) const {
char digits[128] = {};
auto chk = fennec::to_chars(digits, digits + sizeof(digits), fennec::abs(x), fmt.base);
assertf(chk != nullptr, "fennec::format error, to_chars error");
const size_t len = chk - digits;
// handle uppercase
if (fmt.upper) {
for (auto& digit : digits) {
if (digit == 0) {
break;
}
digit = toupper(digit);
}
}
const bool has_sign = (x < 0 or fmt.sign != '-');
const bool zero = fmt.fill == '0';
const size_t prefix = fmt.alt ? (fmt.type == 'd' ? 0 : 2 - (fmt.type == 'o')) : 0;
const size_t sgnlen = len + (zero ? has_sign + prefix : 0);
const size_t explen = fennec::max(sgnlen, fmt.width) + (zero ? 0 : has_sign + prefix);
const size_t fill = fmt.width > sgnlen ? fmt.width - sgnlen : 0;
size_t sign = 0;
string res = string(explen);
if (fill > 0) {
switch (fmt.align) {
case '<':
memcpy(res.data() + has_sign + prefix, digits, len);
memset(res.data() + has_sign + prefix + len, fmt.fill == '0' ? ' ' : fmt.fill, fill);
break;
case '>': case '\0':
memcpy(res.data() + explen - len, digits, len);
sign = fmt.fill == '0' ? 0 : explen - len - 1 - prefix;
memset(res.data(), fmt.fill, explen - len);
break;
case '^':
const size_t bef = fill / 2 + has_sign + prefix;
const size_t aft = explen - bef;
memcpy(res.data() + bef, digits, len);
sign = fmt.fill == '0' ? 0 : bef - 1 - prefix;
memset(res.data(), fmt.fill, bef);
memset(res.data() + bef + len, fmt.fill == '0' ? ' ' : fmt.fill, aft);
break;
}
} else {
memcpy(res.data() + has_sign + prefix, digits, len);
}
if (has_sign) {
res[sign] = (x < 0) ? '-' : fmt.sign;
}
if (prefix) {
res[sign + has_sign] = '0';
if (fmt.type != 'o') {
res[sign + has_sign + 1] = fmt.type;
}
}
return res;
}
};
///
/// \brief formatter of boolean types
template<typename BoolT> requires(is_bool_v<BoolT>)
struct formatter<BoolT> {
///
/// \brief format function
/// \param fmt the format specification
/// \param x the boolean argument
/// \returns the formatted version of \emph{x}
string operator()(const format_arg& fmt, BoolT x) const {
if (fmt.type == 's' or fmt.type == '\0') {
return x ? string("true") : string("false");
}
return formatter<uint8_t>{}(fmt, static_cast<uint8_t>(x));
}
};
///
/// \brief formatter of floating point types
template<typename FloatT> requires(is_floating_point_v<FloatT>)
struct formatter<FloatT> {
///
/// \brief format function
/// \param fmt the format specification
/// \param x the float argument
/// \returns the formatted version of \emph{x}
string operator()(const format_arg& fmt, FloatT x) const {
// nan & inf cases
if (fennec::isnan(x)) {
return string("nan");
}
if (fennec::isinf(x)) {
return string("inf");
}
char digits[128] = {};
auto chk = fennec::to_chars(digits, digits + sizeof(digits), fennec::abs(x), fmt.type, fmt.precision);
assertf(chk != nullptr, "fennec::format error, to_chars error");
size_t len = chk - digits;
// handle uppercase
if (fmt.upper) {
for (auto& digit : digits) {
if (digit == 0) {
break;
}
digit = toupper(digit);
}
}
const bool has_sign = (x < 0 or fmt.sign != '-');
const bool zero = fmt.fill == '0';
const size_t prefix = fmt.alt ? 2 : 0;
const size_t sgnlen = len + (zero ? has_sign + prefix : 0);
const size_t explen = fennec::max(sgnlen, fmt.width) + (zero ? 0 : has_sign + prefix);
const size_t fill = fmt.width > sgnlen ? fmt.width - sgnlen : 0;
size_t sign = 0;
string res = string(explen);
if (fill > 0) {
switch (fmt.align) {
case '<':
memcpy(res.data() + has_sign + prefix, digits, len);
memset(res.data() + has_sign + prefix + len, fmt.fill == '0' ? ' ' : fmt.fill, fill);
break;
case '>': case '\0':
memcpy(res.data() + explen - len, digits, len);
sign = fmt.fill == '0' ? 0 : explen - len - 1 - prefix;
memset(res.data(), fmt.fill, explen - len);
break;
case '^':
const size_t bef = fill / 2 + has_sign + prefix;
const size_t aft = explen - bef;
memcpy(res.data() + bef, digits, len);
sign = fmt.fill == '0' ? 0 : bef - 1 - prefix;
memset(res.data(), fmt.fill, bef);
memset(res.data() + bef + len, fmt.fill == '0' ? ' ' : fmt.fill, aft);
break;
}
} else {
memcpy(res.data() + has_sign + prefix, digits, len);
}
if (has_sign) {
res[sign] = (x < 0) ? '-' : fmt.sign;
}
if (prefix) {
res[sign + has_sign] = '0';
res[sign + has_sign + 1] = fmt.type + ('x' - 'a');
}
return res;
}
};
}
#endif // FENNEC_FORMAT_FORMATTER_H