| // 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" |
| |
| namespace swoc { inline namespace SWOC_VERSION_NS { |
| namespace bwf { |
| struct Spec; |
| |
| class Format; |
| |
| class NameBinding; |
| |
| class ArgPack; |
| } // namespace bwf |
| |
| /** 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); |
| |
| /** Add the contents of @a sv to the buffer, up to the size of the view. |
| |
| Data is added only up to the remaining room in the buffer. If the remaining capacity is |
| exceeded (i.e. data is not written to the output), the instance is put in to an error |
| state. In either case the value for @c extent is incremented by the size of @a sv. |
| |
| @return @c *this |
| */ |
| BufferWriter& write(const std::string_view& sv); |
| |
| /// Address of the first byte in the output 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); |
| |
| /** 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. |
| std::string_view view() const; |
| |
| /// Provide a @c string_view of all successfully written characters as a user conversion. |
| operator std::string_view() 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: |
| 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(); |
| |
| 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; |
| } |
| |
| inline BufferWriter& |
| BufferWriter::write(const std::string_view& sv) { |
| return this->write(sv.data(), sv.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(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 std::string_view |
| FixedBufferWriter::view() const { |
| return {_buffer, size()}; |
| } |
| |
| /// Provide a @c string_view of all successfully written characters as a user conversion. |
| inline FixedBufferWriter::operator std::string_view() const { |
| return this->view(); |
| } |
| |
| // --- LocalBufferWriter --- |
| template<size_t N> LocalBufferWriter<N>::LocalBufferWriter() : super_type(_arr, N) {} |
| |
| }} // namespace swoc |
| |
| namespace std |
| { |
| inline ostream & |
| operator<<(ostream &s, swoc::BufferWriter const &w) { |
| return w >> s; |
| } |
| } // end namespace std |