blob: 103af0b62b8463e5319dbe8abb67a5cbf2544dd4 [file] [log] [blame]
/** @file
Utilities for generating character sequences in buffers.
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <cstdlib>
#include <utility>
#include <cstring>
#include <vector>
#include <string>
#include <iosfwd>
#include <string_view>
#include "tscpp/util/TextView.h"
#include "tscpp/util/MemSpan.h"
#include "tscore/BufferWriterForward.h"
namespace ts
{
/** Base (abstract) class for concrete buffer writers.
*/
class BufferWriter
{
public:
/** Add the character @a c to the buffer.
@a c is added only if there is room in the buffer. If not, the instance is put in to an error
state. In either case the value for @c extent is incremented.
@internal If any variant of @c write discards any characters, the instance must be put in an
error state (indicated by the override of @c error). Derived classes must not assume the
write() functions will not be called when the instance is in an error state.
@return @c *this
*/
virtual BufferWriter &write(char c) = 0;
/** Add @a data to the buffer, up to @a length bytes.
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 @a length.
@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.
@return @c *this
*/
virtual BufferWriter &
write(const void *data, size_t length)
{
const char *d = static_cast<const char *>(data);
while (length--) {
write(*(d++));
}
return *this;
}
/** 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)
{
return write(sv.data(), sv.size());
}
/// Get the 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;
/** Get the address of the next output byte in the buffer.
Succeeding calls to non-const member functions, other than this method, must be presumed to
invalidate the current auxiliary buffer (contents and address).
Care must be taken to not write to data beyond this plus @c remaining bytes. Usually the
safest mechanism is to create a @c FixedBufferWriter on the auxiliary buffer and write to that.
@code
ts::FixedBufferWriter subw(w.auxBuffer(), w.remaining());
write_some_stuff(subw); // generate output into the buffer.
w.fill(subw.extent()); // update main buffer writer.
@endcode
@return Address of the next output byte, or @c nullptr if there is no remaining capacity.
*/
virtual char *
auxBuffer()
{
return nullptr;
}
/** Advance the buffer position @a n bytes.
This treats the next @a n bytes as being written without changing the content. This is useful
only in conjunction with @a auxBuffer to indicate that @a n bytes of the auxiliary buffer has
been written by some other mechanism.
@internal Concrete subclasses @b must override this to advance in a way consistent with the
specific buffer type.
@return @c *this
*/
virtual BufferWriter &
fill(size_t n)
{
return *this;
}
/// Get the total capacity.
/// @return The total number of bytes that can be written without causing an error condition.
virtual size_t capacity() const = 0;
/// Get the extent.
/// @return Total number of characters that have been written, including those discarded due to an error condition.
virtual size_t extent() const = 0;
/// Get the output size.
/// @return Total number of characters that are in the buffer (successfully written and not discarded)
size_t
size() const
{
return std::min(this->extent(), this->capacity());
}
/// Get the remaining buffer space.
/// @return Number of additional characters that can be written without causing an error condition.
size_t
remaining() const
{
return capacity() - size();
}
/// 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.
/// @return @c *this
virtual BufferWriter &clip(size_t n) = 0;
/// Increase the capacity by @a n bytes.
/// 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.
virtual BufferWriter &extend(size_t n) = 0;
// Force virtual destructor.
virtual ~BufferWriter() {}
/** BufferWriter print.
This prints its arguments to the @c BufferWriter @a w according to the format @a fmt. The format
string is based on Python style formatting, each argument substitution marked by braces, {}. Each
specification has three parts, a @a name, a @a specifier, and an @a extension. These are
separated by colons. The name should be either omitted or a number, the index of the argument to
use. If omitted the place in the format string is used as the argument index. E.g. "{} {} {}",
"{} {1} {}", and "{0} {1} {2}" are equivalent. Using an explicit index does not reset the
position of subsequent substitutions, therefore "{} {0} {}" is equivalent to "{0} {0} {2}".
*/
template <typename... Rest> BufferWriter &print(TextView fmt, Rest &&... rest);
/** Print overload to take arguments as a tuple instead of explicitly.
This is useful for forwarding variable arguments from other functions / methods.
*/
template <typename... Args> BufferWriter &printv(TextView fmt, std::tuple<Args...> const &args);
/// Print using a preparsed @a fmt.
template <typename... Args> BufferWriter &print(BWFormat const &fmt, Args &&... args);
/** Print overload to take arguments as a tuple instead of explicitly.
This is useful for forwarding variable arguments from other functions / methods.
*/
template <typename... Args> BufferWriter &printv(BWFormat const &fmt, std::tuple<Args...> const &args);
/// Output the buffer contents to the @a stream.
/// @return The destination stream.
virtual std::ostream &operator>>(std::ostream &stream) const = 0;
/// Output the buffer contents to the file for file descriptor @a fd.
/// @return The number of bytes written.
virtual ssize_t operator>>(int fd) const = 0;
};
/** A @c BufferWrite concrete subclass to write to a fixed size buffer.
*
* Copies and moves are forbidden because that leaves the original in a potentially bad state. An
* instance is cheap to construct and should be done explicitly when needed.
*/
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.
@note If you create a instance of this class with capacity == 0 (and a nullptr buffer), you
can use it to measure the number of characters a series of writes would result it (from the
extent() value) without actually writing.
*/
FixedBufferWriter(char *buffer, size_t capacity);
/** Construct empty buffer.
* This is useful for doing sizing before allocating a buffer.
*/
FixedBufferWriter(std::nullptr_t);
FixedBufferWriter(const FixedBufferWriter &) = delete;
FixedBufferWriter &operator=(const FixedBufferWriter &) = delete;
FixedBufferWriter(FixedBufferWriter &&) = delete;
FixedBufferWriter &operator=(FixedBufferWriter &&) = delete;
FixedBufferWriter(MemSpan<char> &span) : _buf(span.begin()), _capacity(static_cast<size_t>(span.size())) {}
/// Write a single character @a c to the buffer.
FixedBufferWriter &
write(char c) override
{
if (_attempted < _capacity) {
_buf[_attempted] = c;
}
++_attempted;
return *this;
}
/// Write @a data to the buffer, up to @a length bytes.
FixedBufferWriter &
write(const void *data, size_t length) override
{
const size_t newSize = _attempted + length;
if (_buf) {
if (newSize <= _capacity) {
std::memcpy(_buf + _attempted, data, length);
} else if (_attempted < _capacity) {
std::memcpy(_buf + _attempted, data, _capacity - _attempted);
}
}
_attempted = newSize;
return *this;
}
// Bring in non-overridden methods.
using super_type::write;
/// Return the output buffer.
const char *
data() const override
{
return _buf;
}
/// Return whether there has been an error.
bool
error() const override
{
return _attempted > _capacity;
}
/// Get the start of the unused output buffer.
char *
auxBuffer() override
{
return error() ? nullptr : _buf + _attempted;
}
/// Advance the used part of the output buffer.
FixedBufferWriter &
fill(size_t n) override
{
_attempted += n;
return *this;
}
/// Get the total capacity of the output buffer.
size_t
capacity() const override
{
return _capacity;
}
/// Get the total output sent to the writer.
size_t
extent() const override
{
return _attempted;
}
/// Reduce the capacity by @a n.
FixedBufferWriter &
clip(size_t n) override
{
ink_assert(n <= _capacity);
_capacity -= n;
return *this;
}
/// Extend the capacity by @a n.
FixedBufferWriter &
extend(size_t n) override
{
if (error()) {
_attempted = _capacity;
}
_capacity += n;
return *this;
}
/// Reduce extent to @a n.
/// If @a n is less than the capacity the error condition, if any, is cleared.
/// This can be used to clear the output by calling @c reduce(0). In contrast
/// to @c clip this reduces the data in the buffer, rather than the capacity.
self_type &
reduce(size_t n)
{
ink_assert(n <= _attempted);
_attempted = n;
return *this;
}
/// Clear the buffer, reset to empty (no data).
/// This is a convenience for reusing a buffer. For instance
/// @code
/// bw.reset().print("....."); // clear old data and print new data.
/// @endcode
/// This is equivalent to @c reduce(0) but clearer for that case.
self_type &
reset()
{
_attempted = 0;
return *this;
}
/// Provide a string_view of all successfully written characters.
std::string_view
view() const
{
return std::string_view(_buf, size());
}
/// Provide a @c string_view of all successfully written characters as a user conversion.
operator std::string_view() const { return view(); }
/** Get a @c FixedBufferWriter for the unused output buffer.
If @a reserve is non-zero then the buffer size for the auxiliary writer will be @a reserve bytes
smaller than the remaining buffer. This "reserves" space for additional output after writing
to the auxiliary buffer, in a manner similar to @c clip / @c extend.
*/
FixedBufferWriter
auxWriter(size_t reserve = 0)
{
return {this->auxBuffer(), reserve < this->remaining() ? this->remaining() - reserve : 0};
}
/// Output the buffer contents to the @a stream.
std::ostream &operator>>(std::ostream &stream) const override;
/// Output the buffer contents to the file for file descriptor @a fd.
ssize_t operator>>(int fd) const override;
// Overrides for co-variance
template <typename... Rest> self_type &print(TextView fmt, Rest &&... rest);
template <typename... Args> self_type &printv(TextView fmt, std::tuple<Args...> const &args);
template <typename... Args> self_type &print(BWFormat const &fmt, Args &&... args);
template <typename... Args> self_type &printv(BWFormat const &fmt, std::tuple<Args...> const &args);
protected:
char *const _buf; ///< Output buffer.
size_t _capacity; ///< Size of output buffer.
size_t _attempted = 0; ///< Number of characters written, including those discarded due error condition.
private:
// INTERNAL - Overload removed, make sure it's not used.
BufferWriter &write(size_t n);
};
/** A buffer writer that writes to an array of char (of fixed size N) that is internal to the writer instance.
It's called 'local' because instances are typically declared as stack-allocated, local function
variables.
*/
template <size_t N> class LocalBufferWriter : public FixedBufferWriter
{
using self_type = LocalBufferWriter;
using super_type = FixedBufferWriter;
public:
/// Construct an empty writer.
LocalBufferWriter() : FixedBufferWriter(_arr, N) {}
/// Copy another writer.
/// Any data in @a that is copied over.
LocalBufferWriter(const LocalBufferWriter &that) : FixedBufferWriter(_arr, N)
{
std::memcpy(_arr, that._arr, that.size());
_attempted = that._attempted;
}
/// Copy another writer.
/// Any data in @a that is copied over.
template <size_t K> LocalBufferWriter(const LocalBufferWriter<K> &that) : FixedBufferWriter(_arr, N)
{
size_t n = std::min(N, that.size());
std::memcpy(_arr, that.data(), n);
// if a bigger space here, don't leave a gap between size and attempted.
_attempted = N > K ? n : that.extent();
}
/// Copy another writer.
/// Any data in @a that is copied over.
LocalBufferWriter &
operator=(const LocalBufferWriter &that)
{
if (this != &that) {
_attempted = that.extent();
std::memcpy(_buf, that._buf, that.size());
}
return *this;
}
/// Copy another writer.
/// Any data in @a that is copied over.
template <size_t K>
LocalBufferWriter &
operator=(const LocalBufferWriter<K> &that)
{
size_t n = std::min(N, that.size());
// if a bigger space here, don't leave a gap between size and attempted.
_attempted = N > K ? n : that.extent();
std::memcpy(_arr, that.data(), n);
return *this;
}
/// Increase capacity by @a n.
LocalBufferWriter &
extend(size_t n) override
{
if (error()) {
_attempted = _capacity;
}
_capacity += n;
ink_assert(_capacity <= N);
return *this;
}
protected:
char _arr[N]; ///< output buffer.
};
// --------------- Implementation --------------------
/** Overridable formatting for type @a V.
This is the output generator for data to a @c BufferWriter. Default stream operators call this with
the default format specification (although those can be overloaded specifically for performance).
User types should overload this function to format output for that type.
@code
BufferWriter &
bwformat(BufferWriter &w, BWFSpec &, V const &v)
{
// generate output on @a w
}
@endcode
The argument can be passed by value if that would be more efficient.
*/
namespace bw_fmt
{
/// Internal signature for template generated formatting.
/// @a args is a forwarded tuple of arguments to be processed.
template <typename TUPLE> using ArgFormatterSignature = BufferWriter &(*)(BufferWriter &w, BWFSpec const &, TUPLE const &args);
/// Internal error / reporting message generators
void Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n);
// MSVC will expand the parameter pack inside a lambda but not gcc, so this indirection is required.
/// This selects the @a I th argument in the @a TUPLE arg pack and calls the formatter on it. This
/// (or the equivalent lambda) is needed because the array of formatters must have a homogenous
/// signature, not vary per argument. Effectively this indirection erases the type of the specific
/// argument being formatted. Instances of this have the signature @c ArgFormatterSignature.
template <typename TUPLE, size_t I>
BufferWriter &
Arg_Formatter(BufferWriter &w, BWFSpec const &spec, TUPLE const &args)
{
return bwformat(w, spec, std::get<I>(args));
}
/// This exists only to expand the index sequence into an array of formatters for the tuple type
/// @a TUPLE. Due to language limitations it cannot be done directly. The formatters can be
/// accessed via standard array access in contrast to templated tuple access. The actual array is
/// static and therefore at run time the only operation is loading the address of the array.
template <typename TUPLE, size_t... N> ArgFormatterSignature<TUPLE> *Get_Arg_Formatter_Array(std::index_sequence<N...>)
{
static ArgFormatterSignature<TUPLE> fa[sizeof...(N)] = {&bw_fmt::Arg_Formatter<TUPLE, N>...};
return fa;
}
/// Perform alignment adjustments / fill on @a w of the content in @a lw.
/// This is the normal mechanism, but a number of the builtin types handle this internally
/// for performance reasons.
void Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw);
/// Global named argument table.
using GlobalSignature = void (*)(BufferWriter &, BWFSpec const &);
using GlobalTable = std::map<std::string_view, GlobalSignature>;
extern GlobalTable BWF_GLOBAL_TABLE;
extern GlobalSignature Global_Table_Find(std::string_view name);
/// Generic integral conversion.
BufferWriter &Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t n, bool negative_p);
/// Generic floating point conversion.
BufferWriter &Format_Floating(BufferWriter &w, BWFSpec const &spec, double n, bool negative_p);
} // namespace bw_fmt
using BWGlobalNameSignature = bw_fmt::GlobalSignature;
/// Add a global @a name to BufferWriter formatting, output generated by @a formatter.
/// @return @c true if the name was register, @c false if not (name already in use).
bool bwf_register_global(std::string_view name, BWGlobalNameSignature formatter);
/** Compiled BufferWriter format.
@note This is not as useful as hoped, the performance is not much better using this than parsing
on the fly (about 30% better, which is fine for tight loops but not for general use).
*/
class BWFormat
{
public:
/// Construct from a format string @a fmt.
BWFormat(TextView fmt);
~BWFormat();
/** Parse elements of a format string.
@param fmt The format string [in|out]
@param literal A literal if found
@param spec A specifier if found (less enclosing braces)
@return @c true if a specifier was found, @c false if not.
Pull off the next literal and/or specifier from @a fmt. The return value distinguishes
the case of no specifier found (@c false) or an empty specifier (@c true).
*/
static bool parse(TextView &fmt, std::string_view &literal, std::string_view &spec);
/** Parsed items from the format string.
Literals are handled by putting the literal text in the extension field and setting the
global formatter @a _gf to @c LiteralFormatter, which writes out the extension as a literal.
*/
struct Item {
BWFSpec _spec; ///< Specification.
/// If the spec has a global formatter name, cache it here.
mutable bw_fmt::GlobalSignature _gf = nullptr;
Item() {}
Item(BWFSpec const &spec, bw_fmt::GlobalSignature gf) : _spec(spec), _gf(gf) {}
};
using Items = std::vector<Item>;
Items _items; ///< Items from format string.
protected:
/// Handles literals by writing the contents of the extension directly to @a w.
static void Format_Literal(BufferWriter &w, BWFSpec const &spec);
};
template <typename... Args>
BufferWriter &
BufferWriter::print(TextView fmt, Args &&... args)
{
return this->printv(fmt, std::forward_as_tuple(args...));
}
template <typename... Args>
BufferWriter &
BufferWriter::printv(TextView fmt, std::tuple<Args...> const &args)
{
using namespace std::literals;
static constexpr int N = sizeof...(Args); // used as loop limit
static const auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Args...>{});
int arg_idx = 0; // the next argument index to be processed.
while (fmt.size()) {
// Next string piece of interest is an (optional) literal and then an (optional) format specifier.
// There will always be a specifier except for the possible trailing literal.
std::string_view lit_v;
std::string_view spec_v;
bool spec_p = BWFormat::parse(fmt, lit_v, spec_v);
if (lit_v.size()) {
this->write(lit_v);
}
if (spec_p) {
BWFSpec spec{spec_v}; // parse the specifier.
size_t width = this->remaining();
if (spec._max < width) {
width = spec._max;
}
FixedBufferWriter lw{this->auxBuffer(), width};
if (spec._name.size() == 0) {
spec._idx = arg_idx;
}
if (0 <= spec._idx) {
if (spec._idx < N) {
fa[spec._idx](lw, spec, args);
} else {
bw_fmt::Err_Bad_Arg_Index(lw, spec._idx, N);
}
++arg_idx;
} else if (spec._name.size()) {
auto gf = bw_fmt::Global_Table_Find(spec._name);
if (gf) {
gf(lw, spec);
} else {
lw.write("{~"sv).write(spec._name).write("~}"sv);
}
}
if (lw.extent()) {
bw_fmt::Do_Alignment(spec, *this, lw);
}
}
}
return *this;
}
template <typename... Args>
BufferWriter &
BufferWriter::print(BWFormat const &fmt, Args &&... args)
{
return this->printv(fmt, std::forward_as_tuple(args...));
}
template <typename... Args>
BufferWriter &
BufferWriter::printv(BWFormat const &fmt, std::tuple<Args...> const &args)
{
using namespace std::literals;
static constexpr int N = sizeof...(Args);
static const auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Args...>{});
for (BWFormat::Item const &item : fmt._items) {
size_t width = this->remaining();
if (item._spec._max < width) {
width = item._spec._max;
}
FixedBufferWriter lw{this->auxBuffer(), width};
if (item._gf) {
item._gf(lw, item._spec);
} else {
auto idx = item._spec._idx;
if (0 <= idx && idx < N) {
fa[idx](lw, item._spec, args);
} else if (item._spec._name.size()) {
lw.write("{~"sv).write(item._spec._name).write("~}"sv);
}
}
bw_fmt::Do_Alignment(item._spec, *this, lw);
}
return *this;
}
// Must be first so that other inline formatters can use it.
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv);
// Pointers that are not specialized.
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, const void *ptr)
{
BWFSpec ptr_spec{spec};
ptr_spec._radix_lead_p = true;
if (ptr == nullptr) {
if (spec._type == 's' || spec._type == 'S') {
ptr_spec._type = BWFSpec::DEFAULT_TYPE;
ptr_spec._ext = ""_sv; // clear any extension.
return bwformat(w, spec, spec._type == 's' ? "null"_sv : "NULL"_sv);
} else if (spec._type == BWFSpec::DEFAULT_TYPE) {
return w; // print nothing if there is no format character override.
}
}
if (ptr_spec._type == BWFSpec::DEFAULT_TYPE || ptr_spec._type == 'p') {
ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex.
} else if (ptr_spec._type == 'P') {
ptr_spec._type = 'X'; // P means upper hex, overriding other specializations.
}
return bw_fmt::Format_Integer(w, ptr_spec, reinterpret_cast<intptr_t>(ptr), false);
}
// MemSpan
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan<void> const &span);
// -- Common formatters --
// Capture this explicitly so it doesn't go to any other pointer type.
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, std::nullptr_t)
{
return bwformat(w, spec, static_cast<void *>(nullptr));
}
template <size_t N>
BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, const char (&a)[N])
{
return bwformat(w, spec, std::string_view(a, N - 1));
}
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, const char *v)
{
if (spec._type == 'x' || spec._type == 'X' || spec._type == 'p') {
bwformat(w, spec, static_cast<const void *>(v));
} else if (v != nullptr) {
bwformat(w, spec, std::string_view(v));
} else {
bwformat(w, spec, nullptr);
}
return w;
}
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, TextView tv)
{
return bwformat(w, spec, static_cast<std::string_view>(tv));
}
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, std::string const &s)
{
return bwformat(w, spec, std::string_view{s});
}
template <typename F>
auto
bwformat(BufferWriter &w, BWFSpec const &spec, F &&f) ->
typename std::enable_if<std::is_floating_point<typename std::remove_reference<F>::type>::value, BufferWriter &>::type
{
return f < 0 ? bw_fmt::Format_Floating(w, spec, -f, true) : bw_fmt::Format_Floating(w, spec, f, false);
}
/* Integer types.
Due to some oddities for MacOS building, need a bit more template magic here. The underlying
integer rendering is in @c Format_Integer which takes @c intmax_t or @c uintmax_t. For @c
bwformat templates are defined, one for signed and one for unsigned. These forward their argument
to the internal renderer. To avoid additional ambiguity the template argument is checked with @c
std::enable_if to invalidate the overload if the argument type isn't a signed / unsigned
integer. One exception to this is @c char which is handled by a previous overload in order to
treat the value as a character and not an integer. The overall benefit is this works for any set
of integer types, rather tuning and hoping to get just the right set of overloads.
*/
template <typename I>
auto
bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) ->
typename std::enable_if<std::is_unsigned<typename std::remove_reference<I>::type>::value &&
std::is_integral<typename std::remove_reference<I>::type>::value,
BufferWriter &>::type
{
return bw_fmt::Format_Integer(w, spec, i, false);
}
template <typename I>
auto
bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) ->
typename std::enable_if<std::is_signed<typename std::remove_reference<I>::type>::value &&
std::is_integral<typename std::remove_reference<I>::type>::value,
BufferWriter &>::type
{
return i < 0 ? bw_fmt::Format_Integer(w, spec, -i, true) : bw_fmt::Format_Integer(w, spec, i, false);
}
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &, char c)
{
return w.write(c);
}
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, bool f)
{
using namespace std::literals;
if ('s' == spec._type) {
w.write(f ? "true"sv : "false"sv);
} else if ('S' == spec._type) {
w.write(f ? "TRUE"sv : "FALSE"sv);
} else {
bw_fmt::Format_Integer(w, spec, static_cast<uintmax_t>(f), false);
}
return w;
}
// Generically a stream operator is a formatter with the default specification.
template <typename V>
BufferWriter &
operator<<(BufferWriter &w, V &&v)
{
return bwformat(w, BWFSpec::DEFAULT, std::forward<V>(v));
}
// std::string support
/** Print to a @c std::string
Print to the string @a s. If there is overflow then resize the string sufficiently to hold the output
and print again. The effect is the string is resized only as needed to hold the output.
*/
template <typename... Args>
std::string &
bwprintv(std::string &s, ts::TextView fmt, std::tuple<Args...> const &args)
{
auto len = s.size(); // remember initial size
size_t n = ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args)).extent();
s.resize(n); // always need to resize - if shorter, must clip pre-existing text.
if (n > len) { // dropped data, try again.
ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args));
}
return s;
}
template <typename... Args>
std::string &
bwprint(std::string &s, ts::TextView fmt, Args &&... args)
{
return bwprintv(s, fmt, std::forward_as_tuple(args...));
}
// -- FixedBufferWriter --
inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buf(nullptr), _capacity(0) {}
inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buf(buffer), _capacity(capacity)
{
ink_assert(_capacity == 0 || buffer != nullptr);
}
template <typename... Args>
inline auto
FixedBufferWriter::print(TextView fmt, Args &&... args) -> self_type &
{
return static_cast<self_type &>(this->super_type::printv(fmt, std::forward_as_tuple(args...)));
}
template <typename... Args>
inline auto
FixedBufferWriter::printv(TextView fmt, std::tuple<Args...> const &args) -> self_type &
{
return static_cast<self_type &>(this->super_type::printv(fmt, args));
}
template <typename... Args>
inline auto
FixedBufferWriter::print(BWFormat const &fmt, Args &&... args) -> self_type &
{
return static_cast<self_type &>(this->super_type::printv(fmt, std::forward_as_tuple(args...)));
}
template <typename... Args>
inline auto
FixedBufferWriter::printv(BWFormat const &fmt, std::tuple<Args...> const &args) -> self_type &
{
return static_cast<self_type &>(this->super_type::printv(fmt, args));
}
// Basic format wrappers - these are here because they're used internally.
namespace bwf
{
namespace detail
{
/** Write out raw memory in hexadecimal wrapper.
*
* This wrapper indicates the contained view should be dumped as raw memory in hexadecimal format.
* This is intended primarily for internal use by other formatting logic.
*
* @see Hex_Dump
*/
struct MemDump {
std::string_view _view;
/** Dump @a n bytes starting at @a mem as hex.
*
* @param mem First byte of memory to dump.
* @param n Number of bytes.
*/
MemDump(void const *mem, size_t n) : _view(static_cast<char const *>(mem), n) {}
};
} // namespace detail
/** Treat @a t as raw memory and dump the memory as hexadecimal.
*
* @tparam T Type of argument.
* @param t Object to dump.
* @return @a A wrapper to do a hex dump.
*
* This is the standard way to do a hexadecimal memory dump of an object.
*
* @internal This function exists so that other types can overload it for special processing,
* which would not be possible with just @c HexDump.
*/
template <typename T>
detail::MemDump
Hex_Dump(T const &t)
{
return {&t, sizeof(T)};
}
} // namespace bwf
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::detail::MemDump const &hex);
} // end namespace ts
namespace std
{
inline ostream &
operator<<(ostream &s, ts::BufferWriter const &w)
{
return w >> s;
}
} // end namespace std