Skip to content

Commit

Permalink
[FEATURE] seqan3::default_printer
Browse files Browse the repository at this point in the history
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<uint8_t> // [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<type>` 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] <[email protected]>
Co-authored-by: rrahn <[email protected]>
Co-authored-by: Enrico Seiler <[email protected]>
  • Loading branch information
4 people committed Aug 2, 2024
1 parent 4a0b3fe commit 4c65dda
Show file tree
Hide file tree
Showing 26 changed files with 1,010 additions and 510 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,27 +91,31 @@ void stream_alignment(debug_stream_type<char_t> & 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 <typename char_t, typename alignment_t>
requires (detail::debug_streamable_tuple<alignment_t>
&& detail::all_model_aligned_seq<detail::tuple_type_list_t<std::remove_cvref_t<alignment_t>>>)
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & stream, alignment_t && alignment)
template <typename alignment_t>
requires tuple_like<alignment_t> && detail::all_model_aligned_seq<detail::tuple_type_list_t<alignment_t>>
struct alignment_printer<alignment_t>
{
constexpr size_t sequence_count = std::tuple_size_v<std::remove_cvref_t<alignment_t>>;
/*!\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 <typename stream_t, typename arg_t>
constexpr void operator()(stream_t & stream, arg_t && arg) const
{
constexpr size_t sequence_count = std::tuple_size_v<std::remove_cvref_t<arg_t>>;

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<sequence_count - 1>{});
return stream;
}
detail::stream_alignment(stream, std::forward<arg_t>(arg), std::make_index_sequence<sequence_count - 1>{});
}
};

} // namespace seqan3
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename char_t, typename coordinate_type>
requires detail::is_value_specialisation_of_v<std::remove_cvref_t<coordinate_type>,
detail::advanceable_alignment_coordinate>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, coordinate_type && c)
template <auto state_t>
struct advanceable_alignment_coordinate_printer<detail::advanceable_alignment_coordinate<state_t>>
{
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 <typename stream_t>
constexpr void operator()(stream_t & stream, detail::advanceable_alignment_coordinate<state_t> const arg) const
{
stream << std::tie(arg.first, arg.second);
}
};

} // namespace seqan3
82 changes: 58 additions & 24 deletions include/seqan3/alignment/matrix/detail/debug_matrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename char_t, detail::matrix alignment_matrix_t>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, alignment_matrix_t && matrix)
template <detail::matrix alignment_matrix_t>
struct alignment_matrix_printer<alignment_matrix_t>
{
detail::debug_matrix debug{std::forward<alignment_matrix_t>(matrix)};

std::stringstream sstream{};
debug.stream_matrix(sstream, s.flags2());
s << sstream.str();
return s;
}

//!\overload
template <typename char_t, std::ranges::input_range alignment_matrix_t>
requires detail::debug_stream_range_guard<alignment_matrix_t> && detail::matrix<alignment_matrix_t>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, alignment_matrix_t && matrix)
{
return s << detail::debug_matrix{std::forward<alignment_matrix_t>(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 <typename stream_t, typename arg_t>
requires detail::is_type_specialisation_of_v<stream_t, debug_stream_type>
constexpr void operator()(stream_t & stream, arg_t && arg) const
{
print_impl(stream.get_underlying_stream(), stream.flags2(), std::forward<arg_t>(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 <typename stream_t, typename arg_t>
constexpr void operator()(stream_t & stream, arg_t && arg) const
{
print_impl(stream, fmtflags2::none, std::forward<arg_t>(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 <typename stream_t, typename arg_t>
void print_impl(stream_t & stream, fmtflags2 const flags, arg_t && arg) const
{
detail::debug_matrix debug{std::forward<arg_t>(arg)};

debug.stream_matrix(stream, flags);
}
};

} // namespace seqan3
85 changes: 66 additions & 19 deletions include/seqan3/alignment/matrix/detail/trace_directions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@

#pragma once

#include <array>
#include <string_view>

#include <seqan3/core/add_enum_bitwise_operators.hpp>
#include <seqan3/core/debug_stream/debug_stream_type.hpp>
#include <seqan3/core/detail/template_inspection.hpp>

namespace seqan3::detail
{
Expand Down Expand Up @@ -54,12 +58,7 @@ template <>
inline constexpr bool add_enum_bitwise_operators<seqan3::detail::trace_directions> = 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:
*
Expand All @@ -71,23 +70,71 @@ inline constexpr bool add_enum_bitwise_operators<seqan3::detail::trace_direction
* | seqan3::detail::trace_directions::up | ⇡ | u |
* | seqan3::detail::trace_directions::left_open | ← | L |
* | seqan3::detail::trace_directions::left | ⇠ | l |
*
* \ingroup alignment_matrix
*/
template <typename char_t>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, detail::trace_directions const trace)
template <>
struct trace_directions_printer<detail::trace_directions>
{
static char const * unicode[32]{"", "", "", "↖↑", "", "↖⇡", "↑⇡", "↖↑⇡", "", "↖←", "↑←",
"↖↑←", "⇡←", "↖⇡←", "↑⇡←", "↖↑⇡←", "", "↖⇠", "↑⇠", "↖↑⇠", "⇡⇠", "↖⇡⇠",
"↑⇡⇠", "↖↑⇡⇠", "←⇠", "↖←⇠", "↑←⇠", "↖↑←⇠", "⇡←⇠", "↖⇡←⇠", "↑⇡←⇠", "↖↑⇡←⇠"};
private:
//!\brief The unicode representation of the trace directions.
static constexpr std::array<std::string_view, 32> unicode{
"", "", "", "↖↑", "", "↖⇡", "↑⇡", "↖↑⇡", "", "↖←", "↑←", "↖↑←", "⇡←", "↖⇡←", "↑⇡←", "↖↑⇡←",
"", "↖⇠", "↑⇠", "↖↑⇠", "⇡⇠", "↖⇡⇠", "↑⇡⇠", "↖↑⇡⇠", "←⇠", "↖←⇠", "↑←⇠", "↖↑←⇠", "⇡←⇠", "↖⇡←⇠", "↑⇡←⇠", "↖↑⇡←⇠"};

//!\brief The ascii representation of the trace directions.
static constexpr std::array<std::string_view, 32> 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 <typename stream_t>
requires detail::is_type_specialisation_of_v<stream_t, debug_stream_type>
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 <typename stream_t>
constexpr void operator()(stream_t & stream, detail::trace_directions const trace) const
{
print_impl(stream, fmtflags2::none, trace);
}

s << trace_dir[static_cast<size_t>(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 <typename stream_t>
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<size_t>(trace)];
}
};

} // namespace seqan3
Loading

0 comments on commit 4c65dda

Please sign in to comment.