38 Commits

Author SHA1 Message Date
74c4a4d582 Merge pull request 'feature/int_hex' (#6) from feature/int_hex into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/6
2022-02-15 10:35:17 +00:00
2933676ea2 Fixed bug in hex formatting, wrote tests for hex int formatting 2022-02-15 11:34:49 +01:00
7115e09aca Implemented count_digits for hex and started implementing digits2_base 2022-02-15 11:13:15 +01:00
f9099ce6ee Added tests for count_digits_base, decimal, binary and hex 2022-02-15 11:12:19 +01:00
4c3024e978 Merge pull request 'Reintroduced previously erroneously removed memcpy' (#5) from fix/memcpy into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/5
2022-02-15 09:29:55 +00:00
a921676b77 Reintroduced previously erroneously removed memcpy 2022-02-15 10:29:23 +01:00
3b9d9775bf Merge pull request 'Replaced <cstdint> with <stdint.h>' (#4) from fix/stdint into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/4
2022-02-15 09:17:58 +00:00
39c1636858 Replaced <cstdint> with <stdint.h> 2022-02-15 10:01:55 +01:00
fc080a8ba0 Merge pull request 'Removed memcpy and <cstring>' (#3) from feature/no_cstring into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/3
2022-02-15 08:59:58 +00:00
e4ebd0163b Removed memcpy and <cstring> 2022-02-15 09:58:47 +01:00
1d2dab93bd Merge pull request 'feature/int_binary' (#2) from feature/int_float_binary into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/2
2022-02-15 08:50:35 +00:00
5e4f242796 Renamed functions dedicated to decimal to better reflect their function 2022-02-15 09:47:28 +01:00
fa0fef37db Rewrote format_impl.h functions to work with both decimal and binary 2022-02-15 09:37:41 +01:00
c3eb1e2909 Wrote tests for binary int formatting 2022-02-15 09:35:36 +01:00
313ca5e981 Merge pull request 'Added static_assert to check for string validity' (#1) from feature/compile_time_string_validity into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/1
2022-02-14 22:25:16 +00:00
8241045158 Added static_assert to check for string validity 2022-02-14 22:36:43 +01:00
6666a25562 Added license 2022-02-13 23:48:17 +01:00
008c71381c Changed example to show float formatting 2022-02-13 23:43:38 +01:00
9c22b2a084 Added test cases for floats 2022-02-13 23:40:16 +01:00
9bf3e63058 Implemented format_float in terms of format_int 2022-02-13 23:12:25 +01:00
4666b4e737 Added proper error handling for too long ints 2022-02-13 21:57:43 +01:00
1338888e0b Now deciding at compile time how to format minus sign 2022-02-13 21:52:52 +01:00
f47a23ffef PreZero padding now being handled by preprocessing 2022-02-13 20:47:41 +01:00
4128ede5db English 2022-02-13 19:43:22 +01:00
bb5f4d5272 Renamed a bunch of identifiers to make sure there are no clashes with software this might be used in (fmt -> const_fmt) 2022-02-13 17:56:09 +01:00
3db91aebda Proper example 2022-02-13 17:53:17 +01:00
2ecc001a64 Put everything into a new 'const_fmt' namespace; Renamed namespace detail to const_fmt_detail 2022-02-13 17:48:24 +01:00
70fd273a70 Merge branch 'master' of http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt 2022-02-13 17:41:17 +01:00
9626f5efc4 Added examples folder and CMakeLists.txt in repo root directory 2022-02-13 17:40:33 +01:00
9c5a73e2bd Removed Logger.h 2022-02-13 17:30:40 +01:00
50635a8e2f Removed src directory 2022-02-13 17:29:55 +01:00
f9cbfdd018 Fixed typos in readme 2022-02-10 21:17:41 +00:00
55ff861f67 Renamed inc directory to const_fmt 2022-02-09 17:51:37 +01:00
d26cf43b32 Added note about including library in project 2022-02-09 16:48:02 +00:00
c9e845b025 Added limitations to README 2022-02-09 16:45:04 +00:00
6597a3a360 Added instruction to build and run the tests 2022-02-09 16:36:11 +00:00
5bf165463c Created README 2022-02-09 16:31:45 +00:00
83f7f21635 Added build folder to gitignore 2022-02-09 17:21:46 +01:00
22 changed files with 747 additions and 393 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
build
cmake-build-debug
cmake-build-release
.idea

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.21)
project(logger)
project(const_fmt)
set(CMAKE_CXX_STANDARD 20)
@@ -9,16 +9,10 @@ if(NOT CMAKE_BUILD_TYPE)
endif()
include_directories(inc)
add_executable(logger src/main.cpp)
include_directories(.)
if(MSVC)
target_compile_options(logger PRIVATE /W4 /WX)
else()
target_compile_options(logger PRIVATE -O3 -Wall -Wextra -pedantic -fno-exceptions)
endif()
add_subdirectory(examples)
option(PACKAGE_TESTS "Build the tests" ON)

19
LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright 2022 Andreas Tsouchlos
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

55
README.md Normal file
View File

@@ -0,0 +1,55 @@
# const_fmt
An extremely lightweight library, intentionally resembling `fmtlib` as much as possible. This is achieved by moving
as much of the formatting process as possible to compile time.
Meant for systems with very few resources, such as embedded systems.
## Overview
During compile-time, the string to be formatted is preprocessed to the point only the actual values to be formatted
have to be written (If they are not available at compile time).
For example `One number: {:03}; And another one: {:05.3}` is preprocessed into `One number: 000; And another one: 00.000`.
This is returned as a `std::array<char, N>`, where `N` is automatically evaluated. The only code executed at compile
time then formats the numbers and writes them into their place in the array.
Disclaimer: The actual formatting code is largely shamelessly stolen from `fmtlib`.
## Including in a project
In order to keep it as lightweight and optimizable as possible, `const_fmt` is implemented as a header-only
library.
This means that using it in a project is as simple as cloning this repo (or e.g. adding it as a submodule)
and adding the repository root folder to the compiler include directories.
## Building and running the tests
1. Initialize the `googletest` framework submodule
```bash
$ git submodule update --init
```
2. Create the build directory
```bash
$ cmake -B build -S .
```
3. Build the project
```bash
$ cmake --build build/
```
4. Run the tests
```bash
$ ctest --test-dir build/
```
## Limitations
Only a relatively limited subset of the `fmtlib` syntax is recognized (for now anyway). In particular,
there is no support for positional arguments, alignment, chrono format specs and custom const_format specifications.
By nature of the library design, which forces compile-time preprocessing of the const_format string, no dynamic width or
dynamic precision can be implemented.

View File

@@ -2,14 +2,12 @@
#define LOGGER_FORMAT_H
#include <cstring>
#include "format_impl.h"
#include "parse.h"
#include "utility.h"
#include "format_impl.h"
namespace detail {
namespace const_fmt { namespace const_fmt_detail {
/*
@@ -22,7 +20,7 @@ namespace detail {
template <ConstString s>
constexpr inline int get_output_len() {
constexpr auto parse_result = parse_string<s>();
static_assert(parse_result.is_valid, "Syntax error in format string");
static_assert(parse_result.is_valid, "Syntax error in const_format string");
return get_ast_output_len<parse_result.value>();
}
@@ -42,16 +40,16 @@ constexpr inline void check_fmt_params() {
template <fmt_data_t fmt_data, std::integral arg_t>
constexpr inline void format_arg(char* dest, arg_t arg) {
detail::format_int(dest, arg, fmt_data);
const_fmt_detail::format_int<fmt_data>(dest, arg);
};
template <fmt_data_t fmt_data, std::floating_point arg_t>
constexpr inline void format_arg(char* dest, arg_t arg) {
//detail::format_float(dest, arg, fmt_data);
constexpr inline void format_arg(char* dest, arg_t arg){
const_fmt_detail::format_float<fmt_data>(dest, arg);
};
// TODO: Error handling
template<fmt_data_t fmt_data>
template <fmt_data_t fmt_data>
constexpr inline void format_arg(char* dest, const char* arg) {
const std::size_t len = const_strlen(arg);
if (len > fmt_data.length) return;
@@ -74,30 +72,33 @@ constexpr inline void format_args(char*) {
}
template <auto fmt_data_array, typename first_arg_t, typename... args_t>
constexpr inline void format_args(char* dest, first_arg_t first_arg, args_t... args) {
constexpr inline void format_args(char* dest, first_arg_t first_arg,
args_t... args) {
format_arg<fmt_data_array[0]>(dest + fmt_data_array[0].position, first_arg);
format_args<drop_first(fmt_data_array)>(dest, args...);
}
template <auto ast>
consteval inline std::array<char, get_ast_output_len<ast>()> get_preproc_string() {
auto result = get_init_array<get_ast_output_len<ast>()>('f');
consteval inline auto get_preproc_string() {
std::array<char, get_ast_output_len<ast>()> result;
int i = 0;
for (const auto& ast_node : ast) {
if (ast_node.is_char())
if (ast_node.is_char()) {
result[i++] = ast_node.get_char();
else
i += ast_node.get_node().length;
} else {
for (int j = 0; j < ast_node.get_node().length; ++j)
result[i++] = ast_node.get_node().has_zero_padding ? '0' : ' ';
}
}
return result;
}
} // namespace detail
}} // namespace const_fmt::const_fmt_detail
/*
@@ -107,32 +108,40 @@ consteval inline std::array<char, get_ast_output_len<ast>()> get_preproc_string(
*/
template <detail::ConstString s, typename... args_t>
constexpr inline auto format(args_t... args) {
constexpr auto ast = detail::parse_string<s>().value;
constexpr auto fmt_data = detail::get_fmt_data<ast>();
namespace const_fmt {
auto result = detail::get_preproc_string<ast>();
detail::format_args<fmt_data>(result.begin(), args...);
template <const_fmt_detail::ConstString s, typename... args_t>
constexpr inline auto const_format(args_t... args) {
constexpr auto ast = const_fmt_detail::parse_string<s>();
constexpr auto fmt_data = const_fmt_detail::get_fmt_data<ast.value>();
static_assert(ast.is_valid, "Invalid format string");
auto result = const_fmt_detail::get_preproc_string<ast.value>();
const_fmt_detail::format_args<fmt_data>(result.begin(), args...);
return result;
}
template<detail::ConstString t_s>
class fmt_literal_obj_t {
public:
template<typename... args_t>
constexpr auto operator()(args_t... args) {
return format<t_s>(args...);
}
template <const_fmt_detail::ConstString t_s>
class const_fmt_literal_obj_t {
public:
template <typename... args_t>
constexpr auto operator()(args_t... args) {
return const_format<t_s>(args...);
}
};
template <detail::ConstString t_s>
constexpr auto operator""_fmt() {
return fmt_literal_obj_t<t_s>{};
template <const_fmt_detail::ConstString t_s>
constexpr auto operator""_const_fmt() {
return const_fmt_literal_obj_t<t_s>{};
}
} // namespace const_fmt
#endif // LOGGER_FORMAT_H

270
const_fmt/format_impl.h Normal file
View File

@@ -0,0 +1,270 @@
#ifndef LOGGER_FORMAT_IMPL_H
#define LOGGER_FORMAT_IMPL_H
/*
*
* ****************************************************************
* Disclaimer: Most of this code is shamelessly stolen from fmtlib
* ****************************************************************
*
*/
#include <stdint.h>
#include "utility.h"
namespace const_fmt { namespace const_fmt_detail {
/*
*
* Utility functions
*
*/
#define FMT_POWERS_OF_10(factor) \
factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
(factor)*1000000, (factor)*10000000, (factor)*100000000, \
(factor)*1000000000
template <typename T>
constexpr int count_digits_decimal_fallback(T n) {
int count = 1;
for (;;) {
if (n < 10) return count;
if (n < 100) return count + 1;
if (n < 1000) return count + 2;
if (n < 10000) return count + 3;
n /= 10000u;
count += 4;
}
}
inline int do_count_digits_decimal(uint64_t n) {
// Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
static constexpr uint8_t bsr2log10[] = {
1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
auto t = bsr2log10[__builtin_clzll(n | 1) ^ 63];
static constexpr const uint64_t zero_or_powers_of_10[] = {
0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
return t - (n < zero_or_powers_of_10[t]);
}
template <FormatType t_format_type>
constexpr inline auto count_digits_base(uint64_t n) -> int {
if constexpr (t_format_type == FormatType::b) {
int result = 0;
while (n) {
n = n >> 1;
result += 1;
}
return result;
} else {
if constexpr (t_format_type == FormatType::x) {
int result = 0;
while (n) {
n = n >> 4;
result += 1;
}
return (result + count_digits_base<FormatType::b>(n));
} else {
if (!std::is_constant_evaluated()) {
return do_count_digits_decimal(n);
}
return count_digits_decimal_fallback(n);
}
}
}
// Converts value in the range [0, base^2) to a string.
template <FormatType t_format_type>
constexpr inline const char* digits2_base(size_t value) {
// GCC generates slightly better code when value is pointer-size.
if constexpr (t_format_type == FormatType::b) {
return &"00011011"[value * 2];
} else {
if constexpr (t_format_type == FormatType::x) {
// clang-format off
return &"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"
"202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F"
"404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F"
"606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F"
"808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F"
"A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"
"C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"
"E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"[value * 2];
// clang-format on
} else {
return &"0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839"
"4041424344454647484950515253545556575859"
"6061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899"[value * 2];
}
}
}
constexpr inline void copy2(char* dst, const char* src) {
if (!std::is_constant_evaluated()) {
std::memcpy(dst, src, 2);
return;
}
*dst++ = static_cast<char>(*src++);
*dst = static_cast<char>(*src);
}
template <FormatType t_format_type>
consteval inline unsigned get_base_divisor() {
switch (t_format_type) {
case FormatType::b:
return 2;
case FormatType::x:
return 16;
default:
return 10;
}
}
template <FormatType t_format_type, typename uint_t>
constexpr inline void format_base(char* out, uint_t value, int n_digits,
int size) {
constexpr unsigned divisor = get_base_divisor<t_format_type>();
constexpr unsigned square_divisor = const_pow(divisor, 2);
if (n_digits > size) {
for (int i = 0; i < size; ++i) {
*(out++) = 'f';
}
return;
}
out += size;
while (value >= square_divisor) {
out -= 2;
copy2(out, digits2_base<t_format_type>(
static_cast<size_t>(value % square_divisor)));
value /= square_divisor;
}
if (value < divisor) {
*--out = digits2_base<t_format_type>(value*divisor)[0];
return;
}
out -= 2;
copy2(out, digits2_base<t_format_type>(static_cast<size_t>(value)));
}
// returns {abs_value, was_negative}
template <std::signed_integral int_t>
constexpr std::pair<typename std::make_unsigned<int_t>::type, bool>
get_abs_value(int_t value) {
using uint_t = typename std::make_unsigned<int_t>::type;
uint_t abs_value = static_cast<uint_t>(value);
const bool negative = value < 0;
if (negative) abs_value = 0 - abs_value;
return {abs_value, negative};
}
template <std::unsigned_integral int_t>
constexpr std::pair<int_t, bool> get_abs_value(int_t value) {
return {value, false};
}
/*
*
* Integral types
*
*/
template <fmt_data_t t_fmt_node, std::unsigned_integral uint_t>
constexpr inline void format_int(char* out, uint_t value) {
format_base<t_fmt_node.type>(out, value,
count_digits_base<t_fmt_node.type>(value),
t_fmt_node.length);
}
template <fmt_data_t t_fmt_node, std::signed_integral int_t>
constexpr inline void format_int(char* out, int_t value) {
const auto [abs_value, negative] = get_abs_value(value);
const std::size_t n_digits = count_digits_base<t_fmt_node.type>(abs_value);
format_base<t_fmt_node.type>(out + 1 * (negative), abs_value, n_digits,
t_fmt_node.length - 1 * (negative));
if constexpr (t_fmt_node.has_zero_padding) {
if (negative) *(out) = '-';
} else {
if (n_digits < t_fmt_node.length)
if (negative) *(out + t_fmt_node.length - n_digits - 1) = '-';
}
}
/*
*
* Floating point types
*
*/
template <fmt_data_t t_fmt_node, std::floating_point float_t>
constexpr inline void format_float(char* out, float_t value) {
// clang-format off
constexpr fmt_data_t fmt_node_integral = {
t_fmt_node.has_zero_padding, // has_zero_padding
t_fmt_node.length - t_fmt_node.precision - 1, // length
t_fmt_node.precision, // ignored
FormatType::d, // type
t_fmt_node.position // ignored
};
constexpr fmt_data_t fmt_node_fractional = {
true, // has_zero_padding
t_fmt_node.precision, // length
t_fmt_node.precision, // ignored
FormatType::d, // type
t_fmt_node.position // ignored
};
// clang-format on
*(out + t_fmt_node.length - t_fmt_node.precision - 1) = '.';
const int integral = static_cast<int>(value);
constexpr std::size_t factor = const_pow(10, t_fmt_node.precision);
const int fractional = static_cast<int>((value - integral) * factor);
const auto [fractional_abs, fractional_negative] =
get_abs_value(fractional);
format_int<fmt_node_integral, int>(out, integral);
format_int<fmt_node_fractional, uint16_t>(
out + t_fmt_node.length - t_fmt_node.precision, fractional_abs);
}
}} // namespace const_fmt::const_fmt_detail
#endif // LOGGER_FORMAT_IMPL_H

View File

@@ -5,7 +5,7 @@
#include "types.h"
// clang-format off
// clang-const_format off
/*
*
@@ -34,10 +34,10 @@
*
*/
// clang-format on
// clang-const_format on
namespace detail {
namespace const_fmt { namespace const_fmt_detail {
/*
@@ -119,42 +119,42 @@ constexpr inline parse_result_t<unsigned> parse_number(unsigned i) {
template <ConstString s>
constexpr inline parse_result_t<FormatType> parse_type(unsigned i) {
switch (s[i]) {
case 's':
return {true, ++i, FormatType::s};
case 'c':
return {true, ++i, FormatType::c};
case 'b':
return {true, ++i, FormatType::b};
case 'B':
return {true, ++i, FormatType::B};
case 'd':
return {true, ++i, FormatType::d};
case 'o':
return {true, ++i, FormatType::o};
case 'x':
return {true, ++i, FormatType::x};
case 'X':
return {true, ++i, FormatType::X};
case 'a':
return {true, ++i, FormatType::a};
case 'A':
return {true, ++i, FormatType::A};
case 'e':
return {true, ++i, FormatType::e};
case 'E':
return {true, ++i, FormatType::E};
case 'f':
return {true, ++i, FormatType::f};
case 'F':
return {true, ++i, FormatType::F};
case 'g':
return {true, ++i, FormatType::g};
case 'G':
return {true, ++i, FormatType::G};
case 'p':
return {true, ++i, FormatType::p};
default:
return {false, i, FormatType::s};
case 's':
return {true, ++i, FormatType::s};
case 'c':
return {true, ++i, FormatType::c};
case 'b':
return {true, ++i, FormatType::b};
case 'B':
return {true, ++i, FormatType::B};
case 'd':
return {true, ++i, FormatType::d};
case 'o':
return {true, ++i, FormatType::o};
case 'x':
return {true, ++i, FormatType::x};
case 'X':
return {true, ++i, FormatType::X};
case 'a':
return {true, ++i, FormatType::a};
case 'A':
return {true, ++i, FormatType::A};
case 'e':
return {true, ++i, FormatType::e};
case 'E':
return {true, ++i, FormatType::E};
case 'f':
return {true, ++i, FormatType::f};
case 'F':
return {true, ++i, FormatType::F};
case 'g':
return {true, ++i, FormatType::g};
case 'G':
return {true, ++i, FormatType::G};
case 'p':
return {true, ++i, FormatType::p};
default:
return {false, i, FormatType::s};
}
}
@@ -214,7 +214,8 @@ constexpr inline parse_result_t<fmt_node_t> parse_braces(unsigned i) {
}
template <ConstString s>
constexpr inline parse_result_t<string_result_t<get_ast_len<s>()>> parse_string() {
constexpr inline parse_result_t<string_result_t<get_ast_len<s>()>>
parse_string() {
parse_result_t<string_result_t<get_ast_len<s>()>> result;
result.is_valid = true;
@@ -242,7 +243,7 @@ constexpr inline parse_result_t<string_result_t<get_ast_len<s>()>> parse_string(
}
} // namespace detail
}} // namespace const_fmt::const_fmt_detail

View File

@@ -5,7 +5,7 @@
#include <array>
namespace detail {
namespace const_fmt { namespace const_fmt_detail {
/*
@@ -136,7 +136,7 @@ struct fmt_data_t {
};
} // namespace detail
}} // namespace const_fmt::const_fmt_detail
#endif // LOGGER_TYPES_H

View File

@@ -7,7 +7,7 @@
#include "types.h"
namespace detail {
namespace const_fmt { namespace const_fmt_detail {
constexpr inline std::size_t const_pow(std::size_t base, std::size_t pow) {
@@ -18,17 +18,6 @@ constexpr inline std::size_t const_pow(std::size_t base, std::size_t pow) {
}
template <std::size_t t_n>
consteval inline std::array<char, t_n> get_init_array(char val) {
std::array<char, t_n> result;
for (auto& c : result)
c = val;
return result;
}
template <auto ast>
consteval inline std::size_t count_ast_format_nodes() {
std::size_t result = 0;
@@ -41,7 +30,8 @@ consteval inline std::size_t count_ast_format_nodes() {
template <auto ast>
consteval inline std::array<fmt_data_t, count_ast_format_nodes<ast>()> get_fmt_data() {
consteval inline std::array<fmt_data_t, count_ast_format_nodes<ast>()>
get_fmt_data() {
std::array<fmt_data_t, count_ast_format_nodes<ast>()> result = {};
std::size_t position = 0;
@@ -96,7 +86,7 @@ consteval inline int get_ast_output_len() {
return result;
}
constexpr inline std::size_t const_strlen(const char* arg) {
constexpr inline std::size_t const_strlen(const char* arg) {
if (std::is_constant_evaluated()) {
return *arg ? 1 + const_strlen(arg + 1) : 0;
} else {
@@ -105,7 +95,7 @@ constexpr inline std::size_t const_strlen(const char* arg) {
}
} // namespace detail
}} // namespace const_fmt::const_fmt_detail
#endif // LOGGER_UTILITY_H

7
examples/CMakeLists.txt Normal file
View File

@@ -0,0 +1,7 @@
add_executable(const_fmt_example src/examples.cpp)
if(MSVC)
target_compile_options(const_fmt_example PRIVATE /W4 /WX)
else()
target_compile_options(const_fmt_example PRIVATE -O3 -Wall -Wextra -pedantic -fno-exceptions)
endif()

17
examples/src/examples.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include <const_fmt/format.h>
#include <iostream>
#include <string_view>
using namespace const_fmt;
int main() {
constexpr auto s = "This is an integer: {:08x}, and this is a float: {:09.4b}"_const_fmt(122u, -86.2);
// Convert s (with a type of 'std::array<char, N>') into something
// writable to std::cout
std::string_view sv{&s[0], s.size()};
std::cout << sv << std::endl;
return 0;
}

View File

@@ -1,52 +0,0 @@
#ifndef LOGGER_LOGGER_H
#define LOGGER_LOGGER_H
#include <array>
#include <iostream>
#include <tuple>
#include <format.h>
/*
*
* Logger class
*
*/
namespace detail {
template <typename T>
concept output_policy_c = requires(T t) {
t.write('c');
};
} // namespace detail
template <detail::output_policy_c output_policy_t>
class Logger {
public:
Logger(output_policy_t output_policy) : m_output_policy(output_policy) {
}
template <detail::ConstString msg, typename... args_t>
void log(args_t... args) {
const auto formatted_msg = format<msg>(args...);
for (const auto& c : formatted_msg) {
m_output_policy.write(c);
}
m_output_policy.write('\n');
}
private:
output_policy_t& m_output_policy;
};
#endif // LOGGER_LOGGER_H

View File

@@ -1,154 +0,0 @@
#ifndef LOGGER_FORMAT_IMPL_H
#define LOGGER_FORMAT_IMPL_H
/*
*
* ****************************************************************
* Disclaimer: Most of this code is shamelessly stolen from fmtlib
* ****************************************************************
*
*/
#include <cstdint>
#include "utility.h"
namespace detail {
/*
*
* Utility functions
*
*/
#define FMT_POWERS_OF_10(factor) \
factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
(factor)*1000000, (factor)*10000000, (factor)*100000000, \
(factor)*1000000000
template <typename T>
constexpr int count_digits_fallback(T n) {
int count = 1;
for (;;) {
if (n < 10) return count;
if (n < 100) return count + 1;
if (n < 1000) return count + 2;
if (n < 10000) return count + 3;
n /= 10000u;
count += 4;
}
}
inline int do_count_digits(uint64_t n) {
// Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
static constexpr uint8_t bsr2log10[] = {
1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
auto t = bsr2log10[__builtin_clzll(n | 1) ^ 63];
static constexpr const uint64_t zero_or_powers_of_10[] = {
0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
return t - (n < zero_or_powers_of_10[t]);
}
constexpr inline auto count_digits(uint64_t n) -> int {
if (!std::is_constant_evaluated()) {
return do_count_digits(n);
}
return count_digits_fallback(n);
}
// Converts value in the range [0, 100) to a string.
constexpr inline const char* digits2(size_t value) {
// GCC generates slightly better code when value is pointer-size.
return &"0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839"
"4041424344454647484950515253545556575859"
"6061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899"[value * 2];
}
constexpr inline void copy2(char* dst, const char* src) {
if (!std::is_constant_evaluated()) {
std::memcpy(dst, src, 2);
return;
}
*dst++ = static_cast<char>(*src++);
*dst = static_cast<char>(*src);
}
template <typename uint_t>
constexpr inline void format_decimal(char* out, uint_t value, int size) {
if (count_digits(value) > size) return;
out += size;
while (value >= 100) {
out -= 2;
copy2(out, digits2(static_cast<size_t>(value % 100)));
value /= 100;
}
if (value < 10) {
*--out = static_cast<char>('0' + value);
return;
}
out -= 2;
copy2(out, digits2(static_cast<size_t>(value)));
}
/*
*
* Integral types
*
*/
template <std::unsigned_integral uint_t>
constexpr inline void format_int(char* out, uint_t value, fmt_data_t fmt_node) {
format_decimal(out, value, fmt_node.length);
}
template <std::signed_integral uint_t>
constexpr inline void format_int(char* out, uint_t value, fmt_data_t fmt_node) {
auto abs_value = static_cast<uint64_t>(value);
const bool negative = value < 0;
if (negative) abs_value = 0 - abs_value;
format_decimal(out + 1 * (negative), abs_value,
fmt_node.length - 1 * (negative));
if (negative) *out = '-';
}
/*
*
* Floating point types
*
*/
template <std::floating_point float_t>
constexpr inline void format_float(char* out, float_t value,
fmt_data_t fmt_data) {
*(out) = 'f';
*(out + fmt_data.length - fmt_data.precision - 1) = '.';
}
} // namespace detail
#endif // LOGGER_FORMAT_IMPL_H

View File

@@ -1,15 +0,0 @@
//#include <iostream>
#include <Logger.h>
int main(int argc, char* argv[]) {
auto formatted = "Test: {:12} {:012.5} {:8}"_fmt(argv[0], 123.45, -1234567);
for (const auto& c : formatted)
std::cout << c;
std::cout << std::endl;
// return formatted[6];
return 0;
}

View File

@@ -14,9 +14,19 @@ macro(package_add_test TESTNAME)
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
)
set_target_properties(${TESTNAME} PROPERTIES FOLDER tests)
if(MSVC)
target_compile_options(${TESTNAME} PRIVATE /W4 /WX)
else()
target_compile_options(${TESTNAME} PRIVATE -O3 -Wall -Wextra -pedantic -fno-exceptions)
endif()
endmacro()
package_add_test(utility_test src/utility.cpp)
package_add_test(parse_test src/parse.cpp)
package_add_test(format_test src/format.cpp)
package_add_test(format_utility_test src/format_utility.cpp)
package_add_test(format_decimal_test src/format_decimal.cpp)
package_add_test(format_binary_test src/format_binary.cpp)
package_add_test(format_hex_test src/format_hex.cpp)

View File

@@ -1,65 +0,0 @@
#include <format.h>
#include <gtest/gtest.h>
using namespace detail;
TEST(Format, positive_int) {
constexpr std::array<char, 8> control1 = {'0', '0', '0', '0',
'0', '0', '0', '2'};
constexpr std::array<char, 8> formatted1 = format<"{:08}">(2);
constexpr std::array<char, 8> control2 = {' ', ' ', ' ', '2',
'2', '2', '2', '2'};
constexpr std::array<char, 8> formatted2 = format<"{:8}">(22222);
constexpr std::array<char, 8> control3 = {'0', '0', '0', '1',
'2', '3', '4', '5'};
constexpr std::array<char, 8> formatted3 = format<"{:08.4}">(12345);
constexpr std::array<char, 4> control4 = {'6', '7', '8', '9'};
constexpr std::array<char, 4> formatted4 = format<"{:4}">(6789);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
}
TEST(Format, negative_int) {
constexpr std::array<char, 8> control1 = {'-', '0', '0', '0',
'0', '0', '0', '2'};
constexpr std::array<char, 8> formatted1 = format<"{:08}">(-2);
constexpr std::array<char, 8> control2 = {' ', ' ', '-', '2',
'2', '2', '2', '2'};
constexpr std::array<char, 8> formatted2 = format<"{:8}">(-22222);
constexpr std::array<char, 8> control3 = {'-', '0', '0', '1',
'2', '3', '4', '5'};
constexpr std::array<char, 8> formatted3 = format<"{:08.4}">(-12345);
constexpr std::array<char, 5> control4 = {'-', '6', '7', '8', '9'};
constexpr std::array<char, 5> formatted4 = format<"{:5}">(-6789);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
}
//
//TEST(Format, positive_float) {
// // TODO
// EXPECT_EQ(true, false);
//}
//
//TEST(Format, negative_float) {
// // TODO
// EXPECT_EQ(true, false);
//}
//
//TEST(Format, string) {
// // TODO
// EXPECT_EQ(true, false);
//}

View File

@@ -0,0 +1,59 @@
#include <const_fmt/format.h>
#include <gtest/gtest.h>
using namespace const_fmt;
using namespace const_fmt::const_fmt_detail;
TEST(FormatBinary, positive_int) {
constexpr std::array<char, 8> control1 = {'0', '0', '0', '0',
'0', '0', '1', '0'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08b}">(0b10);
constexpr std::array<char, 8> control2 = {' ', ' ', '1', '0',
'1', '0', '1', '0'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8b}">(0b101010);
constexpr std::array<char, 8> control3 = {'0', '0', '0', '1',
'1', '0', '0', '1'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.4b}">(0b11001);
constexpr std::array<char, 4> control4 = {'1', '0', '1', '1'};
constexpr std::array<char, 4> formatted4 = const_format<"{:4b}">(0b1011);
constexpr std::array<char, 4> control5 = {'f', 'f', 'f', 'f'};
constexpr std::array<char, 4> formatted5 = const_format<"{:4b}">(0b10011);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
EXPECT_EQ(control5, formatted5);
}
TEST(FormatBinary, negative_int) {
constexpr std::array<char, 8> control1 = {'-', '0', '0', '0',
'0', '0', '1', '0'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08b}">(-0b10);
constexpr std::array<char, 8> control2 = {' ', '-', '1', '0',
'1', '0', '1', '0'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8b}">(-0b101010);
constexpr std::array<char, 8> control3 = {'-', '0', '0', '1',
'0', '0', '1', '1'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.4b}">(-0b10011);
constexpr std::array<char, 5> control4 = {'-', '1', '1', '0', '1'};
constexpr std::array<char, 5> formatted4 = const_format<"{:5b}">(-0b1101);
constexpr std::array<char, 5> control5 = {'-', 'f', 'f', 'f', 'f'};
constexpr std::array<char, 5> formatted5 = const_format<"{:05b}">(-0b10101);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
EXPECT_EQ(control5, formatted5);
}

103
test/src/format_decimal.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include <const_fmt/format.h>
#include <gtest/gtest.h>
using namespace const_fmt;
using namespace const_fmt::const_fmt_detail;
TEST(FormatDecimal, positive_int) {
constexpr std::array<char, 8> control1 = {'0', '0', '0', '0',
'0', '0', '0', '2'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08}">(2);
constexpr std::array<char, 8> control2 = {' ', ' ', ' ', '2',
'2', '2', '2', '2'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8}">(22222);
constexpr std::array<char, 8> control3 = {'0', '0', '0', '1',
'2', '3', '4', '5'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.4}">(12345);
constexpr std::array<char, 4> control4 = {'6', '7', '8', '9'};
constexpr std::array<char, 4> formatted4 = const_format<"{:4}">(6789);
constexpr std::array<char, 4> control5 = {'f', 'f', 'f', 'f'};
constexpr std::array<char, 4> formatted5 = const_format<"{:4}">(67895);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
EXPECT_EQ(control5, formatted5);
}
TEST(FormatDecimal, negative_int) {
constexpr std::array<char, 8> control1 = {'-', '0', '0', '0',
'0', '0', '0', '2'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08}">(-2);
constexpr std::array<char, 8> control2 = {' ', ' ', '-', '2',
'2', '2', '2', '2'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8}">(-22222);
constexpr std::array<char, 8> control3 = {'-', '0', '0', '1',
'2', '3', '4', '5'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.4}">(-12345);
constexpr std::array<char, 5> control4 = {'-', '6', '7', '8', '9'};
constexpr std::array<char, 5> formatted4 = const_format<"{:5}">(-6789);
constexpr std::array<char, 5> control5 = {'-', 'f', 'f', 'f', 'f'};
constexpr std::array<char, 5> formatted5 = const_format<"{:05}">(-66789);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
EXPECT_EQ(control5, formatted5);
}
TEST(FormatDecimal, positive_float) {
constexpr std::array<char, 8> control1 = {'0', '0', '2', '.',
'3', '4', '5', '6'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08.4}">(2.3456);
// Float error: 2323.2 -> 2323.1
constexpr std::array<char, 8> control2 = {' ', ' ', '2', '3',
'2', '3', '.', '1'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8.1}">(2323.2);
constexpr std::array<char, 8> control3 = {'1', '2', '3', '4',
'.', '5', '0', '0'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.3}">(1234.5);
// Float error: .9 -> .8
constexpr std::array<char, 4> control4 = {'f', 'f', '.', '8'};
constexpr std::array<char, 4> formatted4 = const_format<"{:4.1}">(678.9);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
}
TEST(FormatDecimal, negative_float) {
constexpr std::array<char, 8> control1 = {'-', '0', '2', '.',
'3', '4', '5', '6'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08.4}">(-2.3456);
// Float error: 2323.2 -> 2323.1
constexpr std::array<char, 8> control2 = {' ', '-', '2', '3',
'2', '3', '.', '1'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8.1}">(-2323.2);
constexpr std::array<char, 8> control3 = {'-', 'f', 'f', 'f',
'.', '5', '0', '0'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.3}">(-1234.5);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
}

59
test/src/format_hex.cpp Normal file
View File

@@ -0,0 +1,59 @@
#include <const_fmt/format.h>
#include <gtest/gtest.h>
using namespace const_fmt;
using namespace const_fmt::const_fmt_detail;
TEST(FormatHex, positive_int) {
constexpr std::array<char, 8> control1 = {'0', '0', '0', '0',
'0', '0', '1', '0'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08x}">(0x10);
constexpr std::array<char, 8> control2 = {' ', ' ', ' ', 'F',
'F', 'A', '7', '6'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8x}">(0xffa76);
constexpr std::array<char, 8> control3 = {'0', '0', '0', '0',
'B', 'C', 'E', 'F'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.4x}">(0xbcef);
constexpr std::array<char, 4> control4 = {'A', 'D', '0', '1'};
constexpr std::array<char, 4> formatted4 = const_format<"{:4x}">(0xad01);
constexpr std::array<char, 4> control5 = {'f', 'f', 'f', 'f'};
constexpr std::array<char, 4> formatted5 = const_format<"{:4x}">(0x12345);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
EXPECT_EQ(control5, formatted5);
}
TEST(FormatHex, negative_int) {
constexpr std::array<char, 8> control1 = {'-', '0', '0', '0',
'0', '0', '1', '0'};
constexpr std::array<char, 8> formatted1 = const_format<"{:08x}">(-0x10);
constexpr std::array<char, 8> control2 = {' ', ' ', '-', 'F',
'F', 'A', '7', '6'};
constexpr std::array<char, 8> formatted2 = const_format<"{:8x}">(-0xffa76);
constexpr std::array<char, 8> control3 = {'-', '0', '0', '0',
'B', 'C', 'E', 'F'};
constexpr std::array<char, 8> formatted3 = const_format<"{:08.4x}">(-0xbcef);
constexpr std::array<char, 4> control4 = {'-', 'A', 'D', '1'};
constexpr std::array<char, 4> formatted4 = const_format<"{:4x}">(-0xad1);
constexpr std::array<char, 4> control5 = {'-', 'f', 'f', 'f'};
constexpr std::array<char, 4> formatted5 = const_format<"{:04x}">(-0x1234);
EXPECT_EQ(control1, formatted1);
EXPECT_EQ(control2, formatted2);
EXPECT_EQ(control3, formatted3);
EXPECT_EQ(control4, formatted4);
EXPECT_EQ(control5, formatted5);
}

View File

@@ -0,0 +1,44 @@
#include <const_fmt/format_impl.h>
#include <limits>
#include <gtest/gtest.h>
using namespace const_fmt;
using namespace const_fmt::const_fmt_detail;
TEST(FormatUtility, count_digits_base_decimal) {
constexpr unsigned length1 = count_digits_base<FormatType::d>(123);
constexpr unsigned length2 = count_digits_base<FormatType::d>(std::numeric_limits<uint64_t>::max());
constexpr unsigned length3 = count_digits_base<FormatType::d>(10000011);
constexpr unsigned length4 = count_digits_base<FormatType::d>(1);
EXPECT_EQ(length1, 3);
EXPECT_EQ(length2, 20);
EXPECT_EQ(length3, 8);
EXPECT_EQ(length4, 1);
}
TEST(FormatUtility, count_digits_base_binary) {
constexpr unsigned length1 = count_digits_base<FormatType::b>(0b1001);
constexpr unsigned length2 = count_digits_base<FormatType::b>(std::numeric_limits<uint64_t>::max());
constexpr unsigned length3 = count_digits_base<FormatType::b>(0b10000001);
constexpr unsigned length4 = count_digits_base<FormatType::b>(0b01);
EXPECT_EQ(length1, 4);
EXPECT_EQ(length2, 64);
EXPECT_EQ(length3, 8);
EXPECT_EQ(length4, 1);
}
TEST(FormatUtility, count_digits_base_hex) {
constexpr unsigned length1 = count_digits_base<FormatType::x>(0x123);
constexpr unsigned length2 = count_digits_base<FormatType::x>(std::numeric_limits<uint64_t>::max());
constexpr unsigned length3 = count_digits_base<FormatType::x>(0x1000000f);
constexpr unsigned length4 = count_digits_base<FormatType::x>(0x01);
EXPECT_EQ(length1, 3);
EXPECT_EQ(length2, 16);
EXPECT_EQ(length3, 8);
EXPECT_EQ(length4, 1);
}

View File

@@ -1,8 +1,8 @@
#include <gtest/gtest.h>
#include <parse.h>
#include <const_fmt/parse.h>
using namespace detail;
using namespace const_fmt::const_fmt_detail;
TEST(Parse, parse_number) {
@@ -93,4 +93,4 @@ TEST(Parse, ast_chars_and_nodes) {
' ', ' ', '-'};
EXPECT_EQ(parse_string<"{:06.3}ab d{:8s}{:.4} -">().value, control);
}
}

View File

@@ -1,9 +1,11 @@
#include <utility.h>
#include <const_fmt/utility.h>
#include <gtest/gtest.h>
using namespace const_fmt;
TEST(Utility, const_pow) {
EXPECT_EQ(detail::const_pow(10, 0), 1);
EXPECT_EQ(detail::const_pow(0, 1), 0);
EXPECT_EQ(detail::const_pow(2, 8), 0b1'0000'0000);
EXPECT_EQ(const_fmt_detail::const_pow(10, 0), 1);
EXPECT_EQ(const_fmt_detail::const_pow(0, 1), 0);
EXPECT_EQ(const_fmt_detail::const_pow(2, 8), 0b1'0000'0000);
}