You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2018/03/19 22:37:32 UTC
[trafficserver] branch master updated: bwprint: Type safe printf
like output to BufferWriter instances.
This is an automated email from the ASF dual-hosted git repository.
amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new f8b7db0 bwprint: Type safe printf like output to BufferWriter instances.
f8b7db0 is described below
commit f8b7db0e7cdeda539ecbfda2204ec42da1a6c2a0
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Sun Feb 25 18:17:56 2018 -0600
bwprint: Type safe printf like output to BufferWriter instances.
---
.../internal-libraries/MemSpan.en.rst | 3 +-
.../internal-libraries/buffer-writer.en.rst | 130 +++++-
lib/ts/BufferWriter.h | 281 +++++++++--
lib/ts/BufferWriterFormat.cc | 517 +++++++++++++++++++++
lib/ts/BufferWriterForward.h | 102 ++++
lib/ts/Makefile.am | 5 +-
lib/ts/ink_std_compat.h | 95 ++++
lib/ts/unit-tests/test_BufferWriter.cc | 20 +-
lib/ts/unit-tests/test_BufferWriterFormat.cc | 200 ++++++++
9 files changed, 1291 insertions(+), 62 deletions(-)
diff --git a/doc/developer-guide/internal-libraries/MemSpan.en.rst b/doc/developer-guide/internal-libraries/MemSpan.en.rst
index ce5f7df..a9b6775 100644
--- a/doc/developer-guide/internal-libraries/MemSpan.en.rst
+++ b/doc/developer-guide/internal-libraries/MemSpan.en.rst
@@ -95,4 +95,5 @@ Reference
Strong caution must be used with containers such as :code:`std::vector` or :code:`std::string`
because the lifetime of the memory can be much less than the lifetime of the container. In
particular, adding or removing any element from a :code:`std::vector` can cause a re-allocation,
- invalidating any view of the original memory. In general views should be treated like iterators.
+ invalidating any view of the original memory. In general views should be treated like iterators,
+ suitable for passing to nested function calls but not for storing.
diff --git a/doc/developer-guide/internal-libraries/buffer-writer.en.rst b/doc/developer-guide/internal-libraries/buffer-writer.en.rst
index d72d7ba..7e05547 100644
--- a/doc/developer-guide/internal-libraries/buffer-writer.en.rst
+++ b/doc/developer-guide/internal-libraries/buffer-writer.en.rst
@@ -25,11 +25,6 @@
BufferWriter
*************
-:class:`BufferWriter` is designed to make writing text to a buffer fast and safe. The output buffer
-can have a size and :class:`BufferWriter` will prevent writing past the end, while tracking the
-theoretical output to enable buffer resizing after the fact. This also lets a :class:`BufferWriter`
-instance write into the middle of a larger buffer, making nested output logic easy to build.
-
Synopsis
++++++++
@@ -40,6 +35,17 @@ Synopsis
Description
+++++++++++
+:class:`BufferWriter` is designed to make writing text to a buffer fast, convenient, and safe. It is
+easier and less error-prone than using a combination of :code:`sprintf` and :code:`memcpy` as is
+done in many places in the code.. A :class:`BufferWriter` can have a size and will prevent writing
+past the end, while tracking the theoretical output to enable buffer resizing after the fact. This
+also lets a :class:`BufferWriter` instance write into the middle of a larger buffer, making nested
+output logic easy to build.
+
+The header files are divided in to two variants. ``BufferWriter.h`` provides the basic capabilities
+of buffer output control. ``BufferWriterFormat.h`` provides formatted output mechanisms, primarily
+the implementation and ancillary classes for :func:`BufferWriter::print`.
+
:class:`BufferWriter` is an abstract base class, in the style of :code:`std::ostream`. There are
several subclasses for various use cases. When passing around this is the common type.
@@ -84,7 +90,7 @@ Several basic types are overloaded and it is easy to extend to additional types.
.. code-block:: cpp
- ts::BufferWriter & operator << (ts::BufferWriter & w, ts::TextView const & sv) {
+ ts::BufferWriter & operator << (ts::BufferWriter & w, TextView const & sv) {
w.write(sv.data(), sv.size());
return w;
}
@@ -263,6 +269,10 @@ Reference
well with the standard "try before you buy" approach of attempting to write output, counting
the characters needed, then allocating a sufficiently sized buffer and actually writing.
+ .. function:: BufferWriter & print(TextView fmt, ...)
+
+ Print the arguments according to the format. See `bw-formatting`_.
+
.. class:: FixedBufferWriter : public BufferWriter
This is a class that implements :class:`BufferWriter` on a fixed buffer, passed in to the constructor.
@@ -294,6 +304,114 @@ Reference
Construct an instance with a capacity of :arg:`N`.
+.. _bw-formatting:
+
+Formatted Output
+++++++++++++++++
+
+:class:`BufferWriter` supports formatting output in a style similar to Python formatting via
+:func:`BufferWriter::print`. This takes a format string which then controls the use of subsquent
+arguments in generating out in the buffer. The basic format is divided in to three parts, separated by colons.
+
+.. productionList:: BufferWriterFormat
+ Format: "{" [name] [":" [specifier] [":" extension]] "}"
+ name: index | name
+ extension: <printable character except "{}">*
+
+:arg:`name`
+ The name of the argument to use. This can be a number in which case it is the zero based index of the argument to the method call. E.g. ``{0}`` means the first argument and ``{2}`` is the third argument after the format.
+
+ ``bw.print("{0} {1}", 'a', 'b')`` => ``a b``
+
+ ``bw.print("{1} {0}", 'a', 'b')`` => ``b a``
+
+ The name can be omitted in which case it is treated as an index in parallel to the position in
+ the format string. Only the position in the format string matters, not what names those other
+ format elements may have used.
+
+ ``bw.print("{0} {2} {}", 'a', 'b', 'c')`` => ``a c c``
+
+ ``bw.print("{0} {2} {2}", 'a', 'b', 'c')`` => ``a c c``
+
+ Note that an argument can be printed more than once if the name is used more than once.
+
+ ``bw.print("{0} {} {0}", 'a', 'b')`` => ``a b a``
+
+ ``bw.print("{0} {1} {0}", 'a', 'b')`` => ``a b a``
+
+ Alphanumeric names refer to values in a global table. These will be described in more detail someday.
+
+:arg:`specifier`
+ Basic formatting control.
+
+ .. productionList:: specifier
+ specifier: [[fill]align][sign]["#"]["0"][[min][.precision][,max][type]]
+ fill: <printable character except "{}%:"> | URI-char
+ URI-char: "%" hex-digit hex-digit
+ align: "<" | ">" | "=" | "^"
+ sign: "+" | "-" | " "
+ min: integer
+ precision: integer
+ max: integer
+ type: "x" | "o" | "b"
+
+ The output is placed in a field that is at least :token:`min` wide and no more than :token:`max` wide. If
+ the output is less than :token:`min` then
+
+ * The :token:`fill` character is used for the extra space required. This can be an explicit
+ character or a URI encoded one (to allow otherwise reserved characters).
+ * The output is shifted according to the :token:`align`.
+
+ <
+ Align to the left, fill to the right.
+
+ >
+ Align to the right, fill to the left.
+
+ ^
+ Align in the middle, fill to left and right.
+
+ =
+ Numerically align, putting the fill between the output and the sign character.
+
+ The output is clipped by :token:`max` width characters or the end of the buffer. :token:`precision` is used by
+ floating point values to specify the number of places of precision. The precense of the ``#`` character is used for
+ integer values and causes a radix indicator to be used (one of ``0xb``, ``0``, ``0x``).
+
+ :token:`type` is used to indicate type specific formatting. For integers it indicates the output
+ radix. If ``#`` is present the radix is prefix is generated with case matching that of the type
+ (e.g. type ``x`` causes ``0x`` and type ``X`` causes ``0X``).
+
+ = ===============
+ b binary
+ o octal
+ x hexadecimal
+ = ===============
+
+
+:arg:`extension`
+ Text (excluding braces) that is passed to the formatting function. This can be used to provide
+ extensions for specific argument types (e.g., IP addresses). The base logic ignores it but passes
+ it on to the formatting function for the corresponding argument type which can then behave
+ different based on the extension.
+
+User Defined Formatting
++++++++++++++++++++++++
+
+When an value needs to be formatted an overloaded function for type :code:`V` is called.
+
+.. code-block:: cpp
+
+ BufferWriter& ts::bwformat(BufferWriter& w, BWFSpec const& spec, V const& v)
+
+This can (and should be) overloaded for user defined types. This makes it easier and cheaper to
+build one overload on another by tweaking the :arg:`spec` as it passed through. The calling
+framework will handle basic alignment, the overload does not need to unless the alignment
+requirements are more detailed (e.g. integer alignment operations).
+
+The output stream operator :code:`operator<<` is defined to call this function with a default
+constructed :code:`BWFSpec` instance.
+
Futures
+++++++
diff --git a/lib/ts/BufferWriter.h b/lib/ts/BufferWriter.h
index ab6a6fa..71944fa 100644
--- a/lib/ts/BufferWriter.h
+++ b/lib/ts/BufferWriter.h
@@ -27,9 +27,13 @@
#include <stdlib.h>
#include <utility>
#include <cstring>
+#include <vector>
+#include <map>
+#include <ts/ink_std_compat.h>
-#include <ts/string_view.h>
+#include <ts/TextView.h>
#include <ts/ink_assert.h>
+#include <ts/BufferWriterForward.h>
namespace ts
{
@@ -170,6 +174,22 @@ public:
// 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 formating, each argument substitution marked by braces, {}. Each
+ specification has three parts, a @a name, a @a specifier, and an @a extention. 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 substiations, therefore "{} {0} {}" is equivalent to "{0} {0} {2}".
+ */
+ template <typename... Rest> BufferWriter &print(TextView fmt, Rest... rest);
+
+ template <typename... Rest> BufferWriter &print(BWFormat const &fmt, Rest... rest);
+
+ // bwprint(*this, fmt, std::forward<Rest>(rest)...);
};
/** A @c BufferWrite concrete subclass to write to a fixed size buffer.
@@ -412,64 +432,253 @@ protected:
char _arr[N]; ///< output buffer.
};
-// Define stream operators for built in @c write overloads.
+// --------------- 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
+ */
+
+namespace bw_fmt
+{
+ 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 formatter.
+ 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 langauge limitations it cannot be done directly. The formatters can be
+ /// access via standard array access in constrast 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.
+ void Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw);
+
+ /// Global named argument table.
+ using GlobalSignature = void (*)(BufferWriter &, BWFSpec const &);
+ using GlobalTable = std::map<string_view, GlobalSignature>;
+ extern GlobalTable BWF_GLOBAL_TABLE;
+ extern GlobalSignature Global_Table_Find(string_view name);
+
+ /// Generic integral conversion.
+ BufferWriter &Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t n, bool negative_p);
+
+} // bw_fmt
+
+/** Compiled BufferWriter format
+ */
+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, string_view &literal, 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... Rest>
+BufferWriter &
+BufferWriter::print(TextView fmt, Rest... rest)
+{
+ static constexpr int N = sizeof...(Rest);
+ auto args(std::forward_as_tuple(rest...));
+ auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Rest...>{});
+ int arg_idx = 0;
+
+ while (fmt.size()) {
+ string_view lit_v;
+ 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};
+ size_t width = this->remaining();
+ if (spec._max > 0)
+ width = std::min(width, static_cast<size_t>(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);
+ }
+ } else if (spec._name.size()) {
+ auto gf = bw_fmt::Global_Table_Find(spec._name);
+ if (gf) {
+ gf(lw, spec);
+ } else {
+ static constexpr TextView msg{"{invalid name:"};
+ lw.write(msg).write(spec._name).write('}');
+ }
+ }
+ if (lw.size()) {
+ bw_fmt::Do_Alignment(spec, *this, lw);
+ }
+ ++arg_idx;
+ }
+ }
+ return *this;
+}
+
+template <typename... Rest>
+BufferWriter &
+BufferWriter::print(BWFormat const &fmt, Rest... rest)
+{
+ static constexpr int N = sizeof...(Rest);
+ auto const args(std::forward_as_tuple(rest...));
+ static const auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Rest...>{});
+
+ for (BWFormat::Item const &item : fmt._items) {
+ size_t width = this->remaining();
+ size_t max = item._spec._max;
+ if (max && max < width) {
+ width = 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() && (nullptr != (item._gf = bw_fmt::Global_Table_Find(item._spec._name)))) {
+ item._gf(lw, item._spec);
+ }
+ }
+ bw_fmt::Do_Alignment(item._spec, *this, lw);
+ }
+ return *this;
+}
+
+// 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));
+}
+
+// -- Common formatters --
+
+BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, string_view sv);
inline BufferWriter &
-operator<<(BufferWriter &b, char c)
+bwformat(BufferWriter &w, BWFSpec const &, char c)
{
- return b.write(c);
+ return w.write(c);
}
inline BufferWriter &
-operator<<(BufferWriter &b, const string_view &sv)
+bwformat(BufferWriter &w, BWFSpec const &spec, const char *v)
{
- return b.write(sv);
+ return bwformat(w, spec, string_view(v));
}
inline BufferWriter &
-operator<<(BufferWriter &w, intmax_t i)
+bwformat(BufferWriter &w, BWFSpec const &spec, TextView const &tv)
{
- if (i) {
- char txt[std::numeric_limits<intmax_t>::digits10 + 1];
- int n = sizeof(txt);
- while (i) {
- txt[--n] = '0' + i % 10;
- i /= 10;
- }
- return w.write(txt + n, sizeof(txt) - n);
- } else {
- return w.write('0');
- }
+ return bwformat(w, spec, static_cast<string_view>(tv));
}
-// Annoying but otherwise ambiguous.
+//-- Integral types
inline BufferWriter &
-operator<<(BufferWriter &w, int i)
+bwformat(BufferWriter &w, BWFSpec const &spec, uintmax_t const &i)
{
- return w << static_cast<intmax_t>(i);
+ return bw_fmt::Format_Integer(w, spec, i, false);
}
inline BufferWriter &
-operator<<(BufferWriter &w, uintmax_t i)
+bwformat(BufferWriter &w, BWFSpec const &spec, intmax_t const &i)
{
- if (i) {
- char txt[std::numeric_limits<uintmax_t>::digits10 + 1];
- int n = sizeof(txt);
- while (i) {
- txt[--n] = '0' + i % 10;
- i /= 10;
- }
- return w.write(txt + n, sizeof(txt) - n);
- } else {
- return w.write('0');
- }
+ 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 &spec, unsigned int const &i)
+{
+ return bw_fmt::Format_Integer(w, spec, i, false);
+}
+
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, int const &i)
+{
+ return i < 0 ? bw_fmt::Format_Integer(w, spec, -i, true) : bw_fmt::Format_Integer(w, spec, i, false);
}
-// Annoying but otherwise ambiguous.
+// Annoying but otherwise ambiguous with char
inline BufferWriter &
-operator<<(BufferWriter &w, unsigned int i)
+operator<<(BufferWriter &w, int const &i)
{
- return w << static_cast<uintmax_t>(i);
+ return bwformat(w, BWFSpec::DEFAULT, static_cast<intmax_t>(i));
}
} // end namespace ts
diff --git a/lib/ts/BufferWriterFormat.cc b/lib/ts/BufferWriterFormat.cc
new file mode 100644
index 0000000..f49482a
--- /dev/null
+++ b/lib/ts/BufferWriterFormat.cc
@@ -0,0 +1,517 @@
+/** @file
+
+ Formatted output for BufferWriter.
+
+ @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.
+ */
+
+#include <ts/BufferWriter.h>
+#include <ctype.h>
+#include <ctime>
+
+namespace
+{
+// Customized version of string to int. Using this instead of the general @c svtoi function
+// made @c bwprint performance test run in < 30% of the time, changing it from about 2.5
+// times slower than snprintf to the same speed. This version handles only positive integers
+// in decimal.
+inline int
+tv_to_positive_decimal(ts::TextView src, ts::TextView *out)
+{
+ int zret = 0;
+
+ if (out) {
+ out->clear();
+ }
+ src.ltrim_if(&isspace);
+ if (src.size()) {
+ const char *start = src.data();
+ const char *limit = start + src.size();
+ while (start < limit && ('0' <= *start && *start <= '9')) {
+ zret = zret * 10 + *start - '0';
+ ++start;
+ }
+ if (out && (start > src.data())) {
+ out->set_view(src.data(), start);
+ }
+ }
+ return zret;
+}
+}
+
+namespace ts
+{
+const ts::BWFSpec ts::BWFSpec::DEFAULT;
+
+/// Parse a format specification.
+BWFSpec::BWFSpec(TextView fmt)
+{
+ TextView num;
+ intmax_t n;
+
+ _name = fmt.take_prefix_at(':');
+ // if it's parsable as a number, treat it as an index.
+ n = tv_to_positive_decimal(_name, &num);
+ if (num.size())
+ _idx = static_cast<decltype(_idx)>(n);
+
+ if (fmt.size()) {
+ TextView sz = fmt.take_prefix_at(':'); // the format specifier.
+ _ext = fmt; // anything past the second ':' is the extension.
+ if (sz.size()) {
+ // fill and alignment
+ if ('%' == *sz) { // enable URI encoding of the fill character so metasyntactic chars can be used if needed.
+ if (sz.size() < 4) {
+ throw std::invalid_argument("Fill URI encoding without 2 hex characters and align mark");
+ }
+ if (Align::NONE == (_align = align_of(sz[3]))) {
+ throw std::invalid_argument("Fill URI without alignment mark");
+ }
+ char d1 = sz[1], d0 = sz[2];
+ if (!isxdigit(d0) || !isxdigit(d1)) {
+ throw std::invalid_argument("URI encoding with non-hex characters");
+ }
+ _fill = isdigit(d0) ? d0 - '0' : tolower(d0) - 'a' + 10;
+ _fill += (isdigit(d1) ? d1 - '0' : tolower(d1) - 'a' + 10) << 4;
+ sz += 4;
+ } else if (sz.size() > 1 && Align::NONE != (_align = align_of(sz[1]))) {
+ _fill = *sz;
+ sz += 2;
+ } else if (Align::NONE != (_align = align_of(*sz))) {
+ ++sz;
+ }
+ if (!sz.size())
+ return;
+ // sign
+ if (is_sign(*sz)) {
+ _sign = *sz;
+ if (!(++sz).size())
+ return;
+ }
+ // radix prefix
+ if ('#' == *sz) {
+ _radix_lead_p = true;
+ if (!(++sz).size())
+ return;
+ }
+ // 0 fill for integers
+ if ('0' == *sz) {
+ if (Align::NONE == _align)
+ _align = Align::SIGN;
+ _fill = '0';
+ ++sz;
+ }
+ n = tv_to_positive_decimal(sz, &num);
+ if (num.size()) {
+ _min = static_cast<decltype(_min)>(n);
+ sz.remove_prefix(num.size());
+ if (!sz.size())
+ return;
+ }
+ // precision
+ if ('.' == *sz) {
+ n = tv_to_positive_decimal(++sz, &num);
+ if (num.size()) {
+ _prec = static_cast<decltype(_prec)>(n);
+ sz.remove_prefix(num.size());
+ if (!sz.size())
+ return;
+ } else {
+ throw std::invalid_argument("Precision mark without precision");
+ }
+ }
+ // style (type). Hex, octal, etc.
+ if (is_type(*sz)) {
+ _type = *sz;
+ if (!(++sz).size())
+ return;
+ }
+ // maximum width
+ if (',' == *sz) {
+ n = tv_to_positive_decimal(++sz, &num);
+ if (num.size()) {
+ _max = static_cast<decltype(_max)>(n);
+ sz.remove_prefix(num.size());
+ if (!sz.size())
+ return;
+ } else {
+ throw std::invalid_argument("Maximum width mark without width");
+ }
+ // Can only have a type indicator here if there was a max width.
+ if (is_type(*sz)) {
+ _type = *sz;
+ if (!(++sz).size())
+ return;
+ }
+ }
+ }
+ }
+}
+
+namespace bw_fmt
+{
+ GlobalTable BWF_GLOBAL_TABLE;
+
+ void
+ Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n)
+ {
+ static const BWFormat fmt{"{{BAD_ARG_INDEX:{} of {}}}"_sv};
+ w.print(fmt, i, n);
+ }
+
+ /** This performs generic alignment operations.
+
+ If a formatter specialization performs this operation instead, that should result in output that
+ is at least @a spec._min characters wide, which will cause this function to make no further
+ adjustments.
+ */
+ void
+ Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw)
+ {
+ size_t size = lw.size();
+ size_t min = spec._min;
+ if (size < min) {
+ size_t delta = min - size; // note - size <= extent -> size < min
+ switch (spec._align) {
+ case BWFSpec::Align::NONE: // same as LEFT for output.
+ case BWFSpec::Align::LEFT:
+ w.fill(size);
+ while (delta--)
+ w.write(spec._fill);
+ break;
+ case BWFSpec::Align::RIGHT:
+ std::memmove(w.auxBuffer() + delta, w.auxBuffer(), size);
+ while (delta--)
+ w.write(spec._fill);
+ w.fill(size);
+ break;
+ case BWFSpec::Align::CENTER:
+ if (delta > 1) {
+ size_t d2 = delta / 2;
+ std::memmove(w.auxBuffer() + (delta / 2), w.auxBuffer(), size);
+ while (d2--)
+ w.write(spec._fill);
+ }
+ w.fill(size);
+ delta = (delta + 1) / 2;
+ while (delta--)
+ w.write(spec._fill);
+ break;
+ case BWFSpec::Align::SIGN:
+ w.fill(size);
+ break;
+ }
+ } else {
+ w.fill(size);
+ }
+ }
+
+ // Conversions from remainder to character, in upper and lower case versions.
+ // Really only useful for hexadecimal currently.
+ namespace
+ {
+ char UPPER_DIGITS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ char LOWER_DIGITS[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+ }
+
+ /// Templated radix based conversions. Only a small number of radix are supported
+ /// and providing a template minimizes cut and paste code while also enabling
+ /// compiler optimizations (e.g. for power of 2 radix the modulo / divide become
+ /// bit operations).
+ template <size_t RADIX>
+ size_t
+ To_Radix(uintmax_t n, char *buff, size_t width, char *digits)
+ {
+ static_assert(1 < RADIX && RADIX <= 36, "RADIX must be in the range 2..36");
+ char *out = buff + width;
+ if (n) {
+ while (n) {
+ *--out = digits[n % RADIX];
+ n /= RADIX;
+ }
+ } else {
+ *--out = '0';
+ }
+ return (buff + width) - out;
+ }
+
+ BufferWriter &
+ Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t i, bool neg_p)
+ {
+ size_t n = 0;
+ int width = static_cast<int>(spec._min); // amount left to fill.
+ string_view prefix;
+ char neg = 0;
+ char prefix1 = spec._radix_lead_p ? '0' : 0;
+ char prefix2 = 0;
+ char buff[std::numeric_limits<uintmax_t>::digits + 1];
+
+ if (neg_p) {
+ neg = '-';
+ } else if (spec._sign != '-') {
+ neg = spec._sign;
+ }
+
+ switch (spec._type) {
+ case 'x':
+ prefix2 = 'x';
+ n = bw_fmt::To_Radix<16>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS);
+ break;
+ case 'X':
+ prefix2 = 'X';
+ n = bw_fmt::To_Radix<16>(i, buff, sizeof(buff), bw_fmt::UPPER_DIGITS);
+ break;
+ case 'b':
+ prefix2 = 'b';
+ n = bw_fmt::To_Radix<2>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS);
+ break;
+ case 'B':
+ prefix2 = 'B';
+ n = bw_fmt::To_Radix<2>(i, buff, sizeof(buff), bw_fmt::UPPER_DIGITS);
+ break;
+ case 'o':
+ n = bw_fmt::To_Radix<8>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS);
+ break;
+ default:
+ prefix1 = 0;
+ n = bw_fmt::To_Radix<10>(i, buff, sizeof(buff), bw_fmt::LOWER_DIGITS);
+ break;
+ }
+ // Clip fill width by stuff that's already committed to be written.
+ if (neg)
+ --width;
+ if (prefix1) {
+ --width;
+ if (prefix2)
+ --width;
+ }
+ width -= static_cast<int>(n);
+ string_view digits{buff + sizeof(buff) - n, n};
+
+ // The idea here is the various pieces have all been assembled, the only difference
+ // is the order in which they are written to the output.
+ switch (spec._align) {
+ case BWFSpec::Align::LEFT:
+ if (neg)
+ w.write(neg);
+ if (prefix1) {
+ w.write(prefix1);
+ if (prefix2)
+ w.write(prefix2);
+ }
+ w.write(digits);
+ while (width-- > 0)
+ w.write(spec._fill);
+ break;
+ case BWFSpec::Align::RIGHT:
+ while (width-- > 0)
+ w.write(spec._fill);
+ if (neg)
+ w.write(neg);
+ if (prefix1) {
+ w.write(prefix1);
+ if (prefix2)
+ w.write(prefix2);
+ }
+ w.write(digits);
+ break;
+ case BWFSpec::Align::CENTER:
+ for (int i = width / 2; i > 0; --i)
+ w.write(spec._fill);
+ if (neg)
+ w.write(neg);
+ if (prefix1) {
+ w.write(prefix1);
+ if (prefix2)
+ w.write(prefix2);
+ }
+ w.write(digits);
+ for (int i = (width + 1) / 2; i > 0; --i)
+ w.write(spec._fill);
+ break;
+ case BWFSpec::Align::SIGN:
+ if (neg)
+ w.write(neg);
+ if (prefix1) {
+ w.write(prefix1);
+ if (prefix2)
+ w.write(prefix2);
+ }
+ while (width-- > 0)
+ w.write(spec._fill);
+ w.write(digits);
+ break;
+ default:
+ if (neg)
+ w.write(neg);
+ if (prefix1) {
+ w.write(prefix1);
+ if (prefix2)
+ w.write(prefix2);
+ }
+ w.write(digits);
+ break;
+ }
+ return w;
+ }
+
+} // bw_fmt
+
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, string_view sv)
+{
+ int width = static_cast<int>(spec._min); // amount left to fill.
+ if (spec._prec > 0)
+ sv.remove_prefix(spec._prec);
+
+ width -= sv.size();
+ switch (spec._align) {
+ case BWFSpec::Align::LEFT:
+ case BWFSpec::Align::SIGN:
+ w.write(sv);
+ while (width-- > 0)
+ w.write(spec._fill);
+ break;
+ case BWFSpec::Align::RIGHT:
+ while (width-- > 0)
+ w.write(spec._fill);
+ w.write(sv);
+ break;
+ case BWFSpec::Align::CENTER:
+ for (int i = width / 2; i > 0; --i)
+ w.write(spec._fill);
+ w.write(sv);
+ for (int i = (width + 1) / 2; i > 0; --i)
+ w.write(spec._fill);
+ break;
+ default:
+ w.write(sv);
+ break;
+ }
+ return w;
+}
+
+/// Preparse format string for later use.
+BWFormat::BWFormat(ts::TextView fmt)
+{
+ BWFSpec lit_spec{BWFSpec::DEFAULT};
+ int arg_idx = 0;
+
+ while (fmt) {
+ string_view lit_str;
+ string_view spec_str;
+ bool spec_p = this->parse(fmt, lit_str, spec_str);
+
+ if (lit_str.size()) {
+ lit_spec._ext = lit_str;
+ _items.emplace_back(lit_spec, &Format_Literal);
+ }
+ if (spec_p) {
+ bw_fmt::GlobalSignature gf = nullptr;
+ BWFSpec parsed_spec{spec_str};
+ if (parsed_spec._name.size() == 0) {
+ parsed_spec._idx = arg_idx;
+ }
+ if (parsed_spec._idx < 0) {
+ gf = bw_fmt::Global_Table_Find(parsed_spec._name);
+ }
+ _items.emplace_back(parsed_spec, gf);
+ ++arg_idx;
+ }
+ }
+}
+
+BWFormat::~BWFormat()
+{
+}
+
+bool
+BWFormat::parse(ts::TextView &fmt, string_view &literal, string_view &specifier)
+{
+ TextView::size_type off;
+
+ off = fmt.find_if([](char c) { return '{' == c || '}' == c; });
+ if (off == TextView::npos) {
+ literal = fmt;
+ fmt.remove_prefix(literal.size());
+ return false;
+ }
+
+ if (fmt.size() > off + 1) {
+ char c1 = fmt[off];
+ char c2 = fmt[off + 1];
+ if (c1 == c2) {
+ literal = fmt.take_prefix_at(off + 1);
+ return false;
+ } else if ('}' == c1) {
+ throw std::invalid_argument("Unopened }");
+ } else {
+ literal = string_view{fmt.data(), off};
+ fmt.remove_prefix(off + 1);
+ }
+ } else {
+ throw std::invalid_argument("Invalid trailing character");
+ }
+
+ if (fmt.size()) {
+ // Need to be careful, because an empty format is OK and it's hard to tell if
+ // take_prefix_at failed to find the delimiter or found it as the first byte.
+ off = fmt.find('}');
+ if (off == TextView::npos) {
+ throw std::invalid_argument("Unclosed {");
+ }
+ specifier = fmt.take_prefix_at(off);
+ return true;
+ }
+ return false;
+}
+
+void
+BWFormat::Format_Literal(BufferWriter &w, BWFSpec const &spec)
+{
+ w.write(spec._ext);
+}
+
+bw_fmt::GlobalSignature
+bw_fmt::Global_Table_Find(string_view name)
+{
+ if (name.size()) {
+ auto spot = bw_fmt::BWF_GLOBAL_TABLE.find(name);
+ if (spot != bw_fmt::BWF_GLOBAL_TABLE.end())
+ return spot->second;
+ }
+ return nullptr;
+}
+
+} // ts
+
+namespace
+{
+void
+BWF_Now(ts::BufferWriter &w, ts::BWFSpec const &spec)
+{
+ std::time_t t = std::time(nullptr);
+ w.fill(std::strftime(w.auxBuffer(), w.remaining(), "%Y%b%d:%H%M%S", std::localtime(&t)));
+}
+
+static bool BW_INITIALIZED = []() -> bool {
+ ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("now", &BWF_Now);
+ return true;
+}();
+}
diff --git a/lib/ts/BufferWriterForward.h b/lib/ts/BufferWriterForward.h
new file mode 100644
index 0000000..aca64e8
--- /dev/null
+++ b/lib/ts/BufferWriterForward.h
@@ -0,0 +1,102 @@
+/** @file
+
+ Forward definitions for BufferWriter formatting.
+
+ @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 <stdlib.h>
+#include <utility>
+#include <cstring>
+#include <vector>
+#include <map>
+#include <ts/ink_std_compat.h>
+
+#include <ts/TextView.h>
+#include <ts/ink_assert.h>
+
+namespace ts
+{
+/** A parsed version of a format specifier.
+ */
+struct BWFSpec {
+ using self_type = BWFSpec; ///< Self reference type.
+ /// Constructor a default instance.
+ constexpr BWFSpec() {}
+
+ /// Construct by parsing @a fmt.
+ BWFSpec(TextView fmt);
+
+ char _fill = ' '; ///< Fill character.
+ char _sign = '-'; ///< Numeric sign style, space + -
+ enum class Align : char {
+ NONE, ///< No alignment.
+ LEFT, ///< Left alignment '<'.
+ RIGHT, ///< Right alignment '>'.
+ CENTER, ///< Center alignment '='.
+ SIGN ///< Align plus/minus sign before numeric fill. '^'
+ } _align = Align::NONE; ///< Output field alignment.
+ char _type = 'g'; ///< Type / radix indicator.
+ bool _radix_lead_p = false; ///< Print leading radix indication.
+ // @a _min is unsigned because there's no point in an invalid default, 0 works fine.
+ unsigned int _min = 0; ///< Minimum width.
+ int _prec = -1; ///< Precision
+ unsigned int _max = 0; ///< Maxium width
+ int _idx = -1; ///< Positional "name" of the specification.
+ string_view _name; ///< Name of the specification.
+ string_view _ext; ///< Extension if provided.
+
+ static const self_type DEFAULT;
+
+protected:
+ /// Validate character is alignment character and return the appropriate enum value.
+ Align align_of(char c);
+
+ /// Validate is sign indicator.
+ bool is_sign(char c);
+
+ /// Validate @a c is a specifier type indicator.
+ bool is_type(char c);
+};
+
+inline BWFSpec::Align
+BWFSpec::align_of(char c)
+{
+ return '<' == c ? Align::LEFT : '>' == c ? Align::RIGHT : '=' == c ? Align::CENTER : '^' == c ? Align::SIGN : Align::NONE;
+}
+
+inline bool
+BWFSpec::is_sign(char c)
+{
+ return '+' == c || '-' == c || ' ' == c;
+}
+
+inline bool
+BWFSpec::is_type(char c)
+{
+ return 'x' == c || 'X' == c || 'o' == c || 'b' == c || 'B' == c || 'd' == c;
+}
+
+class BWFormat;
+
+class BufferWriter;
+
+} // ts
diff --git a/lib/ts/Makefile.am b/lib/ts/Makefile.am
index 1c70b8b..57e9036 100644
--- a/lib/ts/Makefile.am
+++ b/lib/ts/Makefile.am
@@ -56,6 +56,9 @@ libtsutil_la_SOURCES = \
BaseLogFile.h \
Bitops.cc \
Bitops.h \
+ BufferWriter.h \
+ BufferWriterForward.h \
+ BufferWriterFormat.cc \
ConsistentHash.cc \
ConsistentHash.h \
ContFlags.cc \
@@ -195,7 +198,6 @@ libtsutil_la_SOURCES = \
SourceLocation.cc \
SourceLocation.h \
string_view.h \
- BufferWriter.h \
TestBox.h \
TextBuffer.cc \
TextBuffer.h \
@@ -261,6 +263,7 @@ test_tslib_LDADD = libtsutil.la $(top_builddir)/iocore/eventsystem/libinkevent.a
test_tslib_SOURCES = \
unit-tests/unit_test_main.cc \
unit-tests/test_BufferWriter.cc \
+ unit-tests/test_BufferWriterFormat.cc \
unit-tests/test_ink_inet.cc \
unit-tests/test_IpMap.cc \
unit-tests/test_layout.cc \
diff --git a/lib/ts/ink_std_compat.h b/lib/ts/ink_std_compat.h
index 0b76f6d..b7d37a0 100644
--- a/lib/ts/ink_std_compat.h
+++ b/lib/ts/ink_std_compat.h
@@ -30,6 +30,8 @@
// C++ 14 compatibility
//
#include <memory>
+#include <type_traits>
+
namespace std
{
template <typename T, typename... Args>
@@ -38,6 +40,99 @@ make_unique(Args &&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
+// Local implementation of integer sequence templates from <utility> in C++14.
+// Drop once we move to C++14.
+
+template <typename T, T... N> struct integer_sequence {
+ typedef T value_type;
+ static_assert(std::is_integral<T>::value, "std::integer_sequence requires an integral type");
+
+ static inline std::size_t
+ size()
+ {
+ return (sizeof...(N));
+ }
+};
+
+template <std::size_t... N> using index_sequence = integer_sequence<std::size_t, N...>;
+
+namespace sequence_expander_detail
+{
+ // Expand a sequence (4 ways)
+ template <typename T, std::size_t... _Extra> struct seq_expand;
+
+ template <typename T, T... N, std::size_t... _Extra> struct seq_expand<integer_sequence<T, N...>, _Extra...> {
+ typedef integer_sequence<T, N..., 1 * sizeof...(N) + N..., 2 * sizeof...(N) + N..., 3 * sizeof...(N) + N..., _Extra...> type;
+ };
+
+ template <std::size_t N> struct modulus;
+ template <std::size_t N> struct construct : modulus<N % 4>::template modular_construct<N> {
+ };
+
+ // 4 base cases (e.g. modulo 4)
+ template <> struct construct<0> {
+ typedef integer_sequence<std::size_t> type;
+ };
+ template <> struct construct<1> {
+ typedef integer_sequence<std::size_t, 0> type;
+ };
+ template <> struct construct<2> {
+ typedef integer_sequence<std::size_t, 0, 1> type;
+ };
+ template <> struct construct<3> {
+ typedef integer_sequence<std::size_t, 0, 1, 2> type;
+ };
+
+ // Modulus cases - split 4 ways and pick up the remainder explicitly.
+ template <> struct modulus<0> {
+ template <std::size_t N> struct modular_construct : seq_expand<typename construct<N / 4>::type> {
+ };
+ };
+ template <> struct modulus<1> {
+ template <std::size_t N> struct modular_construct : seq_expand<typename construct<N / 4>::type, N - 1> {
+ };
+ };
+ template <> struct modulus<2> {
+ template <std::size_t N> struct modular_construct : seq_expand<typename construct<N / 4>::type, N - 2, N - 1> {
+ };
+ };
+ template <> struct modulus<3> {
+ template <std::size_t N> struct modular_construct : seq_expand<typename construct<N / 4>::type, N - 3, N - 2, N - 1> {
+ };
+ };
+
+ template <typename T, typename U> struct convert {
+ template <typename> struct result;
+
+ template <T... N> struct result<integer_sequence<T, N...>> {
+ typedef integer_sequence<U, N...> type;
+ };
+ };
+
+ template <typename T> struct convert<T, T> {
+ template <typename U> struct result {
+ typedef U type;
+ };
+ };
+
+ template <typename T, T N>
+ using make_integer_sequence_unchecked = typename convert<std::size_t, T>::template result<typename construct<N>::type>::type;
+
+ template <typename T, T N> struct make_integer_sequence {
+ static_assert(std::is_integral<T>::value, "std::make_integer_sequence can only be instantiated with an integral type");
+ static_assert(0 <= N, "std::make_integer_sequence input shall not be negative");
+
+ typedef make_integer_sequence_unchecked<T, N> type;
+ };
+
+} // namespace sequence_expander_detail
+
+template <typename T, T N> using make_integer_sequence = typename sequence_expander_detail::make_integer_sequence<T, N>::type;
+
+template <std::size_t N> using make_index_sequence = make_integer_sequence<std::size_t, N>;
+
+template <typename... T> using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
} // namespace std
#endif // C++ 14 compatibility
diff --git a/lib/ts/unit-tests/test_BufferWriter.cc b/lib/ts/unit-tests/test_BufferWriter.cc
index 3d6b1c7..cfccfd2 100644
--- a/lib/ts/unit-tests/test_BufferWriter.cc
+++ b/lib/ts/unit-tests/test_BufferWriter.cc
@@ -21,12 +21,9 @@
limitations under the License.
*/
-#include "BufferWriter.h"
-
#include "catch.hpp"
-
-#include "string_view.h"
-
+#include <ts/BufferWriter.h>
+#include <ts/string_view.h>
#include <cstring>
namespace
@@ -333,19 +330,6 @@ TEST_CASE("Discard Buffer Writer", "[BWD]")
REQUIRE(scratch[0] == '!');
}
-TEST_CASE("Buffer Writer << operator", "[BW<<]")
-{
- ts::LocalBufferWriter<50> bw;
-
- bw << "The" << ' ' << "quick" << ' ' << "brown fox";
-
- REQUIRE(bw.view() == "The quick brown fox");
-
- bw.reduce(0);
- bw << "x=" << bw.capacity();
- REQUIRE(bw.view() == "x=50");
-}
-
TEST_CASE("LocalBufferWriter clip and extend")
{
ts::LocalBufferWriter<10> bw;
diff --git a/lib/ts/unit-tests/test_BufferWriterFormat.cc b/lib/ts/unit-tests/test_BufferWriterFormat.cc
new file mode 100644
index 0000000..2ea8cba
--- /dev/null
+++ b/lib/ts/unit-tests/test_BufferWriterFormat.cc
@@ -0,0 +1,200 @@
+/** @file
+
+ Unit tests for BufferFormat and bwprint.
+
+ @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.
+ */
+
+#include "catch.hpp"
+#include <ts/BufferWriter.h>
+#include <chrono>
+#include <iostream>
+
+TEST_CASE("Buffer Writer << operator", "[bufferwriter][stream]")
+{
+ ts::LocalBufferWriter<50> bw;
+
+ bw << "The" << ' ' << "quick" << ' ' << "brown fox";
+
+ REQUIRE(bw.view() == "The quick brown fox");
+
+ bw.reduce(0);
+ bw << "x=" << bw.capacity();
+ REQUIRE(bw.view() == "x=50");
+}
+
+TEST_CASE("bwprint basics", "[bwprint]")
+{
+ ts::LocalBufferWriter<256> bw;
+ ts::string_view fmt1{"Some text"_sv};
+
+ bw.print(fmt1);
+ REQUIRE(bw.view() == fmt1);
+ bw.reduce(0);
+ bw.print("Arg {}", 1);
+ REQUIRE(bw.view() == "Arg 1");
+ bw.reduce(0);
+ bw.print("arg 1 {1} and 2 {2} and 0 {0}", "zero", "one", "two");
+ REQUIRE(bw.view() == "arg 1 one and 2 two and 0 zero");
+ bw.reduce(0);
+ bw.print("args {2}{0}{1}", "zero", "one", "two");
+ REQUIRE(bw.view() == "args twozeroone");
+ bw.reduce(0);
+ bw.print("left |{:<10}|", "text");
+ REQUIRE(bw.view() == "left |text |");
+ bw.reduce(0);
+ bw.print("right |{:>10}|", "text");
+ REQUIRE(bw.view() == "right | text|");
+ bw.reduce(0);
+ bw.print("right |{:.>10}|", "text");
+ REQUIRE(bw.view() == "right |......text|");
+ bw.reduce(0);
+ bw.print("center |{:.=10}|", "text");
+ REQUIRE(bw.view() == "center |...text...|");
+ bw.reduce(0);
+ bw.print("center |{:.=11}|", "text");
+ REQUIRE(bw.view() == "center |...text....|");
+ bw.reduce(0);
+ bw.print("center |{:==10}|", "text");
+ REQUIRE(bw.view() == "center |===text===|");
+ bw.reduce(0);
+ bw.print("center |{:%3A=10}|", "text");
+ REQUIRE(bw.view() == "center |:::text:::|");
+ bw.reduce(0);
+ bw.print("left >{0:<9}< right >{0:>9}< center >{0:=9}<", 956);
+ REQUIRE(bw.view() == "left >956 < right > 956< center > 956 <");
+
+ bw.reduce(0);
+ bw.print("Format |{:>#010x}|", -956);
+ REQUIRE(bw.view() == "Format |0000-0x3bc|");
+ bw.reduce(0);
+ bw.print("Format |{:<#010x}|", -956);
+ REQUIRE(bw.view() == "Format |-0x3bc0000|");
+ bw.reduce(0);
+ bw.print("Format |{:#010x}|", -956);
+ REQUIRE(bw.view() == "Format |-0x00003bc|");
+
+ bw.reduce(0);
+ bw.print("{{BAD_ARG_INDEX:{} of {}}}", 17, 23);
+ REQUIRE(bw.view() == "{BAD_ARG_INDEX:17 of 23}");
+
+ bw.reduce(0);
+ bw.print("Arg {0} Arg {3}", 1, 2);
+ REQUIRE(bw.view() == "Arg 1 Arg {BAD_ARG_INDEX:3 of 2}");
+
+ bw.reduce(0);
+ bw.print("{{stuff}} Arg {0} Arg {}", 1, 2);
+ REQUIRE(bw.view() == "{stuff} Arg 1 Arg 2");
+ bw.reduce(0);
+ bw.print("Arg {0} Arg {} and {{stuff}}", 3, 4);
+ REQUIRE(bw.view() == "Arg 3 Arg 4 and {stuff}");
+ bw.reduce(0);
+ bw.print("Arg {{{0}}} Arg {} and {{stuff}}", 5, 6);
+ REQUIRE(bw.view() == "Arg {5} Arg 6 and {stuff}");
+ bw.reduce(0);
+ bw.print("Arg {0} Arg {{}}{{}} {} and {{stuff}}", 7, 8);
+ REQUIRE(bw.view() == "Arg 7 Arg {}{} 8 and {stuff}");
+ bw.reduce(0);
+ bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10);
+ REQUIRE(bw.view() == "Arg 9 Arg {{}} 10");
+
+ bw.reduce(0);
+ bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10);
+ REQUIRE(bw.view() == "Arg 9 Arg {{}} 10");
+ bw.reduce(0);
+ bw.print("Time is {now}");
+ // REQUIRE(bw.view() == "Time is");
+}
+
+TEST_CASE("BWFormat", "[bwprint][bwformat]")
+{
+ ts::LocalBufferWriter<256> bw;
+ ts::BWFormat fmt("left >{0:<9}< right >{0:>9}< center >{0:=9}<");
+ ts::string_view text{"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
+
+ bw.reduce(0);
+ static const ts::BWFormat bad_arg_fmt{"{{BAD_ARG_INDEX:{} of {}}}"};
+ bw.print(bad_arg_fmt, 17, 23);
+ REQUIRE(bw.view() == "{BAD_ARG_INDEX:17 of 23}");
+
+ bw.reduce(0);
+ bw.print(fmt, 956);
+ REQUIRE(bw.view() == "left >956 < right > 956< center > 956 <");
+
+ bw.reduce(0);
+ bw.print("Text: _{0:.10,20}_", text);
+ REQUIRE(bw.view() == "Text: _abcdefghijklmnopqrst_");
+ bw.reduce(0);
+ bw.print("Text: _{0:-<20.52,20}_", text);
+ REQUIRE(bw.view() == "Text: _QRSTUVWXYZ----------_");
+}
+
+// Normally there's no point in running the performance tests, but it's worth keeping the code
+// for when additional testing needs to be done.
+#if 0
+TEST_CASE("bwperf", "[bwprint][performance]")
+{
+ // Force these so I can easily change the set of tests.
+ auto start = std::chrono::high_resolution_clock::now();
+ auto delta = std::chrono::high_resolution_clock::now() - start;
+ constexpr int N_LOOPS = 1000000;
+
+ ts::string_view text{"Format |"};
+ ts::LocalBufferWriter<256> bw;
+
+ ts::BWFSpec spec;
+
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N_LOOPS; ++i) {
+ bw.reduce(0);
+ bw.print( "Format |{:#010x}|", -956);
+ }
+ delta = std::chrono::high_resolution_clock::now() - start;
+ std::cout << "BW Timing is " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count()
+ << "ms" << std::endl;
+
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N_LOOPS; ++i) {
+ bw.reduce(0);
+ bw.print("Format |{:#010x}|", -956);
+ }
+ delta = std::chrono::high_resolution_clock::now() - start;
+ std::cout << "bw.print() " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count()
+ << "ms" << std::endl;
+
+ ts::BWFormat fmt("Format |{:#010x}|");
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N_LOOPS; ++i) {
+ bw.reduce(0);
+ bw.print( fmt, -956);
+ }
+ delta = std::chrono::high_resolution_clock::now() - start;
+ std::cout << "Preformatted: " << delta.count() << "ns or "
+ << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() << "ms" << std::endl;
+
+ char buff[256];
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < N_LOOPS; ++i) {
+ snprintf(buff, sizeof(buff), "Format |%#0x10|", -956);
+ }
+ delta = std::chrono::high_resolution_clock::now() - start;
+ std::cout << "snprint Timing is " << delta.count() << "ns or "
+ << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() << "ms" << std::endl;
+}
+#endif
--
To stop receiving notification emails like this one, please contact
amc@apache.org.