Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(format): add formatter for format_args #96

Merged
merged 9 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,17 @@ jobs:
unit-tests:
strategy:
matrix:
compiler: [ gcc-11, gcc-12, gcc-13, clang-16, clang-17 ]
env: [ {os: ubuntu-22.04, compiler: gcc-11},
{os: ubuntu-22.04, compiler: gcc-12},
{os: ubuntu-24.04, compiler: gcc-13},
{os: ubuntu-24.04, compiler: gcc-14},
{os: ubuntu-22.04, compiler: clang-16},
{os: ubuntu-22.04, compiler: clang-17},
{os: ubuntu-24.04, compiler: clang-18}
]
build_type: [ Debug, Release ]

runs-on: ubuntu-22.04
runs-on: ${{ matrix.env.os }}

steps:
- uses: actions/checkout@v3
Expand Down Expand Up @@ -84,9 +91,15 @@ jobs:
sudo apt update && sudo apt install -y clang-17
echo "CC=clang-17" >> $GITHUB_ENV
echo "CXX=clang++-17" >> $GITHUB_ENV
elif [[ "$compiler" == "clang-18" ]]; then
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main'
sudo apt update && sudo apt install -y clang-18
echo "CC=clang-18" >> $GITHUB_ENV
echo "CXX=clang++-18" >> $GITHUB_ENV
fi
env:
compiler: ${{ matrix.compiler }}
compiler: ${{ matrix.env.compiler }}

- name: Configure
run: cmake --preset=ci-ubuntu -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
Expand Down
56 changes: 50 additions & 6 deletions include/emio/detail/format/format_to.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
#include "../../writer.hpp"
#include "../validated_string.hpp"
#include "args.hpp"
#include "formatter.hpp"
#include "parser.hpp"

