| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-FileCopyrightText: 2022-2023 Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de> | ||
| 2 | // SPDX-License-Identifier: MIT | ||
| 3 | /*! | ||
| 4 | * \file | ||
| 5 | * \ingroup Encoding | ||
| 6 | * \brief Encoder and stream using ascii | ||
| 7 | */ | ||
| 8 | #ifndef GRIDFORMAT_COMMON_ENCODING_ASCII_HPP_ | ||
| 9 | #define GRIDFORMAT_COMMON_ENCODING_ASCII_HPP_ | ||
| 10 | |||
| 11 | #include <cmath> | ||
| 12 | #include <limits> | ||
| 13 | #include <concepts> | ||
| 14 | #include <algorithm> | ||
| 15 | #include <optional> | ||
| 16 | #include <cstdint> | ||
| 17 | #include <string> | ||
| 18 | #include <span> | ||
| 19 | #include <sstream> | ||
| 20 | |||
| 21 | #if __has_include(<format>) | ||
| 22 | #include <format> | ||
| 23 | #endif | ||
| 24 | |||
| 25 | #include <gridformat/common/output_stream.hpp> | ||
| 26 | #include <gridformat/common/reserved_string.hpp> | ||
| 27 | |||
| 28 | #ifndef DOXYGEN | ||
| 29 | namespace GridFormat::Encoding::Detail { | ||
| 30 | |||
| 31 | template<typename T> | ||
| 32 | struct AsciiPrintType : std::type_identity<T> {}; | ||
| 33 | template<std::signed_integral T> | ||
| 34 | struct AsciiPrintType<T> : std::type_identity<std::intmax_t> {}; | ||
| 35 | template<std::unsigned_integral T> | ||
| 36 | struct AsciiPrintType<T> : std::type_identity<std::uintmax_t> {}; | ||
| 37 | |||
| 38 | } // namespace GridFormat::Encoding::Detail | ||
| 39 | #endif // DOXYGEN | ||
| 40 | |||
| 41 | namespace GridFormat { | ||
| 42 | |||
| 43 | //! \addtogroup Encoding | ||
| 44 | //! \{ | ||
| 45 | |||
| 46 | //! Options for fomatted output of ranges with ascii encoding | ||
| 47 | struct AsciiFormatOptions { | ||
| 48 | ReservedString<30> delimiter{""}; | ||
| 49 | ReservedString<30> line_prefix{""}; | ||
| 50 | std::size_t entries_per_line = std::numeric_limits<std::size_t>::max(); | ||
| 51 | std::size_t num_cached_lines = 100; //!< Number of line cached between flushing the buffer | ||
| 52 | |||
| 53 | 14950 | friend bool operator==(const AsciiFormatOptions& a, const AsciiFormatOptions& b) { | |
| 54 | 14950 | return a.delimiter == b.delimiter | |
| 55 |
1/2✓ Branch 1 taken 14950 times.
✗ Branch 2 not taken.
|
14950 | && a.line_prefix == b.line_prefix |
| 56 |
1/2✓ Branch 0 taken 14950 times.
✗ Branch 1 not taken.
|
14950 | && a.entries_per_line == b.entries_per_line |
| 57 |
2/4✓ Branch 0 taken 14950 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14950 times.
✗ Branch 3 not taken.
|
29900 | && a.num_cached_lines == b.num_cached_lines; |
| 58 | } | ||
| 59 | }; | ||
| 60 | |||
| 61 | //! Wrapper around a given stream to write formatted ascii output | ||
| 62 | template<typename OStream> | ||
| 63 | class AsciiOutputStream : public OutputStreamWrapperBase<OStream> { | ||
| 64 | class Buffer { | ||
| 65 | public: | ||
| 66 | 14953 | Buffer(std::streamsize precision) | |
| 67 |
1/2✓ Branch 2 taken 14953 times.
✗ Branch 3 not taken.
|
14953 | : _precision{precision} { |
| 68 |
1/2✓ Branch 1 taken 14953 times.
✗ Branch 2 not taken.
|
14953 | _init_stream_buf(); |
| 69 | 14953 | } | |
| 70 | |||
| 71 | template<typename V, typename D> | ||
| 72 | 2711324 | void push(V&& value, D&& delimiter) { | |
| 73 | #if __cpp_lib_format | ||
| 74 | if constexpr (std::floating_point<std::remove_cvref_t<V>>) | ||
| 75 | 4824088 | std::format_to(std::back_inserter(_string_buf), | |
| 76 | 2412044 | "{:.{}g}{}", std::forward<V>(value), _precision, std::forward<D>(delimiter) | |
| 77 | ); | ||
| 78 | else | ||
| 79 | 299280 | std::format_to(std::back_inserter(_string_buf), | |
| 80 | "{}{}", std::forward<V>(value), std::forward<D>(delimiter) | ||
| 81 | ); | ||
| 82 | #else | ||
| 83 | _stream_buf << std::forward<V>(value) << std::forward<D>(delimiter); | ||
| 84 | #endif | ||
| 85 | 2711324 | } | |
| 86 | |||
| 87 | 15004 | void prepare_readout() { | |
| 88 | #if !__cpp_lib_format | ||
| 89 | // move internal string buffer out of the stream | ||
| 90 | // (see https://stackoverflow.com/a/66662433) | ||
| 91 | _string_buf = std::move(_stream_buf).str(); | ||
| 92 | _init_stream_buf(); // reinitialize | ||
| 93 | #endif | ||
| 94 | 15004 | } | |
| 95 | |||
| 96 | 15004 | auto data() const { | |
| 97 | 15004 | return std::span{_string_buf.data(), _string_buf.size()}; | |
| 98 | } | ||
| 99 | |||
| 100 | 51 | void clear() { | |
| 101 | 51 | _string_buf.clear(); | |
| 102 | 51 | } | |
| 103 | |||
| 104 | private: | ||
| 105 | 14953 | void _init_stream_buf() { | |
| 106 |
2/4✓ Branch 1 taken 14953 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 14953 times.
✗ Branch 5 not taken.
|
14953 | _stream_buf = {}; |
| 107 | 14953 | _stream_buf.precision(_precision); | |
| 108 | 14953 | } | |
| 109 | |||
| 110 | std::streamsize _precision; | ||
| 111 | std::string _string_buf; | ||
| 112 | std::ostringstream _stream_buf; | ||
| 113 | }; | ||
| 114 | |||
| 115 | public: | ||
| 116 | 14953 | AsciiOutputStream(OStream& s, AsciiFormatOptions opts = {}) | |
| 117 | : OutputStreamWrapperBase<OStream>(s) | ||
| 118 | 14953 | , _opts{std::move(opts)} | |
| 119 | 14953 | {} | |
| 120 | |||
| 121 | template<typename T, std::size_t size> | ||
| 122 | 29904 | void write(std::span<T, size> data) { | |
| 123 | 29904 | std::size_t count_entries = 0; | |
| 124 | 29904 | std::size_t count_buffer_lines = 0; | |
| 125 | |||
| 126 | using PrintType = typename Encoding::Detail::AsciiPrintType<T>::type; | ||
| 127 |
1/2✓ Branch 1 taken 14953 times.
✗ Branch 2 not taken.
|
29904 | Buffer buffer(std::numeric_limits<PrintType>::digits10); |
| 128 |
2/2✓ Branch 1 taken 91759 times.
✓ Branch 2 taken 14953 times.
|
213420 | while (count_entries < data.size()) { |
| 129 |
3/4✓ Branch 0 taken 76954 times.
✓ Branch 1 taken 14805 times.
✓ Branch 3 taken 91759 times.
✗ Branch 4 not taken.
|
183516 | buffer.push(count_entries > 0 ? "\n" : "", _opts.line_prefix); |
| 130 | |||
| 131 | using std::min; | ||
| 132 | 183516 | const auto num_entries = min(_opts.entries_per_line, data.size() - count_entries); | |
| 133 |
2/2✓ Branch 5 taken 1263903 times.
✓ Branch 6 taken 91759 times.
|
2711314 | for (const auto& value : data.subspan(count_entries, num_entries)) |
| 134 |
1/2✓ Branch 1 taken 1263903 times.
✗ Branch 2 not taken.
|
2527798 | buffer.push(static_cast<PrintType>(value), _opts.delimiter); |
| 135 | |||
| 136 | // update counters | ||
| 137 | 183516 | count_entries += num_entries; | |
| 138 | 183516 | ++count_buffer_lines; | |
| 139 | |||
| 140 | // flush and reset buffer | ||
| 141 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 91708 times.
|
183516 | if (count_buffer_lines >= _opts.num_cached_lines) { |
| 142 | 102 | buffer.prepare_readout(); | |
| 143 |
1/2✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
|
102 | this->_write_raw(buffer.data()); |
| 144 | 102 | buffer.clear(); | |
| 145 | 102 | count_buffer_lines = 0; | |
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | // flush remaining buffer content | ||
| 150 | 29904 | buffer.prepare_readout(); | |
| 151 |
1/2✓ Branch 2 taken 14953 times.
✗ Branch 3 not taken.
|
29904 | this->_write_raw(buffer.data()); |
| 152 | 29904 | } | |
| 153 | |||
| 154 | AsciiFormatOptions _opts; | ||
| 155 | }; | ||
| 156 | |||
| 157 | //! \} group Encoding | ||
| 158 | |||
| 159 | } // namespace GridFormat | ||
| 160 | |||
| 161 | namespace GridFormat::Encoding { | ||
| 162 | |||
| 163 | //! \addtogroup Encoding | ||
| 164 | //! \{ | ||
| 165 | |||
| 166 | //! Ascii encoder | ||
| 167 | struct Ascii { | ||
| 168 | constexpr Ascii() = default; | ||
| 169 | 14951 | constexpr explicit Ascii(AsciiFormatOptions opts) | |
| 170 | 14951 | : _opts{std::move(opts)} | |
| 171 | 14951 | {} | |
| 172 | |||
| 173 | //! Create an ascii stream with the defined options | ||
| 174 | template<typename S> | ||
| 175 | 14953 | constexpr auto operator()(S& s) const noexcept { | |
| 176 | 14953 | return AsciiOutputStream{s, options()}; | |
| 177 | } | ||
| 178 | |||
| 179 | //! Return a new instance with different options | ||
| 180 | 14951 | static constexpr auto with(AsciiFormatOptions opts) { | |
| 181 | 14951 | return Ascii{std::move(opts)}; | |
| 182 | } | ||
| 183 | |||
| 184 | //! Return the current options | ||
| 185 | 29903 | constexpr AsciiFormatOptions options() const { | |
| 186 |
2/4✓ Branch 1 taken 29903 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 29903 times.
✗ Branch 5 not taken.
|
29903 | return _opts.value_or(AsciiFormatOptions{}); |
| 187 | } | ||
| 188 | |||
| 189 | private: | ||
| 190 | // we use optional here in order to be able to define | ||
| 191 | // an inline constexpr instance of this class below | ||
| 192 | std::optional<AsciiFormatOptions> _opts = {}; | ||
| 193 | }; | ||
| 194 | |||
| 195 | //! Instance of the ascii encoder | ||
| 196 | inline constexpr Ascii ascii; | ||
| 197 | |||
| 198 | //! \} group Encoding | ||
| 199 | |||
| 200 | } // namespace GridFormat::Encoding | ||
| 201 | |||
| 202 | #endif // GRIDFORMAT_COMMON_ENCODING_ASCII_HPP_ | ||
| 203 |