code/include/swoc/BufferWriter.h (261 lines of code) (raw):

// SPDX-License-Identifier: Apache-2.0 // Copyright Apache Software Foundation 2019 /** @file Utilities for generating character sequences in buffers. */ #pragma once #include <cstdlib> #include <utility> #include <cstring> #include <vector> #include <string> #include <iosfwd> #include <string_view> #include "swoc/swoc_version.h" #include "swoc/TextView.h" #include "swoc/MemSpan.h" #include "swoc/bwf_fwd.h" namespace swoc { inline namespace SWOC_VERSION_NS { /** Wrapper for operations on a buffer. * * This maintains information about the size and amount in use of the buffer, preventing data * overruns. In all cases, methods that write to the buffer clip the input to the size of the * remaining buffer space. The @c error method can be used to detect such clipping. The theoretical * size of the buffer is also tracked such that if there is not enough buffer space, the amount * needed can be determined by the method @c extent. * * @note This is a protocol class, concrete subclasses implement the functionality. */ class BufferWriter { public: /** Write @a c to the buffer. * * @param c Character to write. * @return @a this. */ virtual BufferWriter &write(char c) = 0; /** Write @a length bytes starting at @a data to the buffer. * * @param data Source data. * @param length Number of bytes in the source data. * @return @a this. * * @internal This uses the single character write to output the data. It is presumed concrete * subclasses will override this method to use more efficient mechanisms, dependent on the type of * output buffer. */ virtual BufferWriter &write(void const *data, size_t length); /** Write data to the buffer. * * @param span Data source. * @return @a this * * Data from @a span is written directly to the buffer, and clipped to the size of the buffer. * * @internal The char poointer overloads protect this method in order to protect against including * the terminal nul in the destination buffer. */ BufferWriter &write(MemSpan<void const> span); /// @return Pointer to first byte in buffer. virtual const char *data() const = 0; /// Get the error state. /// @return @c true if in an error state, @c false if not. virtual bool error() const = 0; /** Address of the first unused byte in the output buffer. The address is fragile and calls to non-const methods can invalidate it. @return Address of the next output byte, or @c nullptr if there is no remaining capacity. */ virtual char *aux_data(); /// @return The number of bytes that can be successfully written to this buffer. virtual size_t capacity() const = 0; /// @return Number of characters written to the buffer, including those discarded. virtual size_t extent() const = 0; /// @return Number of bytes of valid (used) data in the buffer. size_t size() const; /// @return The number of bytes which have not yet been written. size_t remaining() const; /** A memory span of the unused bytes. * * @return A span of the unused bytes. * * This is a convenience method that is identical to * @code * BufferWriter w; * // ... * MemSpan<char>{ w.aux_data(), w.remaining() }; * @endcode */ MemSpan<char> aux_span(); /** Increase the extent by @a n bytes. * * @param n Number of bytes. * @return @c true if the commit is final, @c false if it should be retried. * * This is used to add data written in the @c aux_data to the written data in the buffer. * * The return value should be @c true unless the write operation proceeding the call to @c commit * along with the @c commit call should be retried. That is only reasonable if some state in the * concrete implementation has changed to make success possible on the next try. Generally this * will be because the implementation increased capacity. * * @internal Concrete subclasses @b must override this in a way consistent with the specific buffer type. */ virtual bool commit(size_t n) = 0; /** Decrease the extent by @a n. * * @param n Number of bytes to remove from the extent. * @return @a this. * * The buffer content is unchanged, only the extent value is adjusted. This effectively discards * @a n bytes of already written data. */ virtual BufferWriter &discard(size_t n) = 0; /// Reduce the capacity by @a n bytes /// If the capacity is reduced below the current @c size the instance goes in to an error state. /// @see restore /// @return @c *this virtual BufferWriter &restrict(size_t n) = 0; /// Restore @a n bytes of capacity. /// If there is an error condition, this function clears it and sets the extent to the size. It /// then increases the capacity by n characters. /// @note This does not make the internal buffer size larger. It can only restore capacity earlier /// removed by @c restrict. /// @see restrict virtual BufferWriter &restore(size_t n) = 0; /** Copy data from one part of the buffer to another. * * The copy is guaranteed to be correct even if the @a src and @a dst overlap. The regions are * clipped by the current extent. That is, bytes cannot be copied to nor from unwritten buffer. * If the extent is currently more than the capacity, the copy is performed as if the buffer * existed and then clipped to the actual buffer space. * * @param dst Offset of the first by to copy onto. * @param src Offset of the first byte to copy from. * @param n Number of bytes to copy. * @return @c *this * * @internal This is used to perform justification for formatting. */ virtual BufferWriter &copy(size_t dst, size_t src, size_t n) = 0; // Force virtual destructor. virtual ~BufferWriter(); /** Formatted output to the buffer. * * @tparam Args Types of the format arguments. * @param fmt Format string to control formatted output. * @param args Parameters for the format string. * @return @a this. * * The format string is Python style. * See http://docs.solidwallofcode.com/libswoc/code/BW_Format.en.html for further information. * * @note This must be declared here, but the implementation is in @c bwf_base.h. That file does * not need to be included if formatted output is not used. */ template <typename... Args> BufferWriter &print(const TextView &fmt, Args &&...args); /** Formatted output to the buffer. * * @tparam Args Types of the arguments for formatting. * @param fmt Format string. * @param args The format arguments in a tuple. * @return @a this * * This is the equivalent of the "va..." form for printing. Alternate front ends to formatted * output should gather their formatting arguments into a tuple, usually using * @c std::forward_as_tuple(). */ template <typename... Args> BufferWriter &print_v(const TextView &fmt, const std::tuple<Args...> &args); /** Formatted output to the buffer. * * @tparam Args Types of the format input parameters. * @param fmt Pre-condensed format. * @param args Arguments for the format string. * @return @a this. */ template <typename... Args> BufferWriter &print(const bwf::Format &fmt, Args &&...args); /** Formatted output to the buffer. * * @tparam Args Types of the parameter for formatting. * @param fmt Pre-condensed format string. * @param args The format parameters in a tuple. * @return @a this * * This is the equivalent of the "va..." form for printing. Alternate front ends to formatted * output should gather their formatting arguments into a tuple, usually using * @c std::forward_as_tuple(). */ template <typename... Args> BufferWriter &print_v(const bwf::Format &fmt, const std::tuple<Args...> &args); /** Write formatted output of @a args to @a this buffer. * * @tparam Binding Type for the name binding instance. * @tparam Extractor Format extractor type. * @param names Name set for specifier names. * @param ex Format processor instance, which parse the format piecewise. * @param args The format parameters. * * @a Extractor must have at least two methods * - A conversion to @c bool that indicates if there is data left. * - A function of the signature <tt>bool ex(std::string_view& lit, bwf::Spec & spec)</tt> * * The latter must return whether a specifier was parsed, while filling in @a lit and @a spec * as appropriate for the next chunk of format string. No literal is represented by a empty * @a lit. * * The name binding must have a function operator that takes two arguments, a @c BufferWriter& * and a format specifier @c bwf::Spec. It is expected to generate output to the @c BufferWriter * instance based on data in the format specifier (which contains, among other things, the * name which caused the binding to be invoked). * * @note This is the base implementation, all of the other variants are wrappers for this. * * @see NameBinding */ template <typename Binding, typename Extractor> BufferWriter &print_nfv(Binding &&names, Extractor &&ex, bwf::ArgPack const &args); /** Write formatted output of @a args to @a this buffer. * * @tparam Binding Name binding functor. * @tparam Extractor Format processor type. * @param names Name set for specifier names. * @param ex Format processor instance, which parse the format piecewise. * * @note This is primarily an internal convenience for certain situations where a format parameter * tuple is not needed and difficult to create. */ template <typename Binding, typename Extractor> BufferWriter &print_nfv(Binding const &names, Extractor &&ex); /** Write formatted output to @a this buffer. * * @param names Name set for specifier names. * @param fmt Format string. * * This is intended to be use with context name binding where @a names has the bindings and the * format string @a fmt contains only references to those names, not to any arguments. */ template <typename Binding> BufferWriter &print_n(Binding const &names, TextView const &fmt); /** Write formattted data. * * @tparam T Data type. * @param spec Format specifier. * @param t Instance to print. * @return @a this * * Essentially this forwards @a t to @c bwformat. */ template <typename T> BufferWriter &format(bwf::Spec const &spec, T &&t); /** Write formattted data. * * @tparam T Data type. * @param spec Format specifier. * @param t Instance to print. * @return @a this * * Essentially this forwards @a t to @c bwformat. */ template <typename T> BufferWriter &format(bwf::Spec const &spec, T const &t); /** IO stream operator. * * @param stream Output stream. * @return @a stream * * Write the buffer contents to @a stream. */ virtual std::ostream &operator>>(std::ostream &stream) const = 0; }; /** A concrete @c BufferWriter class for a fixed buffer. * */ class FixedBufferWriter : public BufferWriter { using super_type = BufferWriter; using self_type = FixedBufferWriter; public: /** Construct a buffer writer on a fixed @a buffer of size @a capacity. If writing goes past the end of the buffer, the excess is dropped. */ FixedBufferWriter(char *buffer, size_t capacity); /// Construct using the memory @a span as the buffer. FixedBufferWriter(MemSpan<void> const &span); /// Construct using the memory @a span as the buffer. FixedBufferWriter(MemSpan<char> const &span); /** Constructor an empty buffer with no capacity. * This can be useful to measure the extent of the output before allocating memory. */ FixedBufferWriter(std::nullptr_t); FixedBufferWriter(const FixedBufferWriter &) = delete; FixedBufferWriter &operator=(const FixedBufferWriter &) = delete; /// Move constructor. FixedBufferWriter(FixedBufferWriter &&that); /// Move assignment. FixedBufferWriter &operator=(FixedBufferWriter &&that); /// Reset buffer. self_type &assign(MemSpan<char> const &span); /// Write a single character @a c to the buffer. FixedBufferWriter &write(char c) override; /// Write @a length bytes, starting at @a data, to the buffer. FixedBufferWriter &write(const void *data, size_t length) override; // Bring in non-overridden methods. using super_type::write; /// @return The start of the buffer. const char *data() const override; /// @return @c true if output has been discarded, @a false otherwise. bool error() const override; /// @return Start of the unused buffer, or @c nullptr is there is no remaining unwritten space. char *aux_data() override; /// Get the total capacity of the output buffer. size_t capacity() const override; /// Get the total output sent to the writer. size_t extent() const override; /// Advance the used part of the output buffer. bool commit(size_t n) override; /// Drop @a n characters from the end of the buffer. self_type &discard(size_t n) override; /// Reduce the capacity by @a n. self_type &restrict(size_t n) override; /// Restore @a n bytes of the capacity. self_type &restore(size_t n) override; /// Copy data in the buffer. FixedBufferWriter &copy(size_t dst, size_t src, size_t n) override; /// Erase the buffer, reset to empty (no valid data). /// This is a convenience for reusing a buffer. For instance /// @code /// bw.clear().print("....."); // clear old data and print new data. /// @endcode /// This is equivalent to @c w.discard(w.size()) but clearer for that case. self_type &clear(); self_type &detach(); /// @return The used part of the buffer as a @c std::string_view. swoc::TextView view() const; /// Provide a @c string_view of all successfully written characters as a user conversion. operator std::string_view() const; /// Provide a @c string_view of all successfully written characters as a user conversion. operator swoc::TextView() const; /// Output the buffer contents to the @a stream. std::ostream &operator>>(std::ostream &stream) const override; /// @cond COVARY template <typename... Rest> self_type &print(TextView fmt, Rest &&...rest); template <typename... Args> self_type &print_v(TextView fmt, std::tuple<Args...> const &args); template <typename... Args> self_type &print(bwf::Format const &fmt, Args &&...args); template <typename... Args> self_type &print_v(bwf::Format const &fmt, std::tuple<Args...> const &args); /// @endcond protected: // Used for derived classes where we don't want to throw exceptions. FixedBufferWriter(char *buffer, size_t capacity, bool noexcept_flag) noexcept; char *const _buffer; ///< Output buffer. size_t _capacity; ///< Size of output buffer. size_t _attempted = 0; ///< Number of characters written, including those discarded due error condition. }; /** A @c BufferWriter that has an internal buffer. * * @tparam N Number of bytes in internal buffer. * * The buffer is part of the class instance and is therefore allocated from the same memory pool * as the object. E.g, if this is declared as a local variable the buffer is on the stack. * * This was written to make code such as * @code * char buff[1024]; * FixedBufferWriter w(buff, sizeof(buff)); * @endcode * simpler as * @code * LocalBufferWriter<1024> w; * @endcode * * This also makes it possible to use inside expressions and other stream operations without concern * about having to previously declare the storage. E.g. * @code * create_note(LocalBufferWriter<256>().print("Note {}", idx).view()); * @endcode */ template <size_t N> class LocalBufferWriter : public FixedBufferWriter { using self_type = LocalBufferWriter; using super_type = FixedBufferWriter; public: /// Construct an empty writer. LocalBufferWriter() noexcept; LocalBufferWriter(const LocalBufferWriter &that) = delete; LocalBufferWriter &operator=(const LocalBufferWriter &that) = delete; protected: char _arr[N]; ///< output buffer. }; // --------------- Implementation -------------------- inline BufferWriter::~BufferWriter() {} inline BufferWriter & BufferWriter::write(const void *data, size_t length) { const char *d = static_cast<const char *>(data); while (length--) { this->write(*(d++)); } return *this; } #if 0 inline BufferWriter & BufferWriter::write(const std::string_view &sv) { return this->write(sv.data(), sv.size()); } #endif inline BufferWriter & BufferWriter::write(MemSpan<void const> span) { return this->write(span.data(), span.size()); } inline char * BufferWriter::aux_data() { return nullptr; } inline size_t BufferWriter::size() const { return std::min(this->extent(), this->capacity()); } inline size_t BufferWriter::remaining() const { return this->capacity() - this->size(); } // --- FixedBufferWriter --- inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buffer(buffer), _capacity(capacity) { if (_capacity != 0 && buffer == nullptr) { throw(std::invalid_argument{"FixedBufferWriter created with null buffer and non-zero size."}); }; } inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity, bool /* noexcept_flag */) noexcept : _buffer(buffer), _capacity(capacity) { } inline FixedBufferWriter::FixedBufferWriter(MemSpan<void> const &span) : _buffer{static_cast<char *>(span.data())}, _capacity{span.size()} {} inline FixedBufferWriter::FixedBufferWriter(MemSpan<char> const &span) : _buffer{span.begin()}, _capacity{span.size()} {} inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buffer(nullptr), _capacity(0) {} inline FixedBufferWriter::self_type & FixedBufferWriter::detach() { const_cast<char *&>(_buffer) = nullptr; _capacity = 0; _attempted = 0; return *this; } inline FixedBufferWriter::FixedBufferWriter(FixedBufferWriter &&that) : _buffer(that._buffer), _capacity(that._capacity), _attempted(that._attempted) { that.detach(); } inline FixedBufferWriter::self_type & FixedBufferWriter::assign(MemSpan<char> const &span) { const_cast<char *&>(_buffer) = span.data(); _capacity = span.size(); _attempted = 0; return *this; } inline FixedBufferWriter & FixedBufferWriter::operator=(FixedBufferWriter &&that) { const_cast<char *&>(_buffer) = that._buffer; _capacity = that._capacity; _attempted = that._attempted; that.detach(); return *this; } inline FixedBufferWriter & FixedBufferWriter::write(char c) { if (_attempted < _capacity) { _buffer[_attempted] = c; } ++_attempted; return *this; } inline FixedBufferWriter & FixedBufferWriter::write(const void *data, size_t length) { const size_t newSize = _attempted + length; if (_buffer) { if (newSize <= _capacity) { std::memcpy(_buffer + _attempted, data, length); } else if (_attempted < _capacity) { std::memcpy(_buffer + _attempted, data, _capacity - _attempted); } } _attempted = newSize; return *this; } /// Return the output buffer. inline const char * FixedBufferWriter::data() const { return _buffer; } inline bool FixedBufferWriter::error() const { return _attempted > _capacity; } inline char * FixedBufferWriter::aux_data() { return error() ? nullptr : _buffer + _attempted; } inline bool FixedBufferWriter::commit(size_t n) { _attempted += n; return true; } inline size_t FixedBufferWriter::capacity() const { return _capacity; } inline size_t FixedBufferWriter::extent() const { return _attempted; } inline auto FixedBufferWriter::restrict(size_t n) -> self_type & { if (n > _capacity) { throw(std::invalid_argument{"FixedBufferWriter restrict value more than capacity"}); } _capacity -= n; return *this; } inline auto FixedBufferWriter::restore(size_t n) -> self_type & { if (error()) { _attempted = _capacity; } _capacity += n; return *this; } inline auto FixedBufferWriter::discard(size_t n) -> self_type & { _attempted -= std::min(_attempted, n); return *this; } inline auto FixedBufferWriter::clear() -> self_type & { _attempted = 0; return *this; } inline auto FixedBufferWriter::copy(size_t dst, size_t src, size_t n) -> self_type & { auto limit = std::min<size_t>(_capacity, _attempted); // max offset of region possible. MemSpan<char> src_span{_buffer + src, std::min(limit, src + n)}; MemSpan<char> dst_span{_buffer + dst, std::min(limit, dst + n)}; std::memmove(dst_span.data(), src_span.data(), std::min(dst_span.size(), src_span.size())); return *this; } inline swoc::TextView FixedBufferWriter::view() const { return {_buffer, size()}; } inline FixedBufferWriter::operator std::string_view() const { return this->view(); } inline FixedBufferWriter::operator swoc::TextView() const { return this->view(); } // --- LocalBufferWriter --- template <size_t N> LocalBufferWriter<N>::LocalBufferWriter() noexcept : super_type(_arr, N, true) {} }} // namespace swoc::SWOC_VERSION_NS /// @cond NOT_DOCUMENTED namespace std { inline ostream & operator<<(ostream &s, swoc::BufferWriter const &w) { return w >> s; } } // end namespace std /// @endcond