- Refactored fennec::format to use to_chars for ints. floats TODO but will also use to_chars.

This commit is contained in:
2025-12-06 02:19:52 -05:00
parent a2abb58705
commit 9645856554
10 changed files with 378 additions and 112 deletions

View File

@@ -0,0 +1,67 @@
// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 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 charconv.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///
#ifndef FENNEC_FORMAT_CHARCONV_H
#define FENNEC_FORMAT_CHARCONV_H
namespace fennec
{
char* to_chars(char* first, char* last, char x, int base);
char* to_chars(char* first, char* last, signed char x, int base);
char* to_chars(char* first, char* last, unsigned char x, int base);
char* to_chars(char* first, char* last, signed short x, int base);
char* to_chars(char* first, char* last, unsigned short x, int base);
char* to_chars(char* first, char* last, signed int x, int base);
char* to_chars(char* first, char* last, unsigned int x, int base);
char* to_chars(char* first, char* last, signed long x, int base);
char* to_chars(char* first, char* last, unsigned long x, int base);
char* to_chars(char* first, char* last, signed long long x, int base);
char* to_chars(char* first, char* last, unsigned long long x, int base);
char* to_chars(char* first, char* last, signed long long x, int base);
char* to_chars(char* first, char* last, unsigned long long x, int base);
char* to_chars(char* first, char* last, float x);
char* to_chars(char* first, char* last, float x, char fmt);
char* to_chars(char* first, char* last, float x, char fmt, int precision);
char* to_chars(char* first, char* last, double x);
char* to_chars(char* first, char* last, double x, char fmt);
char* to_chars(char* first, char* last, double x, char fmt, int precision);
}
#endif // FENNEC_FORMAT_CHARCONV_H

View File

