Skip to content

Commit 291a9e2

Browse files
authored
Merge pull request #285 from elbeno/freestanding-fmt
🎨 Make limited formatting available in freestanding
2 parents 4190978 + 6f6cce7 commit 291a9e2

File tree

9 files changed

+228
-11
lines changed

9 files changed

+228
-11
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ target_sources(
6060
include/stdx/cx_set.hpp
6161
include/stdx/cx_vector.hpp
6262
include/stdx/detail/bitset_common.hpp
63+
include/stdx/detail/fmt.hpp
6364
include/stdx/detail/list_common.hpp
6465
include/stdx/env.hpp
6566
include/stdx/for_each_n_args.hpp

docs/ct_format.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ provides `ct_format`, a compile-time function for formatting strings.
99
NOTE: Like xref:ct_string.adoc#_ct_string_hpp[`ct_string`], `ct_format` is
1010
available only in C++20 and later.
1111

12-
IMPORTANT: `ct_format` is not yet available on freestanding implementations.
12+
IMPORTANT: `ct_format` is limited on freestanding implementations: format
13+
specifiers are limited to `{}` and `{:x}`.
1314

1415
The format string is provided as a template argument, and the arguments to be
1516
formatted as regular function arguments.

docs/static_assert.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
`STATIC_ASSERT` is a way to produce compile-time errors using formatted strings.
55

6-
IMPORTANT: `STATIC_ASSERT` is not yet available on freestanding implementations.
6+
IMPORTANT: `STATIC_ASSERT` is limited on freestanding implementations in the
7+
same way as xref:ct_format.adoc#_ct_format_hpp[`ct_format`].
78

89
[source,cpp]
910
----

include/stdx/ct_format.hpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66
#include <stdx/concepts.hpp>
77
#include <stdx/ct_conversions.hpp>
88
#include <stdx/ct_string.hpp>
9+
#include <stdx/detail/fmt.hpp>
910
#include <stdx/pp_map.hpp>
1011
#include <stdx/tuple.hpp>
1112
#include <stdx/tuple_algorithms.hpp>
1213
#include <stdx/type_traits.hpp>
1314
#include <stdx/utility.hpp>
1415

15-
#include <fmt/compile.h>
16-
#include <fmt/format.h>
17-
1816
#include <algorithm>
1917
#include <array>
2018
#include <iterator>
@@ -207,7 +205,7 @@ CONSTEVAL auto perform_format(auto s, auto v) -> ct_string<N + 1> {
207205

208206
template <ct_string Fmt, typename Arg> constexpr auto format1(Arg arg) {
209207
if constexpr (requires { arg_value(arg); }) {
210-
constexpr auto fmtstr = FMT_COMPILE(std::string_view{Fmt});
208+
constexpr auto fmtstr = STDX_FMT_COMPILE(Fmt);
211209
constexpr auto a = arg_value(arg);
212210
if constexpr (is_specialization_of_v<std::remove_cv_t<decltype(a)>,
213211
format_result>) {

include/stdx/detail/fmt.hpp

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#pragma once
2+
3+
#if not STDX_FMT_FREESTANDING
4+
5+
#include <fmt/compile.h>
6+
#include <fmt/format.h>
7+
8+
#include <string_view>
9+
10+
#define STDX_FMT_COMPILE(Fmt) FMT_COMPILE(std::string_view{Fmt})
11+
12+
#else
13+
14+
#include <stdx/compiler.hpp>
15+
#include <stdx/concepts.hpp>
16+
#include <stdx/ranges.hpp>
17+
18+
#include <cstddef>
19+
20+
#define STDX_FMT_COMPILE(X) [] { return X; }
21+
22+
namespace stdx {
23+
inline namespace v1 {
24+
namespace fmt {
25+
26+
namespace detail {
27+
struct fmt_spec {
28+
int len{};
29+
int base{};
30+
bool well_formed{true};
31+
};
32+
33+
CONSTEVAL auto parse_fmt_spec(auto fmtstr) -> fmt_spec {
34+
constexpr auto fmt = fmtstr();
35+
auto i = fmt.begin();
36+
while (*i != '{') {
37+
++i;
38+
}
39+
++i;
40+
if (*i == '}') {
41+
return {.len = 2, .base = 10};
42+
} else if (*i++ == ':' and *i++ == 'x' and *i == '}') {
43+
return {.len = 4, .base = 16};
44+
}
45+
return {.well_formed = false};
46+
}
47+
48+
template <stdx::range R>
49+
CONSTEVAL auto formatted_size(fmt_spec, R const &r) -> std::size_t {
50+
return r.size();
51+
}
52+
53+
CONSTEVAL auto formatted_size(fmt_spec, char const *str) -> std::size_t {
54+
std::size_t sz{};
55+
while (*str) {
56+
++sz;
57+
++str;
58+
}
59+
return sz;
60+
}
61+
62+
CONSTEVAL auto formatted_size(fmt_spec, char) -> std::size_t { return 1; }
63+
64+
template <stdx::integral I>
65+
CONSTEVAL auto formatted_size(fmt_spec s, I i) -> std::size_t {
66+
if (i == 0) {
67+
return 1;
68+
} else {
69+
std::size_t sz{};
70+
if (i < 0) {
71+
++sz;
72+
}
73+
74+
while (i != 0) {
75+
++sz;
76+
i /= s.base;
77+
}
78+
return sz;
79+
}
80+
}
81+
} // namespace detail
82+
83+
CONSTEVAL auto formatted_size(auto fmtstr, auto v) -> std::size_t {
84+
constexpr auto spec = detail::parse_fmt_spec(fmtstr);
85+
static_assert(
86+
spec.well_formed,
87+
"Freestanding fmt implementation does not support that format "
88+
"specifier");
89+
90+
return fmtstr().size() - spec.len + detail::formatted_size(spec, v);
91+
}
92+
93+
namespace detail {
94+
template <typename It, stdx::range R>
95+
CONSTEVAL auto format_to(fmt_spec, It dest, R const &r) -> It {
96+
for (auto const c : r) {
97+
*dest++ = c;
98+
}
99+
return dest;
100+
}
101+
102+
template <typename It>
103+
CONSTEVAL auto format_to(fmt_spec, It dest, char const *str) -> It {
104+
while (*str) {
105+
*dest++ = *str++;
106+
}
107+
return dest;
108+
}
109+
110+
template <typename It>
111+
CONSTEVAL auto format_to(fmt_spec, It dest, char c) -> It {
112+
*dest++ = c;
113+
return dest;
114+
}
115+
116+
template <typename It, stdx::integral I>
117+
CONSTEVAL auto format_to(fmt_spec s, It dest, I i) -> It {
118+
if (i == 0) {
119+
*dest++ = '0';
120+
} else {
121+
auto abs = []<typename T>(T v) -> T { return v < 0 ? -v : v; };
122+
auto to_digit = [](auto n) -> char { return "0123456789abcdef"[n]; };
123+
124+
if (i < 0) {
125+
*dest++ = '-';
126+
}
127+
128+
auto b = dest;
129+
while (i != 0) {
130+
auto digit = to_digit(abs(i % s.base));
131+
i /= s.base;
132+
*dest++ = digit;
133+
}
134+
135+
auto e = dest - 1;
136+
while (b < e) {
137+
auto tmp = *b;
138+
*b++ = *e;
139+
*e-- = tmp;
140+
}
141+
}
142+
return dest;
143+
}
144+
145+
} // namespace detail
146+
147+
template <typename It>
148+
CONSTEVAL auto format_to(It dest, auto fmtstr, auto v) -> void {
149+
constexpr auto fmt = fmtstr();
150+
constexpr auto spec = detail::parse_fmt_spec(fmtstr);
151+
152+
// copy to opening {
153+
auto src = fmt.begin();
154+
while (*src != '{') {
155+
*dest++ = *src++;
156+
}
157+
158+
// skip fmt spec
159+
src += spec.len;
160+
161+
// format the value into the buffer
162+
dest = detail::format_to(spec, dest, v);
163+
164+
// copy the rest of src
165+
while (*src) {
166+
*dest++ = *src++;
167+
}
168+
}
169+
170+
} // namespace fmt
171+
} // namespace v1
172+
} // namespace stdx
173+
174+
#endif

test/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20)
8585
indexed_tuple
8686
tuple
8787
tuple_algorithms)
88+
89+
add_unit_test(
90+
"ct_format_freestanding_test"
91+
CATCH2
92+
FILES
93+
"ct_format.cpp"
94+
LIBRARIES
95+
warnings
96+
stdx)
97+
target_compile_definitions(ct_format_freestanding_test
98+
PRIVATE -DSTDX_FMT_FREESTANDING=1)
8899
endif()
89100

90101
add_subdirectory(fail)

test/ct_format.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33

44
#include <catch2/catch_test_macros.hpp>
55

6+
#include <array>
7+
#include <limits>
8+
#include <string_view>
9+
#include <type_traits>
10+
611
using namespace stdx::ct_string_literals;
712

813
TEST_CASE("detect string format specifiers", "[ct_format]") {
@@ -58,6 +63,29 @@ TEST_CASE("format a compile-time integral argument (CX_VALUE)", "[ct_format]") {
5863
"Hello 42"_fmt_res);
5964
}
6065

66+
TEST_CASE("format a negative compile-time integral argument (CX_VALUE)",
67+
"[ct_format]") {
68+
STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(-42)) ==
69+
"Hello -42"_fmt_res);
70+
}
71+
72+
TEST_CASE("format most negative compile-time integral argument (CX_VALUE)",
73+
"[ct_format]") {
74+
STATIC_REQUIRE(stdx::ct_format<"Hello {}">(
75+
CX_VALUE(std::numeric_limits<int>::min())) ==
76+
"Hello -2147483648"_fmt_res);
77+
}
78+
79+
TEST_CASE("format zero (CX_VALUE)", "[ct_format]") {
80+
STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(0)) ==
81+
"Hello 0"_fmt_res);
82+
}
83+
84+
TEST_CASE("format a char (CX_VALUE)", "[ct_format]") {
85+
STATIC_REQUIRE(stdx::ct_format<"Hello {}orld">(CX_VALUE('w')) ==
86+
"Hello world"_fmt_res);
87+
}
88+
6189
TEST_CASE("format a compile-time integral argument (ct)", "[ct_format]") {
6290
STATIC_REQUIRE(stdx::ct_format<"Hello {}">(stdx::ct<42>()) ==
6391
"Hello 42"_fmt_res);
@@ -73,10 +101,17 @@ TEST_CASE("format a type argument (ct)", "[ct_format]") {
73101
"Hello int"_fmt_res);
74102
}
75103

