Compare commits

..

No commits in common. "master" and "b50d5a7e0e713d207237ce66b34bf22087ee4092" have entirely different histories.

24 changed files with 501 additions and 1163 deletions

2
.gitignore vendored
View File

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

View File

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

19
LICENSE
View File

@ -1,19 +0,0 @@
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.

View File

@ -1,57 +0,0 @@
# 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.

View File

@ -1,144 +0,0 @@
#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

View File

@ -1,271 +0,0 @@
#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

@ -1,304 +0,0 @@
#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

@ -1,7 +0,0 @@
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()

View File

@ -1,19 +0,0 @@
#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;
}

52
inc/Logger.h Normal file
View File

@ -0,0 +1,52 @@
#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

138
inc/format.h Normal file
View File

@ -0,0 +1,138 @@
#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

154
inc/format_impl.h Normal file
View File

@ -0,0 +1,154 @@
#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

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

View File

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

View File

@ -2,13 +2,12 @@
#define LOGGER_UTILITY_H #define LOGGER_UTILITY_H
#include <string.h> #include <cstring>
#include "stdlib.h"
#include "types.h" #include "types.h"
namespace const_fmt { namespace const_fmt_detail { namespace detail {
constexpr inline std::size_t const_pow(std::size_t base, std::size_t pow) { constexpr inline std::size_t const_pow(std::size_t base, std::size_t pow) {
@ -19,6 +18,17 @@ 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> template <auto ast>
consteval inline std::size_t count_ast_format_nodes() { consteval inline std::size_t count_ast_format_nodes() {
std::size_t result = 0; std::size_t result = 0;
@ -31,8 +41,7 @@ consteval inline std::size_t count_ast_format_nodes() {
template <auto ast> template <auto ast>
consteval inline std::array<fmt_data_t, count_ast_format_nodes<ast>()> consteval inline std::array<fmt_data_t, count_ast_format_nodes<ast>()> get_fmt_data() {
get_fmt_data() {
std::array<fmt_data_t, count_ast_format_nodes<ast>()> result = {}; std::array<fmt_data_t, count_ast_format_nodes<ast>()> result = {};
std::size_t position = 0; std::size_t position = 0;
@ -87,7 +96,7 @@ consteval inline int get_ast_output_len() {
return result; 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()) { if (std::is_constant_evaluated()) {
return *arg ? 1 + const_strlen(arg + 1) : 0; return *arg ? 1 + const_strlen(arg + 1) : 0;
} else { } else {
@ -96,7 +105,7 @@ constexpr inline std::size_t const_strlen(const char* arg) {
} }
}} // namespace const_fmt::const_fmt_detail } // namespace detail
#endif // LOGGER_UTILITY_H #endif // LOGGER_UTILITY_H

15
src/main.cpp Normal file
View File

@ -0,0 +1,15 @@
//#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,19 +14,9 @@ macro(package_add_test TESTNAME)
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}" PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
) )
set_target_properties(${TESTNAME} PROPERTIES FOLDER tests) 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() endmacro()
package_add_test(utility_test src/utility.cpp) package_add_test(utility_test src/utility.cpp)
package_add_test(parse_test src/parse.cpp) package_add_test(parse_test src/parse.cpp)
package_add_test(format_utility_test src/format_utility.cpp) package_add_test(format_test src/format.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)

65
test/src/format.cpp Normal file
View File

@ -0,0 +1,65 @@
#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

@ -1,59 +0,0 @@
#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);
}

View File

@ -1,103 +0,0 @@
#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);
}

View File

@ -1,59 +0,0 @@
#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

@ -1,44 +0,0 @@
#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 <gtest/gtest.h>
#include <const_fmt/parse.h> #include <parse.h>
using namespace const_fmt::const_fmt_detail; using namespace detail;
TEST(Parse, parse_number) { 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); EXPECT_EQ(parse_string<"{:06.3}ab d{:8s}{:.4} -">().value, control);
} }

View File

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