@@ -45,10 +45,12 @@ string format(const cstring& str, ArgsT&&...args) {
static constexpr format_arg default_fmt = {
.fill = ' ',
.align = '\0', // default to locale
.sign = '\0', // default to sign only for negative numbers
.sign = '\0', // default to sign only for negative numbers, gets handled later in code
.alt = false, // default no prefix
.upper = false,
.width = 0,
.precision = 6, // default to 6 sigfigs
.base = 10,
.type = '\0',
};
@@ -119,6 +121,7 @@ string format(const cstring& str, ArgsT&&...args) {
// early return case for no colon
if (colon > end) {
fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
res += argarray.format(arg, fmt);
i = end + 1;
continue;
@@ -146,12 +149,72 @@ string format(const cstring& str, ArgsT&&...args) {
assert(spec < str.length() - 1 and str[spec+1] == '}', "fennec::format syntax error, mismatched '{}'");
// check type
if (detail::_isfmt_t(str[spec])) {
fmt.type = str[spec--];
switch (str[spec]) {
default: break;
case 's': case '?': // strings
case 'c': // char
fmt.type = str[spec--];
break;
case 'd': // decimal
fmt.base = 10;
fmt.type = str[spec--];
break;
case 'B': // binary
fmt.upper = true; [[fallthrough]];
case 'b':
fmt.base = 2;
fmt.type = str[spec--];
break;
case 'o': // octal
fmt.base = 8;
fmt.type = str[spec--];
break;
case 'X': // hex
fmt.upper = true; [[fallthrough]];
case 'x':
fmt.base = 16;
fmt.type = str[spec--];
break;
case 'A':
fmt.upper = true; [[fallthrough]];
case 'a': // float hex
fmt.base = 16;
fmt.type = str[spec--];
break;
case 'E': // scientific notation
fmt.upper = true; [[fallthrough]];
case 'e':
fmt.base = 16;
fmt.type = str[spec--];
break;
case 'F': // fixed precision
fmt.upper = true; [[fallthrough]];
case 'f':
fmt.base = 10;
fmt.type = str[spec--];
break;
case 'G': // general precision
fmt.upper = true; [[fallthrough]];
case 'g':
fmt.base = 10;
fmt.type = str[spec--];
break;
}
// early return
if (spec == colon) {
fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
res += argarray.format(arg, fmt);
i = end + 1;
continue;
@@ -218,6 +281,7 @@ string format(const cstring& str, ArgsT&&...args) {
// early return
if (spec == colon) {
fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
res += argarray.format(arg, fmt);
i = end + 1;
continue;

View File

@@ -39,9 +39,10 @@ namespace fennec
struct format_arg {
char fill;
char align, sign;
bool alt;
bool alt, upper;
size_t width, precision;
char type;
size_t base;
char type;
};
}

View File

@@ -31,6 +31,7 @@
#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>
@@ -80,115 +81,66 @@ struct formatter<string> {
template<typename IntT> requires(is_integral_v<IntT> and not is_bool_v<IntT>)
struct formatter<IntT> {
string operator()(const format_arg& fmt, IntT x) {
static constexpr char lowdigits[] = "0123456789abcdef";
static constexpr char highdigits[] = "0123456789ABCDEF";
bool neg = x < 0; x = fennec::abs(x);
const char* digits = lowdigits;
string res;
string pre;
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");
size_t len = chk - digits;
size_t base;
switch (fmt.type) {
// decimal
default:
assertf(false, "invalid format type for integral value");
[[fallthrough]];
case '\0': case 'd':
base = 10;
break;
// binary
case 'B':
pre = "0B";
base = 2;
break;
case 'b':
pre = "0b";
base = 2;
break;
// octal
case 'o':
pre = "0";
base = 8;
break;
// hex
case 'X':
digits = highdigits;
pre = "0X";
base = 16;
break;
case 'x':
pre = "0x";
base = 16;
break;
}
// parse int
while (x != 0) {
res = digits[x % base] + res;
x /= base;
}
// handle 0
if (res.empty()) {
res = '0' + res;
}
// add prefix unless fill is '0'
if (fmt.alt and fmt.fill != '0' and (base != 8 or res[0] != '0')) {
res = pre + res;
}
// fill
size_t fill = fmt.alt ? fmt.width - pre.length() : fmt.width;
switch (fmt.align) {
// align left
case '<':
while (res.size() < fill) {
res += fmt.fill == '0' ? ' ' : fmt.fill;
// handle uppercase
if (fmt.upper) {
for (auto& digit : digits) {
if (digit == 0) {
break;
}
break;
// align right
case '>': default:
while (res.size() < fill) {
res = fmt.fill + res;
}
break;
// align
case '^':
bool rr = true;
while (res.size() < fill) {
if (rr) {
res += fmt.fill == '0' ? ' ' : fmt.fill;
} else {
res = fmt.fill + res;
}
rr = !rr;
}
break;
digit = toupper(digit);
}
}
// add prefix after filled 0s
if (fmt.alt and fmt.fill == '0') {
res = pre + res;
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 '^':
size_t bef = fill / 2 + has_sign + prefix;
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);
}
// add sign
switch (fmt.sign) {
case '+':
res = (neg ? '-' : '+') + res;
break;
case ' ':
res = (neg ? '-' : ' ') + res;
break;
case '-': default:
if (neg) res = '-' + res;
break;
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;
@@ -206,6 +158,22 @@ struct formatter<BoolT> {
}
};
template<typename FloatT> requires(is_floating_point_v<FloatT>)
struct formatter<FloatT> {
string operator()(const format_arg& fmt, FloatT x) {
// nan & inf cases
if (isnan(x)) {
return string("nan");
}
if (isinf(x)) {
return string("inf");
}
}
};
}
#endif // FENNEC_FORMAT_FORMATTER_H

View File

@@ -440,7 +440,10 @@ public:
///
/// \brief Default Destructor, releases the memory block if still present
constexpr ~allocation() noexcept {
if (_data) _alloc.deallocate(_data);
if (_data) {
_alloc.deallocate(_data);
_data = nullptr;
}
}