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 ©(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 ©(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