namespace emio::detail::format {
namespace emio {

namespace detail::format {

struct format_trait {
template <typename... Args>
[[nodiscard]] static constexpr bool validate_string(std::string_view format_str) noexcept {
if (EMIO_Z_INTERNAL_IS_CONST_EVAL) {
return validate<format_specs_checker>(format_str, sizeof...(Args), std::type_identity<Args>{}...);
} else {
return validate<format_specs_checker>(format_str, sizeof...(Args),
make_validation_args<format_validation_arg, Args...>());
return validate<format_specs_checker>(
format_str, sizeof...(Args), related_format_args{make_validation_args<format_validation_arg, Args...>()});
}
}
};
Expand All @@ -39,12 +42,13 @@ inline result<void> vformat_to(buffer& buf, const format_args& args) noexcept {
if (args.is_plain_str()) {
return wtr.write_str(str);
}
return parse<format_parser>(str, wtr, args);
return parse<format_parser>(str, wtr, related_format_args{args});
}

// Constexpr version.
template <typename... Args>
constexpr result<void> format_to(buffer& buf, format_string<Args...> format_string, const Args&... args) noexcept {
constexpr result<void> format_to(buffer& buf, const format_string<Args...>& format_string,
const Args&... args) noexcept {
EMIO_TRY(const std::string_view str, format_string.get());
writer wtr{buf};
if (format_string.is_plain_str()) {
Expand All @@ -53,4 +57,44 @@ constexpr result<void> format_to(buffer& buf, format_string<Args...> format_stri
return parse<format_parser>(str, wtr, args...);
}

} // namespace emio::detail::format
} // namespace detail::format

/**
* Formatter for format_args.
*/
template <>
class formatter<detail::format::format_args> {
public:
static constexpr result<void> validate(reader& format_rdr) noexcept {
return format_rdr.read_if_match_char('}');
}

static constexpr result<void> parse(reader& format_rdr) noexcept {
return format_rdr.read_if_match_char('}');
}

static result<void> format(writer& out, const detail::format::format_args& arg) noexcept {
return detail::format::vformat_to(out.get_buffer(), arg);
}

static constexpr bool format_can_fail = true;
};

/**
* Formatter for types which inherit from format_args.
*/
template <typename T>
requires(std::is_base_of_v<detail::format::format_args, T>)
class formatter<T> : public formatter<detail::format::format_args> {};

namespace detail::format {

template <typename T>
requires(std::is_base_of_v<detail::format::format_args, T>)
struct unified_type<T> {
using type = const detail::format::format_args&;
};

} // namespace detail::format

} // namespace emio
7 changes: 7 additions & 0 deletions include/emio/detail/format/formatter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,13 @@ inline constexpr result<void> check_string_specs(const format_specs& specs) noex
template <typename Arg>
inline constexpr bool has_formatter_v = std::is_constructible_v<formatter<Arg>>;

template <typename Arg>
inline constexpr bool format_can_fail_v = false;

template <typename Arg>
requires requires(Arg) { formatter<Arg>::format_can_fail; }
inline constexpr bool format_can_fail_v<Arg> = formatter<Arg>::format_can_fail;

template <typename T>
concept has_validate_function_v = requires {
{ formatter<T>::validate(std::declval<reader&>()) } -> std::same_as<result<void>>;
Expand Down
21 changes: 10 additions & 11 deletions include/emio/detail/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,14 @@ class parser_base<input_validation::disabled> {
uint8_t increment_arg_number_{};
};

template <typename T>
int is_arg_span2(const args_span<T>& t);

bool is_arg_span2(...);
// Wrapper around an args_span of the string to be parsed so that it can be distinguished from a normal argument.
template <typename Arg>
struct related_format_args {
const args_span<Arg>& args;
};

template <typename T>
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): only used within type traits
constexpr bool is_args_span = sizeof(is_arg_span2(std::declval<T>())) == sizeof(int);
template <typename Arg>
related_format_args(const args_span<Arg>&) -> related_format_args<Arg>;

template <typename CRTP, input_validation Validation>
class parser : public parser_base<Validation> {
Expand All @@ -199,9 +199,9 @@ class parser : public parser_base<Validation> {
parser& operator=(parser&&) = delete;
constexpr ~parser() noexcept override; // NOLINT(performance-trivially-destructible): See definition.

template <typename T>
result<void> apply(uint8_t arg_nbr, const args_span<T>& args) noexcept {
return static_cast<CRTP*>(this)->process_arg(args.get_args()[arg_nbr]);
template <typename Arg>
result<void> apply(uint8_t arg_nbr, const related_format_args<Arg>& args) noexcept {
return static_cast<CRTP*>(this)->process_arg(args.args.get_args()[arg_nbr]);
}

// NOLINTNEXTLINE(readability-convert-member-functions-to-static): not possible because of template function
Expand All @@ -210,7 +210,6 @@ class parser : public parser_base<Validation> {
}

template <typename Arg, typename... Args>
requires(!is_args_span<Arg>)
constexpr result<void> apply(uint8_t arg_pos, Arg& arg, Args&... args) noexcept {
if (arg_pos == 0) {
return static_cast<CRTP*>(this)->process_arg(arg);
Expand Down
6 changes: 3 additions & 3 deletions include/emio/detail/scan/scan_from.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct scan_trait {
return validate<scan_specs_checker>(format_str, sizeof...(Args), std::type_identity<Args>{}...);
} else {
return validate<scan_specs_checker>(format_str, sizeof...(Args),
make_validation_args<scan_validation_arg, Args...>());
related_format_args{make_validation_args<scan_validation_arg, Args...>()});
}
}
};
Expand All @@ -37,11 +37,11 @@ inline result<void> vscan_from(reader& in, const scan_args& args) noexcept {
if (args.is_plain_str()) {
return in.read_if_match_str(str);
}
return parse<scan_parser>(str, in, args);
return parse<scan_parser>(str, in, related_format_args{args});
}

template <typename... Args>
constexpr result<void> scan_from(reader& in, format_string<Args...> format_str, Args&... args) noexcept {
constexpr result<void> scan_from(reader& in, const format_string<Args...>& format_str, Args&... args) noexcept {
EMIO_TRY(const std::string_view str, format_str.get());
if (format_str.is_plain_str()) {
return in.read_if_match_str(str);
Expand Down
Loading
Loading