Skip to content

Commit

Permalink
FEATURE: add seqan3::default_printer
Browse files Browse the repository at this point in the history
This PR will resolve seqan/product_backlog#63.

This issue is a long-standing regret 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 old 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 new design

### Before anything, note that the new design does not break any existing code. As existing `seqan3::debugstream << ` overloads, still take place in overload resolution ().

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) )

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
  • Loading branch information
marehr committed Jan 1, 2024
1 parent 6b681fb commit f3dcea4
Show file tree
Hide file tree
Showing 23 changed files with 343 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,30 @@ namespace seqan3
*
* \return The given stream to which the alignment representation is appended.
*/
#if 0
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)
{
#else
template <typename alignment_t>
requires (tuple_like<std::remove_cvref_t<alignment_t>> && detail::all_model_aligned_seq<detail::tuple_type_list_t<std::remove_cvref_t<alignment_t>>>)
struct alignment_printer<alignment_t>
{
constexpr static auto print = [](auto & stream, auto && alignment)
{
constexpr size_t sequence_count = std::tuple_size_v<std::remove_cvref_t<alignment_t>>;

static_assert(sequence_count >= 2, "An alignment requires at least two sequences.");

detail::stream_alignment(stream, alignment, std::make_index_sequence<sequence_count - 1>{});
};
};
#endif
#if 0
return stream;
}
#endif

} // namespace seqan3
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,27 @@ namespace seqan3
*
* Prints the alignment coordinate as a tuple.
*/
#if 0
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)
{
#else
template <typename coordinate_type>
requires detail::is_value_specialisation_of_v<std::remove_cvref_t<coordinate_type>,
detail::advanceable_alignment_coordinate>
struct advanceable_alignment_coordinate_printer<coordinate_type>
{
constexpr static auto print = [](auto & s, coordinate_type const & c)
{
s << std::tie(c.first, c.second);
};
};
#endif
#if 0
return s;
}
#endif

} // namespace seqan3
15 changes: 15 additions & 0 deletions include/seqan3/alignment/matrix/detail/debug_matrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,23 +479,38 @@ namespace seqan3
*
* This prints out an alignment matrix which can be a score matrix or a trace matrix.
*/
#if 0
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)
{
#else
template <typename alignment_matrix_t>
requires detail::matrix<std::remove_cvref_t<alignment_matrix_t>>
struct alignment_matrix_printer<alignment_matrix_t>
{
constexpr static auto print = [](auto & s, auto && matrix)
{
detail::debug_matrix debug{std::forward<alignment_matrix_t>(matrix)};

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

#if 0
//!\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)};
}
#endif

} // namespace seqan3
13 changes: 13 additions & 0 deletions include/seqan3/alignment/matrix/detail/trace_directions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,17 @@ constexpr bool add_enum_bitwise_operators<seqan3::detail::trace_directions> = tr
* | seqan3::detail::trace_directions::left_open | ← | L |
* | seqan3::detail::trace_directions::left | ⇠ | l |
*/
#if 0
template <typename char_t>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, detail::trace_directions const trace)
{
#else
template <typename trace_directions_t>
requires std::is_same_v<std::remove_cvref_t<trace_directions_t>, detail::trace_directions>
struct trace_directions_printer<trace_directions_t>
{
constexpr static auto print = [](auto & s, detail::trace_directions const trace)
{
static char const * unicode[32]{"", "", "", "↖↑", "", "↖⇡", "↑⇡", "↖↑⇡", "", "↖←", "↑←",
"↖↑←", "⇡←", "↖⇡←", "↑⇡←", "↖↑⇡←", "", "↖⇠", "↑⇠", "↖↑⇠", "⇡⇠", "↖⇡⇠",
"↑⇡⇠", "↖↑⇡⇠", "←⇠", "↖←⇠", "↑←⇠", "↖↑←⇠", "⇡←⇠", "↖⇡←⇠", "↑⇡←⇠", "↖↑⇡←⇠"};
Expand All @@ -90,7 +98,12 @@ inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, det
auto const & trace_dir = is_unicode ? unicode : csv;

s << trace_dir[static_cast<size_t>(trace)];
};
};
#endif
#if 0
return s;
}
#endif

} // namespace seqan3
19 changes: 19 additions & 0 deletions include/seqan3/alignment/pairwise/alignment_result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,24 @@ namespace seqan3
* \param[in] result The alignment result to print.
* \relates seqan3::debug_stream_type
*/
#if 0
template <typename char_t, typename alignment_result_t>
requires detail::is_type_specialisation_of_v<std::remove_cvref_t<alignment_result_t>, alignment_result>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & stream, alignment_result_t && result)
{
#else
template <typename T>
requires (!std::is_same_v<T, std::remove_cvref_t<T>>)
struct alignment_result_printer<T> : public alignment_result_printer<std::remove_cvref_t<T>>
{
};

template <typename result_value_t>
struct alignment_result_printer<alignment_result<result_value_t>>
{
constexpr static auto print = [](auto & stream, alignment_result<result_value_t> const & result)
{
using alignment_result_t = alignment_result<result_value_t>;
using disabled_t = std::nullopt_t *;
using result_data_t =
typename detail::alignment_result_value_type_accessor<std::remove_cvref_t<alignment_result_t>>::type;
Expand Down Expand Up @@ -423,6 +437,11 @@ inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & stream
append_to_stream("\nalignment:\n", result.alignment());
stream << '}';

};
};
#endif
#if 0
return stream;
}
#endif
} // namespace seqan3
2 changes: 2 additions & 0 deletions include/seqan3/alphabet/cigar/cigar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,15 @@ class cigar : public alphabet_tuple_base<cigar, uint32_t, exposition_only::cigar
//!\}
};

