Compare commits

..

63 Commits

Author SHA1 Message Date
b7159c3606 Merge pull request 'fix/zero_length_array' (#10) from fix/zero_length_array into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/10
2022-03-18 00:24:51 +00:00
2be63fda5a Changed the way recursion ends in const_fmt::format_args<>() 2022-03-18 01:23:56 +01:00
036b17fca9 Formatting 2022-03-18 01:15:32 +01:00
045363c733 Merge pull request 'feature/no_std_lib' (#9) from feature/no_std_lib into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/9
2022-03-17 22:45:07 +00:00
4d05a9a8fa Added const versions of std::array::begin() and std::array::end() 2022-03-17 22:44:17 +01:00
ee1153c393 Changed copy constructor signature of std::array implementation 2022-03-17 22:11:30 +01:00
b29695e148 Replaced memcpy with manual while loop 2022-03-17 18:04:06 +01:00
86930fd8b0 Added conepts; Fixed bug in const_fmt::std::true_type 2022-02-24 16:14:38 +01:00
7796be7cd4 Reimplemented is_integral and is_floating_point with different approach; Made functions constexpr inline 2022-02-23 23:12:34 +01:00
73a50cae0f Added is_integral and is_floating_point 2022-02-23 20:42:56 +01:00
91398ef23d Added remove_cv, remove_const and remove_volatile 2022-02-23 19:51:55 +01:00
b5ecd73b18 Formatting 2022-02-23 19:47:06 +01:00
826f3a54b3 Changed comment 2022-02-23 19:46:33 +01:00
ea722ec5bf Changed includes; Implemented std::pair and std::make_unsigned; changed usage of std::memcpy to memcpy 2022-02-23 19:45:17 +01:00
5ba1723295 Made array a structural type 2022-02-20 14:29:58 +01:00
8e51facd10 fixed wrong clang-format comments 2022-02-20 00:27:45 +01:00
9bd1104e5f Code compiles and passes tests without NO_STDLIB define 2022-02-20 00:26:48 +01:00
6ba83fd582 Minor changes to make sure some stuff works with no stl headers 2022-02-20 00:15:31 +01:00
8f7b1cd4e3 Implemented std::array and a bunch of other stuff 2022-02-20 00:14:33 +01:00
ec70a5bba1 Merge branch 'master' into feature/no_std_lib 2022-02-19 19:21:34 +01:00
1263dfee98 Merge pull request 'Added cmake-build-relwithdebinfo to gitignore' (#8) from fix/gitignore_builddir into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/8
2022-02-19 18:18:51 +00:00
52992e255e Added cmake-build-relwithdebinfo to gitignore 2022-02-19 19:13:54 +01:00
8526ae1d91 First version of std::array 2022-02-18 23:31:15 +01:00
b8f59710d3 Merge pull request 'Updated README.md' (#7) from fix/README_wording into master
Reviewed-on: http://git.mercurial-manifold.eu/an.tsouchlos/const_fmt/pulls/7
2022-02-15 13:54:04 +00:00
184815c75b Updated README.md 2022-02-15 13:53:47 +00:00
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
24 changed files with 1163 additions and 501 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
build
cmake-build-debug
cmake-build-release
cmake-build-relwithdebinfo
.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.

57
README.md Normal file
View File

@ -0,0 +1,57 @@
# 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 runtime then formats the numbers and writes them into their places 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
For the compile time preprocessing of format strings non non-type template parameters are heavily relied upon,
which means C++20 is required.
Only a relatively limited subset of the `fmtlib` syntax is recognized (for now anyway). In particular,
float formatting in decimal and integer formatting in decimal, binary and hexadecimal are supported.
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.

144
const_fmt/format.h Normal file
View File

@ -0,0 +1,144 @@
#ifndef LOGGER_FORMAT_H
#define LOGGER_FORMAT_H
#include "format_impl.h"
#include "parse.h"
#include "utility.h"
namespace const_fmt { namespace const_fmt_detail {
/*
*
* Utility functions
*
*/
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 const_format string");
return get_ast_output_len<parse_result.value>();
}
template <fmt_node_t fmt_node, typename T>
constexpr inline void check_fmt_params() {
static_assert(fmt_node.length > fmt_node.precision + 1,
"Insufficient length for desired precision");
}
/*
*
* Formatting wrapper functions
*
*/
template <fmt_data_t fmt_data, std::integral arg_t>
constexpr inline void format_arg(char* dest, arg_t arg) {
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){
const_fmt_detail::format_float<fmt_data>(dest, arg);
};
// TODO: Error handling
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;
dest = dest + fmt_data.length - len;
if (!std::is_constant_evaluated()) {
memcpy(dest, arg, len);
return;
}
for (std::size_t i = 0; i < len; ++i) {
*(dest++) = *(arg++);
}
};
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) {
format_arg<fmt_data_array[0]>(dest + fmt_data_array[0].position, first_arg);
if constexpr(fmt_data_array.size() > 1)
format_args<drop_first(fmt_data_array)>(dest, args...);
}
template <auto ast>
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()) {
result[i++] = ast_node.get_char();
} 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 const_fmt::const_fmt_detail
/*
*
* Public Interface
*
*/
namespace const_fmt {
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 <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 <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

271
const_fmt/format_impl.h Normal file
View File

@ -0,0 +1,271 @@
#ifndef LOGGER_FORMAT_IMPL_H
#define LOGGER_FORMAT_IMPL_H
/*
*
* ****************************************************************
* Disclaimer: Most of this code is shamelessly stolen from fmtlib
* ****************************************************************
*
*/
#include <stdint.h>
#include "stdlib.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()) {
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

@ -37,7 +37,7 @@
// clang-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

304
const_fmt/stdlib.h Normal file
View File

@ -0,0 +1,304 @@
#ifndef CONST_FMT_STDLIB_H
#define CONST_FMT_STDLIB_H
/*
*
* Disclaimer: Very bad (partially at least) implementation of some features of
* the C++ Standard Library. Not meant as a full-on stdlib implementation, only
* for usage in this project (Underlined by the fact, that in this case the
* namespace std is actually const_fmt::std)
*
*/
#ifndef CONST_FMT_NO_CPP_STDLIB
#include <array>
#include <cstring>
#else
#include <stdint.h>
#include <string.h>
namespace const_fmt { namespace std {
using size_t = uint16_t;
/*
*
* type_traits
*
*/
// various
constexpr inline bool is_constant_evaluated() noexcept {
return __builtin_is_constant_evaluated();
}
struct true_type {
constexpr static bool value = true;
};
struct false_type {
constexpr static bool value = false;
};
// is_same
template <typename fist_t, typename second_t>
struct is_same : public false_type {};
template <typename type_t>
struct is_same<type_t, type_t> : public true_type {};
// is_one_of
template <typename...>
struct is_one_of;
template <typename type_t>
struct is_one_of<type_t> {
constexpr static bool value = false;
};
template <typename type_t, typename first_t, typename... rest_t>
struct is_one_of<type_t, first_t, rest_t...> {
constexpr static bool value = std::is_same<type_t, first_t>::value ||
is_one_of<type_t, rest_t...>::value;
};
// remove_x
// clang-format off
template <typename type_t> struct remove_cv { typedef type_t type; };
template <typename type_t> struct remove_cv<const type_t> { typedef type_t type; };
template <typename type_t> struct remove_cv<volatile type_t> { typedef type_t type; };
template <typename type_t> struct remove_cv<const volatile type_t> { typedef type_t type; };
template <typename type_t> struct remove_const { typedef type_t type; };
template <typename type_t> struct remove_const<const type_t> { typedef type_t type; };
template <typename type_t> struct remove_volatile { typedef type_t type; };
template <typename type_t> struct remove_volatile<volatile type_t> { typedef type_t type; };
template <typename type_t> struct remove_reference { using type = type_t; };
template <typename type_t> struct remove_reference<type_t &> { using type = type_t; };
template <typename type_t> struct remove_reference<type_t &&> { using type = type_t; };
template <typename type_t> using remove_reference_t = typename std::remove_reference<type_t>::type;
template <typename type_t> using remove_cv_t = typename std::remove_cv<type_t>::type;
template <typename type_t> using remove_const_t = typename std::remove_const<type_t>::type;
template <typename type_t> using remove_volatile_t = typename std::remove_volatile<type_t>::type;
// clang-format on
// is_integral
template <typename type_t>
using is_integral =
is_one_of<remove_cv_t<type_t>, bool, char, signed char, unsigned char,
wchar_t, char16_t, char32_t, short, unsigned short, int,
unsigned int, long, unsigned long, long long, unsigned long long>;
// is_signed
template <typename _Tp>
using is_signed_integer = is_one_of<remove_cv_t<_Tp>, signed char, signed short,
signed int, signed long, signed long long>;
// is_floating_point
template <typename type_t>
using is_floating_point =
is_one_of<remove_cv_t<type_t>, float, double, long double>;
// make_unsigned
// clang-format off
template <typename type_t> struct make_unsigned { using type = type_t; };
template <> struct make_unsigned<signed char> { using type = char; };
template <> struct make_unsigned<unsigned short> { using type = unsigned short; };
template <> struct make_unsigned<signed int> { using type = unsigned int; };
template <> struct make_unsigned<signed long> { using type = unsigned long; };
template <> struct make_unsigned<signed long long> { using type = unsigned long long; };
// clang-format on
// value definitions
template <typename type_t>
inline constexpr bool is_integral_v = is_integral<type_t>::value;
template <typename type_t>
inline constexpr bool is_signed_integer_v = is_signed_integer<type_t>::value;
template <typename type_t>
inline constexpr bool is_floating_point_v = is_floating_point<type_t>::value;
/*
*
* concepts
*
*/
template <typename type_t>
concept integral = is_integral_v<type_t>;
template <typename type_t>
concept signed_integral = is_signed_integer_v<type_t>;
template <typename type_t>
concept unsigned_integral = integral<type_t> && !signed_integral<type_t>;
template <typename type_t>
concept floating_point = is_floating_point_v<type_t>;
/*
*
* algorithm
*
*/
template <typename input_t, typename output_t>
constexpr inline void copy(const input_t* start, const input_t* end,
output_t* dest_start) {
auto temp = start;
while (temp != end)
*(dest_start++) = *(temp++);
}
/*
*
* utility
*
*/
template <typename T>
std::remove_reference_t<T>&& move(T&& arg) noexcept {
return reinterpret_cast<std::remove_reference_t<T>&&>(arg);
}
template <typename T>
constexpr inline void swap(T& t1, T& t2) {
T temp = std::move(t1);
t1 = std::move(t2);
t2 = std::move(temp);
}
template <typename first_t, typename second_t>
struct pair {
first_t first;
second_t second;
};
/*
*
* array
*
*/
// TODO: Is std::size_t really the best bet here?
template <typename data_t, std::size_t t_size>
class array {
public:
template <typename... args_t>
constexpr array(args_t... args) noexcept : m_data{args...} {
static_assert(sizeof...(args) == t_size, "Invalid number of arguments");
}
constexpr array() noexcept = default;
constexpr array(const array&) = default;
constexpr array(array&&) = default;
constexpr array& operator=(const array& other) = default;
constexpr array& operator=(array&& other) = default;
constexpr void swap(array<data_t, t_size>& other) noexcept {
for (int i = 0; i < t_size; ++i) {
using std::swap;
swap(m_data[i], other.m_data[i]);
}
}
constexpr std::size_t size() const noexcept {
return t_size;
}
constexpr data_t& operator[](std::size_t index) noexcept {
return m_data[index];
}
constexpr const data_t& operator[](std::size_t index) const noexcept {
return m_data[index];
}
using iterator = data_t*;
using const_iterator = const data_t*;
constexpr iterator begin() noexcept {
return &(m_data[0]);
}
constexpr iterator end() noexcept {
return (&(m_data[t_size - 1]) + 1);
}
constexpr const_iterator begin() const noexcept {
return &(m_data[0]);
}
constexpr const_iterator end() const noexcept {
return (&(m_data[t_size - 1]) + 1);
}
constexpr const_iterator cbegin() const noexcept {
return &(m_data[0]);
}
constexpr const_iterator cend() const noexcept {
return (&(m_data[t_size - 1]) + 1);
}
data_t m_data[t_size];
};
}} // namespace const_fmt::std
#endif
#endif // CONST_FMT_STDLIB_H

View File

@ -2,10 +2,11 @@
#define LOGGER_TYPES_H
#include <array>
#include "stdlib.h"
namespace detail {
namespace const_fmt { namespace const_fmt_detail {
/*
@ -19,8 +20,7 @@ template <std::size_t N>
class ConstString {
public:
constexpr ConstString(const char (&content)[N]) noexcept {
std::copy(std::begin(content), std::end(content),
std::begin(m_content));
std::copy(&content[0], (&content[N - 1] + 1), m_content.begin());
}
constexpr char operator[](std::size_t index) const noexcept {
@ -136,7 +136,7 @@ struct fmt_data_t {
};
} // namespace detail
}} // namespace const_fmt::const_fmt_detail
#endif // LOGGER_TYPES_H

View File

@ -2,12 +2,13 @@
#define LOGGER_UTILITY_H
#include <cstring>
#include <string.h>
#include "stdlib.h"
#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 +19,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 +31,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 +87,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 +96,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()

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

@ -0,0 +1,19 @@
#define CONST_FMT_NO_CPP_STDLIB
#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,138 +0,0 @@
#ifndef LOGGER_FORMAT_H
#define LOGGER_FORMAT_H
#include <cstring>
#include "parse.h"
#include "utility.h"
#include "format_impl.h"
namespace detail {
/*
*
* Utility functions
*
*/
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");
return get_ast_output_len<parse_result.value>();
}
template <fmt_node_t fmt_node, typename T>
constexpr inline void check_fmt_params() {
static_assert(fmt_node.length > fmt_node.precision + 1,
"Insufficient length for desired precision");
}
/*
*
* Formatting wrapper functions
*
*/
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);
};
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);
};
// TODO: Error handling
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;
dest = dest + fmt_data.length - len;
if (!std::is_constant_evaluated()) {
std::memcpy(dest, arg, len);
return;
}
for (std::size_t i = 0; i < len; ++i) {
*(dest++) = *(arg++);
}
};
// End of recursion
template <auto ast>
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) {
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');
int i = 0;
for (const auto& ast_node : ast) {
if (ast_node.is_char())
result[i++] = ast_node.get_char();
else
i += ast_node.get_node().length;
}
return result;
}
} // namespace detail
/*
*
* Public Interface
*
*/
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>();
auto result = detail::get_preproc_string<ast>();
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 <detail::ConstString t_s>
constexpr auto operator""_fmt() {
return fmt_literal_obj_t<t_s>{};
}
#endif // LOGGER_FORMAT_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);
}