- 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

@@ -247,9 +247,13 @@ add_library(fennec STATIC
# format ===============================================================================================================
include/fennec/format/format.h
include/fennec/format/detail/_format.h
include/fennec/format/format_arg.h
include/fennec/format/formatter.h
include/fennec/format/charconv.h
include/fennec/format/detail/_format.h
source/format/charconv.cpp

View File

@@ -49,9 +49,11 @@ Some main areas where the engine strays from the GNU standard includes the follo
* [Section 4.7, Standards for Graphical Interfaces](https://www.gnu.org/prep/standards/html_node/Graphical-Interfaces.html).
fennec provides an implementation for X11, however it does not use the GTK toolkit.
- [Section 6.1, GNU Manuals](https://www.gnu.org/prep/standards/html_node/GNU-Manuals.html)
- [Section 5.1, Formatting Your Source Code](https://www.gnu.org/prep/standards/html_node/index.html).
fennec uses Allman (BSD) for namespaces, otherwise K&R.
* [Section 6.1, GNU Manuals](https://www.gnu.org/prep/standards/html_node/GNU-Manuals.html)
fennec does not use Texinfo and instead uses Doxygen. Otherwise, it follows the other standards of this section.
* [Section 7, The Release Process](https://www.gnu.org/prep/standards/html_node/Managing-Releases.html)
- [Section 7, The Release Process](https://www.gnu.org/prep/standards/html_node/Managing-Releases.html)
fennec follows most of the conventions in this section, however the build system used is CMake and not
Makefile. CMake, although overwhelming at first, is much more friendly to those who are learning build systems for the first time.

View File

@@ -27,4 +27,5 @@ fennec_add_definitions(
FENNEC_COMPILER_GCC=1
FENNEC_NO_INLINE=[[gnu::noinline]]
FENNEC_FUNCTION_NAME=__PRETTY_FUNCTION__
RYU_ONLY_64_BIT_OPS
)

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;
}
}

147
source/format/charconv.cpp Normal file
View File

@@ -0,0 +1,147 @@
// =====================================================================================================================
// 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))
///
///
#define __cpp_lib_to_chars
#include <charconv>
#include <system_error>
#include <fennec/format/charconv.h>
#include <fennec/lang/assert.h>
namespace fennec
{
char* to_chars(char* first, char* last, char x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, signed char x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, unsigned char x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, signed short x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, unsigned short x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, signed int x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, unsigned int x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, signed long x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, unsigned long x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, signed long long x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, unsigned long long x, int base) {
auto res = std::to_chars(first, last, x, base);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
std::chars_format ctofmt(char c) {
switch (c) {
default: return std::chars_format::general;
case 'a': case 'A': return std::chars_format::hex;
case 'f': case 'F': return std::chars_format::fixed;
case 'e': case 'E': return std::chars_format::scientific;
}
}
char* to_chars(char* first, char* last, float x) {
auto res = std::to_chars(first, last, x);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, float x, char fmt) {
auto res = std::to_chars(first, last, x, ctofmt(fmt));
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, float x, char fmt, int precision) {
auto res = std::to_chars(first, last, x, ctofmt(fmt), precision);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, double x) {
auto res = std::to_chars(first, last, x);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, double x, char fmt) {
auto res = std::to_chars(first, last, x, ctofmt(fmt));
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
char* to_chars(char* first, char* last, double x, char fmt, int precision) {
auto res = std::to_chars(first, last, x, ctofmt(fmt), precision);
assertf(res.ec == std::errc(), std::make_error_code(res.ec).message().c_str());
return res.ptr;
}
}

View File

@@ -72,6 +72,15 @@ inline void fennec_test_format() {
fennec_test_spacer(1);
fennec_test_run(fennec::format("{:#06d}", -15), string("-00015"));
fennec_test_run(fennec::format("{:#06x}", -15), string("-0x00f"));
fennec_test_run(fennec::format("{:#06X}", -15), string("-0X00F"));
fennec_test_run(fennec::format("{:#06o}", -15), string("-00017"));
fennec_test_run(fennec::format("{:#06b}", -15), string("-0b1111"));
fennec_test_run(fennec::format("{:#06B}", -15), string("-0B1111"));
fennec_test_spacer(1);
fennec_test_run(fennec::format("{},{}", true, false), string("true,false"));
fennec_test_run(fennec::format("{:#06b},{:#06b}", true, false), string("0b0001,0b0000"));
}