From 9645856554d4a9727dc47da77395a26587a2a50a Mon Sep 17 00:00:00 2001 From: Medusa Slockbower Date: Sat, 6 Dec 2025 02:19:52 -0500 Subject: [PATCH] - Refactored fennec::format to use to_chars for ints. floats TODO but will also use to_chars. --- CMakeLists.txt | 6 +- README.md | 6 +- cmake/gcc.cmake | 1 + include/fennec/format/charconv.h | 67 +++++++++++ include/fennec/format/format.h | 70 +++++++++++- include/fennec/format/format_arg.h | 5 +- include/fennec/format/formatter.h | 174 ++++++++++++----------------- include/fennec/memory/allocator.h | 5 +- source/format/charconv.cpp | 147 ++++++++++++++++++++++++ test/tests/test_format.h | 9 ++ 10 files changed, 378 insertions(+), 112 deletions(-) create mode 100644 include/fennec/format/charconv.h create mode 100644 source/format/charconv.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7497d86..6e4ada3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/README.md b/README.md index 22cffb3..65ca483 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/cmake/gcc.cmake b/cmake/gcc.cmake index 9ef7621..4008a70 100644 --- a/cmake/gcc.cmake +++ b/cmake/gcc.cmake @@ -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 ) diff --git a/include/fennec/format/charconv.h b/include/fennec/format/charconv.h new file mode 100644 index 0000000..fc6f2f3 --- /dev/null +++ b/include/fennec/format/charconv.h @@ -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 . +// ===================================================================================================================== + +/// +/// \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 \ No newline at end of file diff --git a/include/fennec/format/format.h b/include/fennec/format/format.h index 5c1ba49..5451173 100644 --- a/include/fennec/format/format.h +++ b/include/fennec/format/format.h @@ -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; diff --git a/include/fennec/format/format_arg.h b/include/fennec/format/format_arg.h index ed864d6..0c92a68 100644 --- a/include/fennec/format/format_arg.h +++ b/include/fennec/format/format_arg.h @@ -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; }; } diff --git a/include/fennec/format/formatter.h b/include/fennec/format/formatter.h index 5226cc2..b93a215 100644 --- a/include/fennec/format/formatter.h +++ b/include/fennec/format/formatter.h @@ -31,6 +31,7 @@ #ifndef FENNEC_FORMAT_FORMATTER_H #define FENNEC_FORMAT_FORMATTER_H +#include #include #include @@ -80,115 +81,66 @@ struct formatter { template requires(is_integral_v and not is_bool_v) struct formatter { 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 { } }; +template requires(is_floating_point_v) +struct formatter { + 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 \ No newline at end of file diff --git a/include/fennec/memory/allocator.h b/include/fennec/memory/allocator.h index 1beaa5e..890d86d 100644 --- a/include/fennec/memory/allocator.h +++ b/include/fennec/memory/allocator.h @@ -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; + } } diff --git a/source/format/charconv.cpp b/source/format/charconv.cpp new file mode 100644 index 0000000..0ae0525 --- /dev/null +++ b/source/format/charconv.cpp @@ -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 . +// ===================================================================================================================== + +/// +/// \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 +#include +#include +#include + +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; +} + +} diff --git a/test/tests/test_format.h b/test/tests/test_format.h index bf30f6c..5890d41 100644 --- a/test/tests/test_format.h +++ b/test/tests/test_format.h @@ -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")); }