#if 1
//!\brief Overload for the seqan3::debug_stream's operator.
template <typename char_t>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, cigar const c)
{
s << c.to_string();
return s;
}
#endif

inline namespace literals
{
Expand Down
24 changes: 24 additions & 0 deletions include/seqan3/alphabet/detail/debug_stream_alphabet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

namespace seqan3
{
#if 0
/*!\name Formatted output overloads
* \{
*/
Expand All @@ -33,10 +34,22 @@ inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, alp
{
return s << to_char(l);
}
#else
template <typename alphabet_t>
requires alphabet<alphabet_t>
struct alphabet_printer<alphabet_t>
{
constexpr static auto print = [](auto & s, alphabet_t l)
{
s << to_char(l);
};
};
#endif

// forward declare seqan3::mask
class mask;

#if 0
/*!\brief Overload for the seqan3::mask alphabet.
* \tparam char_t Type char type of the debug_stream.
* \param s The seqan3::debug_stream.
Expand All @@ -51,5 +64,16 @@ inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, alp
}

//!\}
#else
template <typename alphabet_t>
requires std::same_as<std::remove_cvref_t<alphabet_t>, mask>
struct mask_printer<alphabet_t>
{
constexpr static auto print = [](auto & s, alphabet_t const l)
{
s << (l == std::remove_cvref_t<alphabet_t>{} ? "UNMASKED" : "MASKED");
};
};
#endif

} // namespace seqan3
2 changes: 2 additions & 0 deletions include/seqan3/argument_parser/auxiliary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ concept argument_parser_compatible_option =
* This searches the seqan3::enumeration_names of the respective type for the value \p op and prints the
* respective string if found or '\<UNKNOWN_VALUE\>' if the value cannot be found in the map.
*/
#if 1
template <typename char_t, typename option_type>
requires named_enumeration<std::remove_cvref_t<option_type>>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, option_type && op)
Expand All @@ -234,6 +235,7 @@ inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, opt

return s << "<UNKNOWN_VALUE>";
}
#endif
//!\}

/*!\brief Used to further specify argument_parser options/flags.
Expand Down
2 changes: 2 additions & 0 deletions include/seqan3/core/debug_stream/byte.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ namespace seqan3
* \param[in] arg The std::byte.
* \relates seqan3::debug_stream_type
*/
#if 1
template <typename char_t, typename byte_type>
requires std::same_as<std::remove_cvref_t<byte_type>, std::byte>
inline debug_stream_type<char_t> & operator<<(debug_stream_type<char_t> & s, byte_type && arg)
{
s << std::to_integer<uint8_t>(arg);
return s;
}
#endif

//!\}

Expand Down
Loading

0 comments on commit f3dcea4

Please sign in to comment.