104+
TEST_CASE("format a compile-time argument with different base", "[ct_format]") {
105+
STATIC_REQUIRE(stdx::ct_format<"Hello 0x{:x}">(CX_VALUE(42)) ==
106+
"Hello 0x2a"_fmt_res);
107+
}
108+
109+
#if not STDX_FMT_FREESTANDING
76110
TEST_CASE("format a compile-time argument with fmt spec", "[ct_format]") {
77111
STATIC_REQUIRE(stdx::ct_format<"Hello {:*>#6x}">(CX_VALUE(42)) ==
78112
"Hello **0x2a"_fmt_res);
79113
}
114+
#endif
80115

81116
namespace {
82117
enum struct E { A };

usage_test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ cpmaddpackage(NAME stdx SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." GIT_TAG HEAD)
1212
add_executable(app main.cpp)
1313
target_link_libraries(app PRIVATE stdx)
1414
if(CPP_IMPLEMENTATION STREQUAL "FREESTANDING")
15-
target_compile_definitions(app PRIVATE SIMULATE_FREESTANDING)
15+
target_compile_definitions(app PRIVATE STDX_FMT_FREESTANDING)
1616
target_compile_options(app PRIVATE -ffreestanding)
1717
endif()

usage_test/main.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
#include <stdx/compiler.hpp>
1010
#include <stdx/concepts.hpp>
1111
#include <stdx/ct_conversions.hpp>
12-
#ifndef SIMULATE_FREESTANDING
1312
#include <stdx/ct_format.hpp>
14-
#endif
1513
#include <stdx/ct_string.hpp>
1614
#include <stdx/cx_map.hpp>
1715
#include <stdx/cx_multimap.hpp>
@@ -35,9 +33,7 @@
3533
#include <stdx/ranges.hpp>
3634
#include <stdx/rollover.hpp>
3735
#include <stdx/span.hpp>
38-
#ifndef SIMULATE_FREESTANDING
3936
#include <stdx/static_assert.hpp>
40-
#endif
4137
#include <stdx/tuple.hpp>
4238
#include <stdx/tuple_algorithms.hpp>
4339
#include <stdx/tuple_destructure.hpp>

0 commit comments

Comments
 (0)