Skip to content

Commit

Permalink
Merge branch 'main' into sean/implicit-string-conv
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmiddleditch committed Jan 14, 2024
2 parents 79b718e + b979434 commit 8c60e5c
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

- name: Test
working-directory: ${{ github.workspace }}/build
run: ctest -C ${{ matrix.config }}
run: ctest --no-tests=error -C ${{ matrix.config }}

- name: Install
working-directory: ${{ github.workspace }}/build
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ add_subdirectory(source)
add_subdirectory(include/nanofmt)

if (NANOFMT_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

Expand Down
47 changes: 46 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Formatting

The format API is available in the header ``nanofmt/format.h``.

The header ``nanofmt/forward.h`` offers forward declarations of
nanofmt types, including the ``formatter<T>`` template that users
must specialize to support custom types.

Extensions for C++ standard library string types are in the header
``nanofmt/std_string.h``.

Expand All @@ -24,10 +28,18 @@ The :cpp:func:`nanofmt::format_to` functions format a given format string
and arguments into the target buffer. The result will be NUL-terminated.
The return value is a pointer to the terminating NUL character.

The :cpp:func:`nanofmt::format_append_to` functions format a given format
string and arguments onto the end of target buffer. The result will be NUL-
terminated. The return value is a pointer to the terminating NUL character.

.. cpp:function:: char* nanofmt::format_to(char (&dest)[N], format_string format_str, Args const&... args)

.. cpp:function:: char* nanofmt::vformat_to(char (&dest)[N], format_string format_str, format_args args)

.. cpp:function:: char* nanofmt::format_append_to(char (&dest)[N], format_string format_str, Args const&... args)

.. cpp:function:: char* nanofmt::vformat_append_to(char (&dest)[N], format_string format_str, format_args args)

Length-Delimited Formatting
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -36,10 +48,19 @@ and arguments into the target buffer, up to the given number of characters.
The result will **NOT** be NUL-terminated. The return value is a pointer to
one past the last character written.

The :cpp:func:`nanofmt::format_append_to_n` functions format a given format
string and arguments onto the end of the target buffer, up to the given
number of characters. The result will **NOT** be NUL-terminated. The return
value is a pointer to one past the last character written.

.. cpp:function:: char* nanofmt::format_to_n(char* dest, std::size_t count, format_string format_str, Args const&... args)

.. cpp:function:: char* nanofmt::vformat_to_n(char* dest, std::size_t count, format_string format_str, format_args&&)

.. cpp:function:: char* nanofmt::format_append_to_n(char* dest, std::size_t count, format_string format_str, Args const&... args)

.. cpp:function:: char* nanofmt::vformat_append_to_n(char* dest, std::size_t count, format_string format_str, format_args&&)

Custom Formatters
^^^^^^^^^^^^^^^^^

Expand All @@ -63,6 +84,26 @@ specialized structure for nanofmt to work.

Formats ``value`` to ``out``.

A header implementing a custom formatter may choose to only depend on
``nanofmt/foward.h`` header. This header does not offer any of the
implementations, nor does it provide declarations of the formatting
functions. A formatter may work around this by specifying the
``format_output&`` parameter of ``format`` as a template, as in:

.. code-block:: c++

#include <nanofmt/forward.h>

namespace nanofmt {
template<>
struct formatter<my_type> {
constexpr char const* parse(char const* in, char const*) noexcept;

template <typename OutputT>
void format(my_type const& value, OutputT& output);
}
}

Format Length
^^^^^^^^^^^^^

Expand Down Expand Up @@ -156,14 +197,18 @@ General string utiltities that are useful in implementing formatting.

Copies the provided character ``ch`` to the destination buffer, but not
extending past the provided buffer end pointer. Returns the pointer past
the last character written.
the last character written.

.. cpp:function:: char* fill_n(char* dest, char const* end, char ch, std::size_t count) noexcept

Copies ``count`` copies of the charcter ``ch`` to the destination buffer,
but not extending past the provided buffer end pointer. Returns the
pointer past the last character written.

.. cpp:function:: std::size_t strnlen(char const* buffer, std::size_t count) noexcept

Returns the length of the string in buffer, to a maximum of count.

Format Strings
^^^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions include/nanofmt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ target_sources(nanofmt PRIVATE
"config.h"
"format.h"
"format.inl"
"forward.h"
"std_string.h"
)
4 changes: 4 additions & 0 deletions include/nanofmt/config.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details.

#ifndef NANOFMT_CONFIG_H_
#define NANOFMT_CONFIG_H_ 1
#pragma once

#if !defined(NANOFMT_NS)
Expand All @@ -22,3 +24,5 @@
#if !defined(NANOFMT_GSL_SUPPRESS)
# define NANOFMT_GSL_SUPPRESS(rule)
#endif

#endif
27 changes: 24 additions & 3 deletions include/nanofmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ namespace NANOFMT_NS {
template <typename T>
struct formatter;

/// Overload to support converting user-defined string types to format_string.
template <typename StringT>
constexpr format_string to_format_string(StringT const& value) noexcept;

// ----------------------
// String Utilities
// ----------------------
Expand Down Expand Up @@ -82,9 +86,9 @@ namespace NANOFMT_NS {
/// pointer past the last character written.
[[nodiscard]] constexpr char* fill_n(char* dest, char const* end, char ch, std::size_t count) noexcept;

/// Overload to support converting user-defined string types to format_string.
template <typename StringT>
constexpr format_string to_format_string(StringT const& value) noexcept;
/// Finds the first NUL character in the target buffer. Returns the length
/// of the buffer if no NUL is found.
[[nodiscard]] constexpr std::size_t strnlen(char const* buffer, std::size_t count) noexcept;

// ----------------------
// Format API
Expand Down Expand Up @@ -118,6 +122,18 @@ namespace NANOFMT_NS {

[[nodiscard]] inline std::size_t vformat_length(format_string format_str, format_args args);

template <std::size_t N, typename... Args>
[[nodiscard]] char* format_append_to(char* dest, std::size_t count, format_string format_str, Args const&... args);

template <std::size_t N, typename... Args>
[[nodiscard]] char* vformat_append_to(char* dest, std::size_t count, format_string format_str, format_args args);

template <std::size_t N, typename... Args>
char* format_append_to(char (&dest)[N], format_string format_str, Args const&... args);

template <std::size_t N, typename... Args>
char* vformat_append_to(char (&dest)[N], format_string format_str, format_args args);

// ----------------------
// Format Args
// ----------------------
Expand Down Expand Up @@ -153,6 +169,11 @@ namespace NANOFMT_NS {
};
} // namespace detail

template <typename T>
struct formatter {
formatter() = delete; // formatters must be default-constructible
};

template <>
struct formatter<char> : detail::default_formatter<char> {};
template <>
Expand Down
70 changes: 56 additions & 14 deletions include/nanofmt/format.inl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ namespace NANOFMT_NS {
template <typename ValueT>
constexpr format_arg make_format_arg(ValueT const& value) noexcept;

template <typename T, typename = void>
struct has_formatter;
template <typename T>
struct value_type_map;

Expand Down Expand Up @@ -180,6 +178,14 @@ namespace NANOFMT_NS {
return copy_to_n(dest, end, pad_buffer, count);
}

constexpr std::size_t strnlen(char const* buffer, std::size_t count) noexcept {
char const* const end = buffer + count;
for (char const* pos = buffer; pos != end; ++pos)
if (*pos == '\0')
return pos - buffer;
return count;
}

constexpr format_output& format_output::append(char const* const zstr) noexcept {
char* const p = copy_to(pos, end, zstr);
std::size_t const consumed = p - pos;
Expand Down Expand Up @@ -275,6 +281,49 @@ namespace NANOFMT_NS {
return pos;
}

template <std::size_t N>
char* vformat_to(char (&dest)[N], format_string format_str, format_args args) {
char* const pos = detail::vformat(format_output{dest, dest + (N - 1 /*NUL*/)}, format_str, args).pos;
*pos = '\0';
return pos;
}

template <std::size_t N, typename... Args>
[[nodiscard]] char* format_append_to_n(
char* dest,
std::size_t count,
format_string format_str,
Args const&... args) {
std::size_t const start = ::NANOFMT_NS::strnlen(dest, N);
return detail::vformat(
format_output{dest + start, dest + count},
format_str,
::NANOFMT_NS::make_format_args(args...))
.pos;
}

template <std::size_t N, typename... Args>
[[nodiscard]] char* vformat_append_to_n(char* dest, std::size_t count, format_string format_str, format_args args) {
std::size_t const start = ::NANOFMT_NS::strnlen(dest, N);
return detail::vformat(format_output{dest + start, dest + count}, format_str, args).pos;
}

template <std::size_t N, typename... Args>
char* format_append_to(char (&dest)[N], format_string format_str, Args const&... args) {
return vformat_append_to(dest, format_str, ::NANOFMT_NS::make_format_args(args...));
}

template <std::size_t N, typename... Args>
char* vformat_append_to(char (&dest)[N], format_string format_str, format_args args) {
std::size_t const start = ::NANOFMT_NS::strnlen(dest, N);
if (start == N) {
return dest + N;
}
char* const pos = detail::vformat(format_output{dest + start, dest + (N - 1 /*NUL*/)}, format_str, args).pos;
*pos = '\0';
return pos;
}

template <typename... Args>
[[nodiscard]] std::size_t format_length(format_string format_str, Args const&... args) {
return detail::vformat(format_output{}, format_str, ::NANOFMT_NS::make_format_args(args...)).advance;
Expand All @@ -285,18 +334,11 @@ namespace NANOFMT_NS {
}

namespace detail {
template <typename T, typename>
struct has_formatter {
static constexpr bool value = false;
};
template <typename T>
struct has_formatter<
T,
std::void_t<
decltype(formatter<T>{}.parse("", "")),
decltype(formatter<T>{}.format(declval<T>(), format_output{}))>> {
static constexpr bool value = true;
};
using has_formatter = std::is_default_constructible<::NANOFMT_NS::formatter<T>>;

template <typename T>
constexpr bool always_false_v = false;

template <typename T>
struct value_type_map {
Expand Down Expand Up @@ -347,7 +389,7 @@ namespace NANOFMT_NS {
return static_cast<typename detail::value_type_map<std::underlying_type_t<ValueT>>::type>(value);
}
else {
return {};
static_assert(always_false_v<ValueT>, "Type has no nanofmt::formatter<> specialization");
}
}
} // namespace detail
Expand Down
19 changes: 19 additions & 0 deletions include/nanofmt/forward.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details.

#ifndef NANOFMT_FORWARD_H_
#define NANOFMT_FORWARD_H_ 1
#pragma once

#include "config.h"

namespace NANOFMT_NS {
struct format_arg;
struct format_args;
struct format_string;
struct format_string_view;
struct format_output;
template <typename T>
struct formatter;
} // namespace NANOFMT_NS

#endif
5 changes: 2 additions & 3 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ target_sources(nanofmt_test PRIVATE
"test_format.cpp"
"test_format_args.cpp"
"test_utils.h"
)
"fwd_only_type.h")
target_link_libraries(nanofmt_test PRIVATE
nanofmt
Catch2::Catch2WithMain
)

include(Catch)
catch_discover_tests(nanofmt_test)
add_test(NAME nanofmt_test COMMAND nanofmt_test)
25 changes: 25 additions & 0 deletions tests/fwd_only_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details.

#pragma once

#if defined(NANOFMT_FORMAT_H_)
# error "fwd_only_type.h should be included before format.h for the test to be correct"
#endif

#include "nanofmt/forward.h"

struct fwd_only_type {};

namespace NANOFMT_NS {
template <>
struct formatter<fwd_only_type> {
constexpr char const* parse(char const* in, char const*) noexcept {
return in;
}

template <typename OutputT>
void format(fwd_only_type, OutputT& output) {
output.append("fwd_only_type");
}
};
} // namespace NANOFMT_NS
Loading

0 comments on commit 8c60e5c

Please sign in to comment.