From 4c65dda58ab61ba447fb98e74b17fb43a2a9c01e Mon Sep 17 00:00:00 2001 From: marehr Date: Fri, 2 Aug 2024 14:02:03 +0200 Subject: [PATCH] [FEATURE] seqan3::default_printer FEATURE: add seqan3::default_printer This PR will resolve seqan/product_backlog#63. This issue is a long-standing open to-do of mine. I hope that you can take it over and push it over the finishing line. The state of the current PR is just a draft of an idea. I'll comment on multiple code locations to point out the advantages of the new design. The major idea of the design is due to the following observation: > We use overload resolution and in particular overload ordering via concepts to determine the order in which we should print Just to give a small example (the more down, the more specialized): ```cpp std::cout //-printable [1] < seqan3::tuple_like // [4] < std::ranges::input_range // [2] < std::vector // [3] < seqan3::sequence // [3] < char * // [2] std::cout //-printable [1] < char // [5] < seqan3::tuple_like // [4] < seqan3::alphabet // [5] < seqan3::mask // [6] ``` NOTE: that using concepts as overload resolution always uses a partially ordered set, which can be depicted by as a [Hasse Diagram](https://en.wikipedia.org/wiki/Hasse_diagram), and by using the except clauses via `requires` we give it a total order. The idea is simple: * Have a list of printers. * The order of the printers dictates in which order an object should be printed. * We allow that multiple printers might be viable to print a type. * Each `printer` either has a function object / lambda `print` or not; depending on whether the `printer` can print that `type` or not (implemented by [template specialization](https://en.cppreference.com/w/cpp/language/template_specialization)) * We can explicitly use `printer` in other printer's, if we know that only that overload should be used, So put together: For a given type, ask every printer in order whether it can print that type and the first one to answer yes, is the selected printer. ---- [1] If all overloads do not work, use `std::ostream` https://github.com/seqan/seqan3/blob/6b681fb2eae5ab2997d293e99fc6a7f869a20316/include/seqan3/core/debug_stream/debug_stream_type.hpp#L242-L247 [2] Use this for all `std::ranges::input_range`s except if type is something like `std::filesystem` (type == range_value_t) or `char *` https://github.com/seqan/seqan3/blob/6b681fb2eae5ab2997d293e99fc6a7f869a20316/include/seqan3/core/debug_stream/range.hpp#L96-L98 https://github.com/seqan/seqan3/blob/6b681fb2eae5ab2997d293e99fc6a7f869a20316/include/seqan3/core/debug_stream/range.hpp#L38-L45 [3] Same as [2] where value_type is an alphabet but only if the alphabet is not an `unsigned int` (this condition has no test case?!) https://github.com/seqan/seqan3/blob/6b681fb2eae5ab2997d293e99fc6a7f869a20316/include/seqan3/core/debug_stream/range.hpp#L138-L141 [4] Use this for all `std::tuple`-like types except if it is a `std::ranges::input_range` (what is a tuple and ranges at the same time?!) and an `seqan3::alphabet` (basically `seqan3::alphabet_tuple_base` derived types) https://github.com/seqan/seqan3/blob/6b681fb2eae5ab2997d293e99fc6a7f869a20316/include/seqan3/core/debug_stream/tuple.hpp#L53-L56 [5] Use this for all `seqan3::alphabet`s except if it can be printed by `std::cout` (like `char`) https://github.com/seqan/seqan3/blob/6b681fb2eae5ab2997d293e99fc6a7f869a20316/include/seqan3/alphabet/detail/debug_stream_alphabet.hpp#L30-L32 [6] Type must be `seqan3::semialphabet` and `seqan3::mask` https://github.com/seqan/seqan3/blob/6b681fb2eae5ab2997d293e99fc6a7f869a20316/include/seqan3/alphabet/detail/debug_stream_alphabet.hpp#L46-L48 Co-authored-by: seqan-actions[bot] Co-authored-by: rrahn Co-authored-by: Enrico Seiler --- .../debug_stream_alignment.hpp | 38 +-- .../advanceable_alignment_coordinate.hpp | 31 +-- .../alignment/matrix/detail/debug_matrix.hpp | 82 ++++-- .../matrix/detail/trace_directions.hpp | 85 +++++-- .../alignment/pairwise/alignment_result.hpp | 98 ++++---- include/seqan3/alphabet/cigar/cigar.hpp | 25 +- include/seqan3/alphabet/concept.hpp | 3 +- .../alphabet/detail/debug_stream_alphabet.hpp | 65 +++-- include/seqan3/argument_parser/auxiliary.hpp | 44 ++-- include/seqan3/core/debug_stream/byte.hpp | 29 ++- .../core/debug_stream/debug_stream_type.hpp | 139 +++++++---- .../core/debug_stream/default_printer.hpp | 236 ++++++++++++++++++ include/seqan3/core/debug_stream/optional.hpp | 68 ++--- include/seqan3/core/debug_stream/range.hpp | 210 ++++++++-------- include/seqan3/core/debug_stream/tuple.hpp | 48 ++-- include/seqan3/core/debug_stream/variant.hpp | 55 ++-- include/seqan3/core/detail/strong_type.hpp | 49 ++-- include/seqan3/io/sam_file/sam_flag.hpp | 24 +- include/seqan3/io/structure_file/detail.hpp | 3 +- include/seqan3/search/search_result.hpp | 55 ++-- .../utility/container/dynamic_bitset.hpp | 41 +-- .../utility/simd/detail/debug_stream_simd.hpp | 34 ++- .../detail/debug_stream_debug_matrix_test.cpp | 24 ++ .../debug_stream_trace_directions_test.cpp | 17 ++ test/unit/io/record_test.cpp | 1 + test/unit/test/pretty_printing_test.cpp | 16 ++ 26 files changed, 1010 insertions(+), 510 deletions(-) create mode 100644 include/seqan3/core/debug_stream/default_printer.hpp diff --git a/include/seqan3/alignment/aligned_sequence/debug_stream_alignment.hpp b/include/seqan3/alignment/aligned_sequence/debug_stream_alignment.hpp index c78b1f920d..535dc88c8b 100644 --- a/include/seqan3/alignment/aligned_sequence/debug_stream_alignment.hpp +++ b/include/seqan3/alignment/aligned_sequence/debug_stream_alignment.hpp @@ -91,27 +91,31 @@ void stream_alignment(debug_stream_type & stream, namespace seqan3 { -/*!\brief Stream operator for alignments, which are represented as tuples of aligned sequences. + +/*!\brief The printer for alignment. + * \tparam alignment_t The type of the alignment; must model seqan3::tuple_like and all sequences must be + * seqan3::aligned_sequence. * \ingroup alignment_aligned_sequence - * - * \tparam alignment_t The alignment type, must satisfy tuple_like and its size must be at least 2. - * - * \param[in,out] stream The target stream for the formatted output. - * \param[in] alignment The alignment that shall be formatted. All sequences must be equally long. - * - * \return The given stream to which the alignment representation is appended. */ -template - requires (detail::debug_streamable_tuple - && detail::all_model_aligned_seq>>) -inline debug_stream_type & operator<<(debug_stream_type & stream, alignment_t && alignment) +template + requires tuple_like && detail::all_model_aligned_seq> +struct alignment_printer { - constexpr size_t sequence_count = std::tuple_size_v>; + /*!\brief The function call operator that pretty prints the alignment to the stream. + * \tparam stream_t The type of the stream. + * \tparam arg_t The type of the argument. + * \param[in,out] stream The target stream for the formatted output. + * \param[in] arg The alignment that shall be formatted. All sequences must be equally long. + */ + template + constexpr void operator()(stream_t & stream, arg_t && arg) const + { + constexpr size_t sequence_count = std::tuple_size_v>; - static_assert(sequence_count >= 2, "An alignment requires at least two sequences."); + static_assert(sequence_count >= 2, "An alignment requires at least two sequences."); - detail::stream_alignment(stream, alignment, std::make_index_sequence{}); - return stream; -} + detail::stream_alignment(stream, std::forward(arg), std::make_index_sequence{}); + } +}; } // namespace seqan3 diff --git a/include/seqan3/alignment/matrix/detail/advanceable_alignment_coordinate.hpp b/include/seqan3/alignment/matrix/detail/advanceable_alignment_coordinate.hpp index 5100314105..23af51f03a 100644 --- a/include/seqan3/alignment/matrix/detail/advanceable_alignment_coordinate.hpp +++ b/include/seqan3/alignment/matrix/detail/advanceable_alignment_coordinate.hpp @@ -294,23 +294,26 @@ class advanceable_alignment_coordinate namespace seqan3 { -/*!\brief A seqan3::detail::advanceable_alignment_coordinate can be printed to the seqan3::debug_stream. - * \tparam coordinate_type The alignment coordinate type. - * \param[in] s The seqan3::debug_stream. - * \param[in] c The alignment coordinate to print. - * \relates seqan3::debug_stream_type +/*!\brief The printer for seqan3::detail::advanceable_alignment_coordinate. * - * \details + * Prints the alignment coordinate as a tuple of the column and row index. * - * Prints the alignment coordinate as a tuple. + * \tparam state_t The state of the detail::advanceable_alignment_coordinate. + * \ingroup alignment_matrix */ -template - requires detail::is_value_specialisation_of_v, - detail::advanceable_alignment_coordinate> -inline debug_stream_type & operator<<(debug_stream_type & s, coordinate_type && c) +template +struct advanceable_alignment_coordinate_printer> { - s << std::tie(c.first, c.second); - return s; -} + /*!\brief The function call operator that prints the coordinate to the given stream. + * \tparam stream_t The type of the stream. + * \param[in,out] stream The stream to print to. + * \param[in] arg The alignment coordinate to print. + */ + template + constexpr void operator()(stream_t & stream, detail::advanceable_alignment_coordinate const arg) const + { + stream << std::tie(arg.first, arg.second); + } +}; } // namespace seqan3 diff --git a/include/seqan3/alignment/matrix/detail/debug_matrix.hpp b/include/seqan3/alignment/matrix/detail/debug_matrix.hpp index 615abe26b5..302dff33a4 100644 --- a/include/seqan3/alignment/matrix/detail/debug_matrix.hpp +++ b/include/seqan3/alignment/matrix/detail/debug_matrix.hpp @@ -467,33 +467,67 @@ debug_matrix(matrix_t &&, namespace seqan3 { -/*!\brief An alignment matrix can be printed to the seqan3::debug_stream. - * \tparam alignment_matrix_t Type of the alignment matrix to be printed; must model seqan3::detail::matrix. - * \param s The seqan3::debug_stream. - * \param matrix The alignment matrix. - * \relates seqan3::debug_stream_type + +/*!\brief The printer for alignment scoring and trace matrices. * - * \details + * Prints the alignment matrix to the given formatted ouput stream. * - * This prints out an alignment matrix which can be a score matrix or a trace matrix. + * \tparam alignment_matrix_t The type of the alignment matrix; must model seqan3::detail::matrix. + * \ingroup alignment_matrix */ -template -inline debug_stream_type & operator<<(debug_stream_type & s, alignment_matrix_t && matrix) +template +struct alignment_matrix_printer { - detail::debug_matrix debug{std::forward(matrix)}; - - std::stringstream sstream{}; - debug.stream_matrix(sstream, s.flags2()); - s << sstream.str(); - return s; -} - -//!\overload -template - requires detail::debug_stream_range_guard && detail::matrix -inline debug_stream_type & operator<<(debug_stream_type & s, alignment_matrix_t && matrix) -{ - return s << detail::debug_matrix{std::forward(matrix)}; -} + /*!\brief Prints the alignment matrix into the given stream using formatting specified by seqan3::fmtflags2. + * + * This overload is selected if the stream is a seqan3::debug_stream_type and has a `flags2()` member function that + * returns a seqan3::fmtflags2 object. + * Using the flags2() member function allows to print the matrix with unicode characters if seqan3::fmtflags2::utf8 + * is set to the seqan3::debug_stream. + * + * \tparam stream_t The type of the stream. + * \tparam arg_t The type of the argument. + * \param stream The stream to print to. + * \param arg The alignment matrix to print + */ + template + requires detail::is_type_specialisation_of_v + constexpr void operator()(stream_t & stream, arg_t && arg) const + { + print_impl(stream.get_underlying_stream(), stream.flags2(), std::forward(arg)); + } + + /*!\brief Prints the alignment matrix into the given stream using ascii formatting. + * + * This overload is selected if the stream is \b not a seqan3::debug_stream_type and always prints + * with seqan3::fmtflags2::none. + * + * \tparam stream_t The type of the stream. + * \tparam arg_t The type of the argument. + * \param stream The stream to print to. + * \param arg The alignment matrix to print. + */ + template + constexpr void operator()(stream_t & stream, arg_t && arg) const + { + print_impl(stream, fmtflags2::none, std::forward(arg)); + } + +private: + /*!\brief Prints the alignment matrix into the given formatted output stream. + * \tparam stream_t The type of the stream. + * \tparam arg_t The type of the argument. + * \param stream The stream to print to. + * \param flags The flags to modify the way the matrix is printed. + * \param arg The alignment matrix to print. + */ + template + void print_impl(stream_t & stream, fmtflags2 const flags, arg_t && arg) const + { + detail::debug_matrix debug{std::forward(arg)}; + + debug.stream_matrix(stream, flags); + } +}; } // namespace seqan3 diff --git a/include/seqan3/alignment/matrix/detail/trace_directions.hpp b/include/seqan3/alignment/matrix/detail/trace_directions.hpp index 1cb9648d94..f518a95b79 100644 --- a/include/seqan3/alignment/matrix/detail/trace_directions.hpp +++ b/include/seqan3/alignment/matrix/detail/trace_directions.hpp @@ -9,8 +9,12 @@ #pragma once +#include +#include + #include #include +#include namespace seqan3::detail { @@ -54,12 +58,7 @@ template <> inline constexpr bool add_enum_bitwise_operators = true; //!\endcond -/*!\brief All trace_directions can be printed as ascii or as utf8 to the seqan3::debug_stream. - * \param s The seqan3::debug_stream. - * \param trace The trace direction. - * \relates seqan3::debug_stream_type - * - * \details +/*!\brief Prints `trace_directions` as ascii or as utf8 to output stream. * * The following table shows the printed symbol of a particular seqan3::detail::trace_directions: * @@ -71,23 +70,71 @@ inline constexpr bool add_enum_bitwise_operators -inline debug_stream_type & operator<<(debug_stream_type & s, detail::trace_directions const trace) +template <> +struct trace_directions_printer { - static char const * unicode[32]{"↺", "↖", "↑", "↖↑", "⇡", "↖⇡", "↑⇡", "↖↑⇡", "←", "↖←", "↑←", - "↖↑←", "⇡←", "↖⇡←", "↑⇡←", "↖↑⇡←", "⇠", "↖⇠", "↑⇠", "↖↑⇠", "⇡⇠", "↖⇡⇠", - "↑⇡⇠", "↖↑⇡⇠", "←⇠", "↖←⇠", "↑←⇠", "↖↑←⇠", "⇡←⇠", "↖⇡←⇠", "↑⇡←⇠", "↖↑⇡←⇠"}; +private: + //!\brief The unicode representation of the trace directions. + static constexpr std::array unicode{ + "↺", "↖", "↑", "↖↑", "⇡", "↖⇡", "↑⇡", "↖↑⇡", "←", "↖←", "↑←", "↖↑←", "⇡←", "↖⇡←", "↑⇡←", "↖↑⇡←", + "⇠", "↖⇠", "↑⇠", "↖↑⇠", "⇡⇠", "↖⇡⇠", "↑⇡⇠", "↖↑⇡⇠", "←⇠", "↖←⇠", "↑←⇠", "↖↑←⇠", "⇡←⇠", "↖⇡←⇠", "↑⇡←⇠", "↖↑⇡←⇠"}; + + //!\brief The ascii representation of the trace directions. + static constexpr std::array csv{ + "N", "D", "U", "DU", "u", "Du", "Uu", "DUu", "L", "DL", "UL", "DUL", "uL", "DuL", "UuL", "DUuL", + "l", "Dl", "Ul", "DUl", "ul", "Dul", "Uul", "DUul", "Ll", "DLl", "ULl", "DULl", "uLl", "DuLl", "UuLl", "DUuLl"}; - static char const * csv[32]{"N", "D", "U", "DU", "u", "Du", "Uu", "DUu", "L", "DL", "UL", - "DUL", "uL", "DuL", "UuL", "DUuL", "l", "Dl", "Ul", "DUl", "ul", "Dul", - "Uul", "DUul", "Ll", "DLl", "ULl", "DULl", "uLl", "DuLl", "UuLl", "DUuLl"}; +public: + /*!\brief Prints the trace directions into the given stream. + * + * This overload is only available if the stream has a member function `flags2` that returns a `fmtflags2`. + * Using the flags2() member function allows to print the trace with unicode characters if seqan3::fmtflags2::utf8 + * is set to the seqan3::debug_stream. + * + * \tparam stream_t The type of the stream. + * \param stream The stream to print to. + * \param trace The trace directions to print. + */ + template + requires detail::is_type_specialisation_of_v + constexpr void operator()(stream_t & stream, detail::trace_directions const trace) const + { + print_impl(stream, stream.flags2(), trace); + } - bool is_unicode = (s.flags2() & fmtflags2::utf8) == fmtflags2::utf8; - auto const & trace_dir = is_unicode ? unicode : csv; + /*!\brief Prints the trace directions into the given stream. + * + * This overload is only available if the stream has no member function `flags2`. In this case it will use + * ascii characters to print the trace. + * + * \tparam stream_t The type of the stream. + * \param stream The stream to print to. + * \param trace The trace directions to print. + */ + template + constexpr void operator()(stream_t & stream, detail::trace_directions const trace) const + { + print_impl(stream, fmtflags2::none, trace); + } - s << trace_dir[static_cast(trace)]; - return s; -} +private: + /*!\brief Prints the trace directions + * \tparam stream_t The type of the stream. + * \param stream The stream to print to. + * \param flag The flags of the stream. + * \param trace The trace directions to print. + */ + template + constexpr void print_impl(stream_t & stream, fmtflags2 const flag, detail::trace_directions const trace) const + { + bool const is_unicode = (flag & fmtflags2::utf8) == fmtflags2::utf8; + auto const & trace_dir = is_unicode ? unicode : csv; + + stream << trace_dir[static_cast(trace)]; + } +}; } // namespace seqan3 diff --git a/include/seqan3/alignment/pairwise/alignment_result.hpp b/include/seqan3/alignment/pairwise/alignment_result.hpp index 04774ce76d..8ef58b6751 100644 --- a/include/seqan3/alignment/pairwise/alignment_result.hpp +++ b/include/seqan3/alignment/pairwise/alignment_result.hpp @@ -10,6 +10,7 @@ #pragma once +#include #include #include @@ -171,6 +172,9 @@ class alignment_result requires seqan3::detail::is_type_specialisation_of_v friend class detail::policy_alignment_result_builder; + template + friend struct alignment_result_printer; + public: /*!\name Constructors, destructor and assignment * \{ @@ -401,54 +405,58 @@ struct alignment_result_value_type_accessor> namespace seqan3 { -/*!\brief Streams the seqan3::alignment_result to the seqan3::debug_stream. + +/*!\brief The printer used for formatted output of the alignment result. * - * \tparam char_t The underlying character type of the seqan3::debug_stream_type. - * \tparam alignment_result_t A type specialisation of seqan3::alignment_result. + * The type of the printer must be a seqan3::alignment_result type. * - * \param[in,out] stream The output stream. - * \param[in] result The alignment result to print. - * \relates seqan3::debug_stream_type + * \tparam result_value_t The type of the alignment result value. + * \ingroup alignment_pairwise */ -template - requires detail::is_type_specialisation_of_v, alignment_result> -inline debug_stream_type & operator<<(debug_stream_type & stream, alignment_result_t && result) +template +struct alignment_result_printer> { - using disabled_t = std::nullopt_t *; - using result_data_t = - typename detail::alignment_result_value_type_accessor>::type; - - constexpr bool has_sequence1_id = !std::is_same_v().sequence1_id), disabled_t>; - constexpr bool has_sequence2_id = !std::is_same_v().sequence2_id), disabled_t>; - constexpr bool has_score = !std::is_same_v().score), disabled_t>; - constexpr bool has_end_positions = - !std::is_same_v().end_positions), disabled_t>; - constexpr bool has_begin_positions = - !std::is_same_v().begin_positions), disabled_t>; - constexpr bool has_alignment = !std::is_same_v().alignment), disabled_t>; - - bool prepend_comma = false; - auto append_to_stream = [&](auto &&... args) + /*!\brief Prints the formatted output of the alignment result to the stream. + * \tparam stream_t The type of the stream. + * \tparam arg_t The type of the argument. + * \param[in,out] stream The output stream. + * \param[in] arg The alignment result to print. + */ + template + constexpr void operator()(stream_t & stream, arg_t && arg) const noexcept { - ((stream << (prepend_comma ? std::string{", "} : std::string{})) << ... << std::forward(args)); - prepend_comma = true; - }; - - stream << '{'; - if constexpr (has_sequence1_id) - append_to_stream("sequence1 id: ", result.sequence1_id()); - if constexpr (has_sequence2_id) - append_to_stream("sequence2 id: ", result.sequence2_id()); - if constexpr (has_score) - append_to_stream("score: ", result.score()); - if constexpr (has_begin_positions) - append_to_stream("begin: (", result.sequence1_begin_position(), ",", result.sequence2_begin_position(), ")"); - if constexpr (has_end_positions) - append_to_stream("end: (", result.sequence1_end_position(), ",", result.sequence2_end_position(), ")"); - if constexpr (has_alignment) - append_to_stream("\nalignment:\n", result.alignment()); - stream << '}'; - - return stream; -} + using disabled_t = std::nullopt_t *; + using result_t = std::remove_cvref_t; + constexpr bool has_sequence1_id = !std::is_same_v; + constexpr bool has_sequence2_id = !std::is_same_v; + constexpr bool has_score = !std::is_same_v; + constexpr bool has_end_positions = !std::is_same_v; + constexpr bool has_begin_positions = !std::is_same_v; + constexpr bool has_alignment = !std::is_same_v; + + bool prepend_comma = false; + auto append_to_stream = [&](auto &&... args) + { + ((stream << (prepend_comma ? std::string{", "} : std::string{})) + << ... << std::forward(args)); + prepend_comma = true; + }; + + stream << '{'; + if constexpr (has_sequence1_id) + append_to_stream("sequence1 id: ", arg.sequence1_id()); + if constexpr (has_sequence2_id) + append_to_stream("sequence2 id: ", arg.sequence2_id()); + if constexpr (has_score) + append_to_stream("score: ", arg.score()); + if constexpr (has_begin_positions) + append_to_stream("begin: (", arg.sequence1_begin_position(), ",", arg.sequence2_begin_position(), ")"); + if constexpr (has_end_positions) + append_to_stream("end: (", arg.sequence1_end_position(), ",", arg.sequence2_end_position(), ")"); + if constexpr (has_alignment) + append_to_stream("\nalignment:\n", arg.alignment()); + stream << '}'; + } +}; + } // namespace seqan3 diff --git a/include/seqan3/alphabet/cigar/cigar.hpp b/include/seqan3/alphabet/cigar/cigar.hpp index 1463901d7c..fd006ac69f 100644 --- a/include/seqan3/alphabet/cigar/cigar.hpp +++ b/include/seqan3/alphabet/cigar/cigar.hpp @@ -207,13 +207,26 @@ class cigar : public alphabet_tuple_base -inline debug_stream_type & operator<<(debug_stream_type & s, cigar const c) +/*!\brief The printer used for formatted output of the cigar alphabets. + * + * The type of the printer must be a seqan3::cigar type. + * + * \ingroup alphabet_cigar + */ +template <> +struct cigar_printer { - s << c.to_string(); - return s; -} + /*!\brief Prints the formatted output of the cigar symbol to the stream. + * \tparam stream_t The type of the stream. + * \param[in,out] stream The output stream. + * \param[in] arg The cigar symbol to print. + */ + template + constexpr void operator()(stream_t & stream, cigar const arg) const noexcept + { + stream << arg.to_string(); + } +}; inline namespace literals { diff --git a/include/seqan3/alphabet/concept.hpp b/include/seqan3/alphabet/concept.hpp index 5e43ebf3ce..80e03b063e 100644 --- a/include/seqan3/alphabet/concept.hpp +++ b/include/seqan3/alphabet/concept.hpp @@ -1065,8 +1065,7 @@ concept writable_alphabet = alphabet && writable_semialphabet && requires * * \{ */ -/*! - * \brief Save an alphabet letter to stream. +/*!\brief Save an alphabet letter to stream. * \tparam archive_t Must satisfy seqan3::cereal_output_archive. * \tparam alphabet_t Type of l; must satisfy seqan3::semialphabet. * \param l The alphabet letter. diff --git a/include/seqan3/alphabet/detail/debug_stream_alphabet.hpp b/include/seqan3/alphabet/detail/debug_stream_alphabet.hpp index 9b7e8e0fe7..a75bc026d3 100644 --- a/include/seqan3/alphabet/detail/debug_stream_alphabet.hpp +++ b/include/seqan3/alphabet/detail/debug_stream_alphabet.hpp @@ -15,38 +15,53 @@ namespace seqan3 { -/*!\name Formatted output overloads - * \{ - */ -/*!\brief All alphabets can be printed to the seqan3::debug_stream by their char representation. - * \tparam alphabet_t Type of the alphabet to be printed; must model seqan3::alphabet. - * \param s The seqan3::debug_stream. - * \param l The alphabet letter. - * \relates seqan3::debug_stream_type + +/*!\brief The printer used for formatted output of seqan3::alphabet types. + * + * Prints the char representation of the given alphabet letter. + * + * \tparam alphabet_t The type of the alphabet to be printed. + * \ingroup alphabet */ -template -inline debug_stream_type & operator<<(debug_stream_type & s, alphabet_t && l) - requires (!output_stream_over, alphabet_t>) +template +struct alphabet_printer { - return s << to_char(l); -} + /*!\brief Print the alphabet to the stream + * \tparam stream_t The type of the stream. + * \param[in,out] stream The stream to print to. + * \param[in] letter The alphabet letter. + */ + template + constexpr void operator()(stream_t & stream, alphabet_t const letter) const noexcept + { + stream << to_char(letter); + } +}; // forward declare seqan3::mask class mask; -/*!\brief Overload for the seqan3::mask alphabet. - * \tparam char_t Type char type of the debug_stream. - * \param s The seqan3::debug_stream. - * \param l The mask alphabet letter. - * \relates seqan3::debug_stream_type +/*!\brief The printer used for formatted output of seqan3::mask alphabet. + * + * Prints "MASKED" if the letter is masked and "UNMASKED" otherwise. + * + * \tparam mask_t The type of the alphabet to be printed. Must be seqan3::mask. + * \ingroup alphabet_mask */ -template -inline debug_stream_type & operator<<(debug_stream_type & s, alphabet_t && l) - requires std::same_as, mask> +template mask_t> +struct mask_printer { - return s << (l == alphabet_t{} ? "UNMASKED" : "MASKED"); -} - -//!\} + /*!\brief Print the mask alphabet to the stream + * \tparam stream_t The type of the stream. + * \param[in,out] stream The stream to print to. + * \param[in] arg The mask alphabet letter. + */ + template + constexpr void operator()(stream_t & stream, mask_t const arg) const noexcept + { + // seqan3::mask is incomplete at this point, so we cannot use `arg == mask{}` + stream << (arg == mask_t{} ? "UNMASKED" : "MASKED"); + } +}; } // namespace seqan3 diff --git a/include/seqan3/argument_parser/auxiliary.hpp b/include/seqan3/argument_parser/auxiliary.hpp index d5af167bca..54461e18e8 100644 --- a/include/seqan3/argument_parser/auxiliary.hpp +++ b/include/seqan3/argument_parser/auxiliary.hpp @@ -201,33 +201,39 @@ concept argument_parser_compatible_option = input_stream_over || named_enumeration; //!\endcond -/*!\name Formatted output overloads - * \{ - */ /*!\brief A type (e.g. an enum) can be made debug streamable by customizing the seqan3::enumeration_names. - * \tparam option_type Type of the enum to be printed. - * \param s The seqan3::debug_stream. - * \param op The value to print. - * \relates seqan3::debug_stream_type - * - * \details * * This searches the seqan3::enumeration_names of the respective type for the value \p op and prints the * respective string if found or '\' if the value cannot be found in the map. + * + * \tparam enum_t Type of the enum to be printed; must model seqan3::named_enumeration. + * \ingroup argument_parser */ -template - requires named_enumeration> -inline debug_stream_type & operator<<(debug_stream_type & s, option_type && op) +template +struct enumeration_printer { - for (auto & [key, value] : enumeration_names) + /*!\brief Prints the associated label of the given enum value. + * \tparam stream_t The type of the stream. + * \param[in,out] stream The output stream. + * \param[in] arg The enum value to print. + * + * If for the given enumeration value no enumeration name can be found, "" is printed. + */ + template + constexpr void operator()(stream_t & stream, enum_t const arg) const { - if (op == value) - return s << key; - } + for (auto & [label, enumerator] : enumeration_names) + { + if (arg == enumerator) + { + stream << label; + return; + } + } - return s << ""; -} -//!\} + stream << ""; + } +}; /*!\brief Used to further specify argument_parser options/flags. * \ingroup argument_parser diff --git a/include/seqan3/core/debug_stream/byte.hpp b/include/seqan3/core/debug_stream/byte.hpp index 2c39246f1e..6bccbcf1af 100644 --- a/include/seqan3/core/debug_stream/byte.hpp +++ b/include/seqan3/core/debug_stream/byte.hpp @@ -16,23 +16,22 @@ namespace seqan3 { -/*!\name Formatted output overloads - * \{ - */ /*!\brief A std::byte can be printed by printing its value as integer. - * \tparam byte_type The type of the input; must be equal to `std::byte`. - * \param[in] s The seqan3::debug_stream. - * \param[in] arg The std::byte. - * \relates seqan3::debug_stream_type + * \ingroup core_debug_stream */ -template - requires std::same_as, std::byte> -inline debug_stream_type & operator<<(debug_stream_type & s, byte_type && arg) +template <> +struct std_byte_printer { - s << std::to_integer(arg); - return s; -} - -//!\} + /*!\brief Prints the byte as uint8_t value. + * \tparam stream_t The type of the stream. + * \param[in,out] stream The output stream. + * \param[in] arg The byte argument to print. + */ + template + constexpr void operator()(stream_t & stream, std::byte const arg) const + { + stream << std::to_integer(arg); + } +}; } // namespace seqan3 diff --git a/include/seqan3/core/debug_stream/debug_stream_type.hpp b/include/seqan3/core/debug_stream/debug_stream_type.hpp index 56c74def92..18458972ea 100644 --- a/include/seqan3/core/debug_stream/debug_stream_type.hpp +++ b/include/seqan3/core/debug_stream/debug_stream_type.hpp @@ -9,9 +9,13 @@ #pragma once +#include #include +#include #include +#include +#include namespace seqan3 { @@ -114,6 +118,13 @@ class debug_stream_type { stream = &out; } + + //!\brief Retrieve the underlying stream. + std::basic_ostream & get_underlying_stream() const noexcept + { + assert(stream != nullptr); + return *stream; + } //!\} /*!\name Formatted output @@ -121,7 +132,19 @@ class debug_stream_type */ //!\brief Forwards to the underlying stream object. template - friend debug_stream_type & operator<<(debug_stream_type & s, t && v); + friend debug_stream_type & operator<<(debug_stream_type & s, t && v) + { + if constexpr (printable_with) + { + std::invoke(default_printer{}, s, std::forward(v)); + } + else + { + std::string const msg = "debug_stream has no print overload for type: " + detail::type_name_as_string; + throw std::runtime_error{msg}; + } + return s; + } //!\brief This overloads enables forwarding std::endl and other manipulators. debug_stream_type & operator<<(std::ostream & (*fp)(std::ostream &)) @@ -130,27 +153,14 @@ class debug_stream_type return *this; } - //!\cond - debug_stream_type & operator<<(int8_t const v) - { - if ((flags2() & fmtflags2::small_int_as_number) == fmtflags2::small_int_as_number) - *stream << static_cast(v); - else - *stream << v; - return *this; - } - - debug_stream_type & operator<<(uint8_t const v) - { - if ((flags2() & fmtflags2::small_int_as_number) == fmtflags2::small_int_as_number) - *stream << static_cast(v); - else - *stream << v; - return *this; - } - //!\endcond //!\} + template + friend struct debug_stream_printer; + + template + friend struct std_printer; + //!\brief This type is std::ios_base::fmtflags using fmtflags = typename std::basic_ostream::fmtflags; @@ -182,17 +192,6 @@ class debug_stream_type stream->unsetf(flag); } -// fmtflags is an enum in libstdc++ and an unsigned in libc++ -#ifdef _LIBCPP_VERSION - static_assert(std::same_as); -#else - //!\copybrief setf() - debug_stream_type & operator<<(fmtflags const flag) - { - setf(flag); - return *this; - } -#endif //!\} /*!\name Format flags (seqan3::fmtflags2) @@ -224,12 +223,6 @@ class debug_stream_type flgs2 &= ~flag; } - //!\copybrief setf() - debug_stream_type & operator<<(fmtflags2 const flag) - { - setf(flag); - return *this; - } //!\} private: @@ -240,12 +233,72 @@ class debug_stream_type fmtflags2 flgs2{fmtflags2::default_}; }; -//!\brief Forwards to the underlying stream object. -template -debug_stream_type & operator<<(debug_stream_type & s, t && v) +/*!\brief A struct that provides a debug stream printer for a specific value type. + * + * This struct provides operator() overloads for printing values of type int8_t, uint8_t, and seqan3::fmtflags2 + * to a debug stream. The operator() overloads handle the formatting of the values based on the + * fmtflags2 settings of the debug stream. + * + * \tparam value_t The type of the value to be printed. + * \ingroup core_debug_stream + */ +template + requires (std::is_same_v, int8_t> + || std::is_same_v, uint8_t> + || std::is_same_v, fmtflags2>) +struct debug_stream_printer { - (*s.stream) << v; - return s; -} + /*!\brief Prints an int8_t value to the debug stream. + * + * \tparam char_t The character type of the debug stream. + * \param stream The debug stream to print to. + * \param v The int8_t value to be printed. + * + * This function prints the int8_t value to the debug stream, taking into account the + * fmtflags2 settings of the stream. If the fmtflags2::small_int_as_number flag is set, + * the value is printed as an int, otherwise it is printed as is. + */ + template + constexpr void operator()(debug_stream_type & stream, int8_t const v) const + { + if ((stream.flags2() & fmtflags2::small_int_as_number) == fmtflags2::small_int_as_number) + *stream.stream << static_cast(v); + else + *stream.stream << v; + } + + /*!\brief Prints a uint8_t value to the debug stream. + * + * \tparam char_t The character type of the debug stream. + * \param stream The debug stream to print to. + * \param v The uint8_t value to be printed. + * + * This function prints the uint8_t value to the debug stream, taking into account the + * fmtflags2 settings of the stream. If the fmtflags2::small_int_as_number flag is set, + * the value is printed as an unsigned int, otherwise it is printed as is. + */ + template + constexpr void operator()(debug_stream_type & stream, uint8_t const v) const + { + if ((stream.flags2() & fmtflags2::small_int_as_number) == fmtflags2::small_int_as_number) + *stream.stream << static_cast(v); + else + *stream.stream << v; + } + + /*!\brief Sets the fmtflags2 of the debug stream. + * + * \tparam char_t The character type of the debug stream. + * \param stream The debug stream to set the fmtflags2 for. + * \param flag The fmtflags2 value to set. + * + * This function sets the fmtflags2 of the debug stream to the specified flag value. + */ + template + constexpr void operator()(debug_stream_type & stream, fmtflags2 const flag) const + { + stream.setf(flag); + } +}; } // namespace seqan3 diff --git a/include/seqan3/core/debug_stream/default_printer.hpp b/include/seqan3/core/debug_stream/default_printer.hpp new file mode 100644 index 0000000000..d0f24d6bcc --- /dev/null +++ b/include/seqan3/core/debug_stream/default_printer.hpp @@ -0,0 +1,236 @@ +// SPDX-FileCopyrightText: 2006-2024 Knut Reinert & Freie Universität Berlin +// SPDX-FileCopyrightText: 2016-2024 Knut Reinert & MPI für molekulare Genetik +// SPDX-License-Identifier: BSD-3-Clause + +/*!\file + * \author Marcel Ehrhardt + * \brief Provides seqan3::default_printer. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace seqan3 +{ + +// clang-format off +/*!\brief A tag that indicates that no printer was found for the given type. + * \ingroup core_debug_stream + */ +struct no_printer_found{}; +template struct advanceable_alignment_coordinate_printer {}; +template struct alignment_matrix_printer {}; +template struct alignment_printer {}; +template struct alignment_result_printer {}; +template struct alphabet_printer {}; +template struct cigar_printer {}; +template struct debug_stream_printer {}; +template struct dynamic_bitset_printer {}; +template struct enumeration_printer {}; +template struct input_range_printer {}; +template struct integer_sequence_printer {}; +template struct integral_printer {}; +template struct mask_printer {}; +template struct optional_printer {}; +template struct sam_flag_printer {}; +template struct sequence_printer {}; +template struct search_result_printer {}; +template struct simd_printer {}; +template struct std_byte_printer {}; +template struct std_variant_printer {}; +template struct std_printer {}; +template struct strong_type_printer {}; +template struct char_sequence_printer {}; +template struct trace_directions_printer {}; +template struct tuple_printer {}; +// clang-format on + +/*! + * \interface seqan3::printable_with <> + * \brief The concept for a printable object. + * + * A printable object is a printer that can print an argument to a stream. + * The printer must be invocable with a stream and an argument. + * + * \tparam printer_t The type of the printer. + * \tparam stream_t The type of the stream. + * \tparam arg_t The type of the argument. + * \ingroup core_debug_stream + */ +template +concept printable_with = std::invocable; + +/*!\brief The printer for standard output streams. + * + * The std_printer is used as a generic fallback to print regular types that can be printed to a + * standard output stream, e.g. std::cout. + * + * \tparam type_t The type of the printable argument. + * \ingroup core_debug_stream + */ +template + requires requires (std::ostream & cout, type_t const & value) { + { cout << value }; + } +struct std_printer +{ + /*!\brief The function call operator that prints the value to the stream. + * + * This function call operator prints the value to underlying stream of the seqan3::debug_stream. + * This overload is only provided if the stream is a seqan3::debug_stream. + * + * \tparam stream_t The type of the stream. + * \tparam arg_t The type of the argument. + * \param[in,out] stream The stream to print to. + * \param[in] arg The value to print. + */ + template + requires requires (stream_t & stream) { stream.stream; } + constexpr void operator()(stream_t & stream, arg_t && arg) const + { + *stream.stream << arg; + } +}; + +/*! + 8 \brief The printer for integral types. + * + * The integral_printer is used to print integral types to a stream. + * + * \tparam integral_t The type of the integral. + * \ingroup core_debug_stream + */ +template +struct integral_printer +{ + /*!\brief The function call operator that prints the integral to the stream. + * \tparam stream_t The type of the stream. + * \param[in,out] stream The stream to print to. + * \param[in] arg The integral to print. + */ + template + constexpr void operator()(stream_t & stream, integral_t const arg) const + { + // note that we assume here that we always can print all std::integral's, + // but this is not correct since std::cout << char32_t{5}; is not possible. + // since char32_t is also an alphabet, we avoid infinite recursion here. + if constexpr (printable_with, stream_t &, integral_t>) + std::invoke(std_printer{}, stream, arg); + else + static_assert(std::same_as, "This type is not printable."); + // We actually want `static_assert(false, "This type is not printable.");`. + // But this only works starting with GCC13. Before that, also the `if constexpr` branches that are not taken + // are evaluated and `static_assert(false)` will always result in an error. As a pre-GCC13 workaround, + // we can make the `false` dependent on some template type, which then will only be evaluated if the branch is + // taken. + } +}; + +/*!\brief The printer_order is a variadic template that defines the order of the printers. + * + * The printer_order is a variadic template that defines the order of the printers. + * It is used to find the first valid printer among all given printers that can print the argument to the given stream. + * The printer_order is used by the seqan3::default_printer. + * + * \tparam printer_templates_t The printer class templates that are used to print the arguments. + * \ingroup core_debug_stream + */ +template