diff --git a/doc/developer-guide/internal-libraries/intrusive-list.en.rst b/doc/developer-guide/internal-libraries/intrusive-list.en.rst index d5f79f31b7a..3a33d55b7dc 100644 --- a/doc/developer-guide/internal-libraries/intrusive-list.en.rst +++ b/doc/developer-guide/internal-libraries/intrusive-list.en.rst @@ -134,7 +134,7 @@ Examples In this example the goal is to have a list of :code:`Message` objects. First the class is declared along with the internal linkage support. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 38-63 The struct :code:`Linkage` is used both to provide the descriptor to :class:`IntrusiveDList` and to @@ -144,35 +144,35 @@ used only by a specific container class (:code:`Container`) the struct is made p The implementation of the link accessor methods. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 65-74 A method to check if the message is in a list. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 76-80 The container class for the messages could be implemented as -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 82-99 The :code:`debug` method takes a format string (:arg:`fmt`) and an arbitrary set of arguments, formats the arguments in to the string, and adds the new message to the list. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 122-131 The :code:`print` method demonstrates the use of the range :code:`for` loop on a list. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 142-148 The maximum severity level can also be computed even more easily using :code:`std::max_element`. This find the element with the maximum severity and returns that severity, or :code:`LVL_DEBUG` if no element is found (which happens if the list is empty). -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 134-140 Other methods for the various severity levels would be implemented in a similar fashion. Because the @@ -183,20 +183,20 @@ risky. One approach, illustrated here, is to use :func:`IntrusiveDList::take_hea element before destroying it. Another option is to allocation the elements in a :class:`MemArena` to avoid the need for any explicit cleanup. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 106-114 In some cases the elements of the list are subclasses and the links are declared in a super class and are therefore of the super class type. For instance, in the unit test a class :code:`Thing` is defined for testing. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 159 Later on, to validate use on a subclass, :code:`PrivateThing` is defined as a subclass of :code:`Thing`. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 181 However, the link members :code:`_next` and :code:`_prev` are of type :code:`Thing*` but the @@ -205,7 +205,7 @@ descriptor for a list of :code:`PrivateThing` must have link accessors that retu :code:`ts::ptr_ref_cast` that converts a member of type :code:`T*` to a reference to a pointer to :code:`X`, e.g. :code:`X*&`. This is used in the setup for testing :code:`PrivateThing`. -.. literalinclude:: ../../../src/tscore/unit_tests/test_IntrusiveDList.cc +.. literalinclude:: ../../../src/tscpp/util/unit_tests/test_IntrusiveDList.cc :lines: 190-199 While this can be done directly with :code:`reinterpret_cast<>`, use of :code:`ts::ptr_cast` avoids diff --git a/include/tscore/BufferWriter.h b/include/tscore/BufferWriter.h deleted file mode 100644 index 052a4de29cf..00000000000 --- a/include/tscore/BufferWriter.h +++ /dev/null @@ -1,897 +0,0 @@ -/** @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 -#include -#include -#include -#include -#include -#include - -#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(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 auxillary 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 conjuction with @a auxBuffer to indicate that @a n bytes of the auxillary 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 condidtion. - 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 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 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 BufferWriter &printv(TextView fmt, std::tuple const &args); - - /// Print using a preparsed @a fmt. - template 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 BufferWriter &printv(BWFormat const &fmt, std::tuple 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. - */ -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; - /// Move constructor. - FixedBufferWriter(FixedBufferWriter &&) = default; - /// Move assignment. - FixedBufferWriter &operator=(FixedBufferWriter &&) = default; - - FixedBufferWriter(MemSpan &span) : _buf(span.begin()), _capacity(static_cast(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 auxillary writer will be @a reserve bytes - smaller than the remaining buffer. This "reserves" space for additional output after writing - to the auxillary 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 self_type &print(TextView fmt, Rest &&... rest); - template self_type &printv(TextView fmt, std::tuple const &args); - template self_type &print(BWFormat const &fmt, Args &&... args); - template self_type &printv(BWFormat const &fmt, std::tuple 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 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 LocalBufferWriter(const LocalBufferWriter &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 - LocalBufferWriter & - operator=(const LocalBufferWriter &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 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 - BufferWriter & - Arg_Formatter(BufferWriter &w, BWFSpec const &spec, TUPLE const &args) - { - return bwformat(w, spec, std::get(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 - /// accessed 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 - ArgFormatterSignature * - Get_Arg_Formatter_Array(std::index_sequence) - { - static ArgFormatterSignature fa[sizeof...(N)] = {&bw_fmt::Arg_Formatter...}; - 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; - 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; - 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 -BufferWriter & -BufferWriter::print(TextView fmt, Args &&... args) -{ - return this->printv(fmt, std::forward_as_tuple(args...)); -} - -template -BufferWriter & -BufferWriter::printv(TextView fmt, std::tuple 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(std::index_sequence_for{}); - 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 (optinal) 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 -BufferWriter & -BufferWriter::print(BWFormat const &fmt, Args &&... args) -{ - return this->printv(fmt, std::forward_as_tuple(args...)); -} - -template -BufferWriter & -BufferWriter::printv(BWFormat const &fmt, std::tuple const &args) -{ - using namespace std::literals; - static constexpr int N = sizeof...(Args); - static const auto fa = bw_fmt::Get_Arg_Formatter_Array(std::index_sequence_for{}); - - 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; -} - -// 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_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(ptr), false); -} - -// MemSpan -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span); - -// -- Common formatters -- - -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv); - -template -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') { - bwformat(w, spec, static_cast(v)); - } else { - bwformat(w, spec, std::string_view(v)); - } - return w; -} - -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, TextView tv) -{ - return bwformat(w, spec, static_cast(tv)); -} - -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, std::string const &s) -{ - return bwformat(w, spec, std::string_view{s}); -} - -template -auto -bwformat(BufferWriter &w, BWFSpec const &spec, F &&f) -> - typename std::enable_if::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 -auto -bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) -> - typename std::enable_if::type>::value && - std::is_integral::type>::value, - BufferWriter &>::type -{ - return bw_fmt::Format_Integer(w, spec, i, false); -} - -template -auto -bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) -> - typename std::enable_if::type>::value && - std::is_integral::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(f), false); - } - return w; -} - -// Generically a stream operator is a formatter with the default specification. -template -BufferWriter & -operator<<(BufferWriter &w, V &&v) -{ - return bwformat(w, BWFSpec::DEFAULT, std::forward(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 -std::string & -bwprintv(std::string &s, ts::TextView fmt, std::tuple const &args) -{ - auto len = s.size(); // remember initial size - size_t n = ts::FixedBufferWriter(const_cast(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(s.data()), s.size()).printv(fmt, std::move(args)); - } - return s; -} - -template -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 -inline auto -FixedBufferWriter::print(TextView fmt, Args &&... args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); -} - -template -inline auto -FixedBufferWriter::printv(TextView fmt, std::tuple const &args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, args)); -} - -template -inline auto -FixedBufferWriter::print(BWFormat const &fmt, Args &&... args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); -} - -template -inline auto -FixedBufferWriter::printv(BWFormat const &fmt, std::tuple const &args) -> self_type & -{ - return static_cast(this->super_type::printv(fmt, args)); -} - -} // end namespace ts - -namespace std -{ -inline ostream & -operator<<(ostream &s, ts::BufferWriter const &w) -{ - return w >> s; -} -} // end namespace std diff --git a/include/tscore/BufferWriterForward.h b/include/tscore/BufferWriterForward.h deleted file mode 100644 index 9bc874c57df..00000000000 --- a/include/tscore/BufferWriterForward.h +++ /dev/null @@ -1,150 +0,0 @@ -/** @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 -#include -#include -#include -#include - -#include "tscpp/util/TextView.h" -#include "tscore/ink_assert.h" - -namespace ts -{ -/** A parsed version of a format specifier. - */ -struct BWFSpec { - using self_type = BWFSpec; ///< Self reference type. - static constexpr char DEFAULT_TYPE = 'g'; ///< Default format 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 = DEFAULT_TYPE; ///< 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 = std::numeric_limits::max(); ///< Maxium width - int _idx = -1; ///< Positional "name" of the specification. - std::string_view _name; ///< Name of the specification. - std::string_view _ext; ///< Extension if provided. - - static const self_type DEFAULT; - - /// Validate @a c is a specifier type indicator. - static bool is_type(char c); - /// Check if the type flag is numeric. - static bool is_numeric_type(char c); - /// Check if the type is an upper case variant. - static bool is_upper_case_type(char c); - /// Check if the type @a in @a this is numeric. - bool has_numeric_type() const; - /// Check if the type in @a this is an upper case variant. - bool has_upper_case_type() const; - /// Check if the type is a raw pointer. - bool has_pointer_type() const; - -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); - - /// Handrolled initialization the character syntactic property data. - static const struct Property { - Property(); ///< Default constructor, creates initialized flag set. - /// Flag storage, indexed by character value. - uint8_t _data[0x100]; - /// Flag mask values. - static constexpr uint8_t ALIGN_MASK = 0x0F; ///< Alignment type. - static constexpr uint8_t TYPE_CHAR = 0x10; ///< A valid type character. - static constexpr uint8_t UPPER_TYPE_CHAR = 0x20; ///< Upper case flag. - static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output. - static constexpr uint8_t SIGN_CHAR = 0x80; ///< Is sign character. - } _prop; -}; - -inline BWFSpec::Align -BWFSpec::align_of(char c) -{ - return static_cast(_prop._data[static_cast(c)] & Property::ALIGN_MASK); -} - -inline bool -BWFSpec::is_sign(char c) -{ - return _prop._data[static_cast(c)] & Property::SIGN_CHAR; -} - -inline bool -BWFSpec::is_type(char c) -{ - return _prop._data[static_cast(c)] & Property::TYPE_CHAR; -} - -inline bool -BWFSpec::is_upper_case_type(char c) -{ - return _prop._data[static_cast(c)] & Property::UPPER_TYPE_CHAR; -} - -inline bool -BWFSpec::has_numeric_type() const -{ - return _prop._data[static_cast(_type)] & Property::NUMERIC_TYPE_CHAR; -} - -inline bool -BWFSpec::has_upper_case_type() const -{ - return _prop._data[static_cast(_type)] & Property::UPPER_TYPE_CHAR; -} - -inline bool -BWFSpec::has_pointer_type() const -{ - return _type == 'p' || _type == 'P'; -} - -class BWFormat; - -class BufferWriter; - -} // namespace ts diff --git a/include/tscore/CryptoHash.h b/include/tscore/CryptoHash.h index b3334c03984..25f359c26f5 100644 --- a/include/tscore/CryptoHash.h +++ b/include/tscore/CryptoHash.h @@ -22,7 +22,7 @@ #pragma once -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include /// Apache Traffic Server commons. @@ -180,19 +180,9 @@ CryptoContext::finalize(CryptoHash &hash) return reinterpret_cast(_obj)->finalize(hash); } -} // namespace ats +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, ats::CryptoHash const &hash); -namespace ts -{ -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, ats::CryptoHash const &hash) -{ - BWFSpec local_spec{spec}; - if ('X' != local_spec._type) - local_spec._type = 'x'; - return bwformat(w, local_spec, std::string_view(reinterpret_cast(hash.u8), CRYPTO_HASH_SIZE)); -} -} // namespace ts +} // namespace ats using ats::CryptoHash; using ats::CryptoContext; diff --git a/include/tscore/IpMap.h b/include/tscore/IpMap.h index fc95ebb1ccb..34ec5dc3175 100644 --- a/include/tscore/IpMap.h +++ b/include/tscore/IpMap.h @@ -27,7 +27,7 @@ #include "tscore/ink_defs.h" #include "tscore/RbTree.h" #include "tscore/ink_inet.h" -#include "tscore/IntrusiveDList.h" +#include "tscpp/util/IntrusiveDList.h" #include "tscore/ink_assert.h" namespace ts diff --git a/include/tscore/SourceLocation.h b/include/tscore/SourceLocation.h index e3af488b383..c719064db48 100644 --- a/include/tscore/SourceLocation.h +++ b/include/tscore/SourceLocation.h @@ -23,7 +23,7 @@ #pragma once -#include "tscore/BufferWriterForward.h" +#include "tscpp/util/BufferWriter.h" // The SourceLocation class wraps up a source code location, including // file name, function name, and line number, and contains a method to @@ -59,13 +59,13 @@ class SourceLocation } char *str(char *buf, int buflen) const; - ts::BufferWriter &print(ts::BufferWriter &w, ts::BWFSpec const &spec) const; + ts::BufferWriter &print(ts::BufferWriter &w, ts::bwf::Spec const &spec) const; }; namespace ts { inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, SourceLocation const &loc) +bwformat(BufferWriter &w, bwf::Spec const &spec, SourceLocation const &loc) { return loc.print(w, spec); } diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h index 4cbf14c1d41..5076ff3e927 100644 --- a/include/tscore/ink_inet.h +++ b/include/tscore/ink_inet.h @@ -31,7 +31,7 @@ #include "tscore/ink_memory.h" #include "tscore/ink_apidefs.h" -#include "tscore/BufferWriterForward.h" +#include "tscpp/util/BufferWriter.h" #if !TS_HAS_IN6_IS_ADDR_UNSPECIFIED #if defined(IN6_IS_ADDR_UNSPECIFIED) @@ -1551,10 +1551,10 @@ IpEndpoint::setToLoopback(int family) // BufferWriter formatting support. namespace ts { -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, IpAddr const &addr); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr); inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, IpEndpoint const &addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, IpEndpoint const &addr) { return bwformat(w, spec, &addr.sa); } diff --git a/include/tscore/ts_file.h b/include/tscore/ts_file.h index b5ec194a1f4..fc1944d2ad1 100644 --- a/include/tscore/ts_file.h +++ b/include/tscore/ts_file.h @@ -30,7 +30,7 @@ #include #include "tscore/ink_memory.h" #include "tscpp/util/TextView.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" namespace ts { diff --git a/include/tscpp/util/BufferWriter.h b/include/tscpp/util/BufferWriter.h new file mode 100644 index 00000000000..b23949aa607 --- /dev/null +++ b/include/tscpp/util/BufferWriter.h @@ -0,0 +1,556 @@ +/** @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 +#include +#include +#include +#include +#include +#include + +#include "tscpp/util/TextView.h" +#include "tscpp/util/MemSpan.h" + +namespace ts +{ +namespace bwf +{ + struct Spec; + class Format; + class BoundNames; +} // namespace bwf + +/** 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); + + /** 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); + + /// 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 auxillary buffer and write to that. + + @code + ts::FixedBufferWriter subw(w.aux_data(), 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 *aux_data(); + + /// 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; + + /// Get the remaining buffer space. + /// @return Number of additional characters that can be written without causing an error condidtion. + size_t remaining() const; + + /** Increase the extent by @a n bytes. + + @param n The number of bytes to add to the extent. + + The buffer content is unchanged, only the extent value is adjusted. + + This is useful + when doing local buffer filling using @c aux_data. After writing data there, it can be + added to the extent using this method. + + @internal Concrete subclasses @b must override this to advance in a way consistent with the + specific buffer type. + + @return @c *this + */ + virtual BufferWriter &commit(size_t n) = 0; + + /** Decrease the extent by @a n. + * + * @param n Number of bytes to remove from the extent. + * + * The buffer content is unchanged, only the extent value is adjusted. + */ + 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 It is required that any restored capacity have been previously removed by @c shrink. + /// @see shrink + 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 + */ + virtual BufferWriter ©(size_t dst, size_t src, 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 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}". + + @note This must be declared here, but the implementation is in @c bwf_base.h + */ + template BufferWriter &print(const 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 BufferWriter &printv(const TextView &fmt, const std::tuple &args); + + /// Print using a preparsed @a fmt. + template BufferWriter &print(const bwf::Format &fmt, Args &&... args); + template BufferWriter &printv(const bwf::Format &fmt, const std::tuple &args); + + /** Print the arguments on to the buffer. + * + * This is the base implementation, all of the other variants are wrappers for this. + * + * @tparam F Format processor - returns chunks of the format. + * @tparam Args Arguments for the format. + * @param names Name set for specifier names. + */ + template + BufferWriter &print_nv(bwf::BoundNames const &names, F &&f, std::tuple const &args); + /// Convenience for no format argument style invocation. + template BufferWriter &print_nv(const bwf::BoundNames &names, F &&f); + + /// Output the buffer contents to the @a stream. + /// @return The destination stream. + virtual std::ostream &operator>>(std::ostream &stream) const = 0; +}; + +/** A @c BufferWrite concrete subclass to write to a fixed size 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. + + @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 from span + * + */ + FixedBufferWriter(const MemSpan &span); + + /** 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; + + /// Move constructor. + FixedBufferWriter(FixedBufferWriter &&) = default; + + /// Move assignment. + FixedBufferWriter &operator=(FixedBufferWriter &&) = default; + + FixedBufferWriter(MemSpan &span) : _buf(span.begin()), _capacity(static_cast(span.size())) {} + + /// Write a single character @a c to the buffer. + FixedBufferWriter &write(char c) override; + + /// Write @a data to the buffer, up to @a length bytes. + FixedBufferWriter &write(const void *data, size_t length) override; + + // Bring in non-overridden methods. + using super_type::write; + + /// Return the output buffer. + const char *data() const override; + + /// Return whether there has been an error. + bool error() const override; + + /// Get the start of the unused output buffer. + char *aux_data() override; + + /// Get the span of the unused output buffer + MemSpan aux_span(); + + /// 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. + self_type &commit(size_t n) override; + + /// Drop @a n characters from the end of the buffer. + /// The extent is reduced but the data is not overwritten and can be recovered with + /// @c fill. + self_type &discard(size_t n) override; + + /// Reduce the capacity by @a n. + self_type &restrict(size_t n) override; + + /// Extend the capacity by @a n. + self_type &restore(size_t n) override; + + /// Copy data in the buffer. + FixedBufferWriter ©(size_t dst, size_t src, size_t n) override; + + /// 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 &clear(); + + /// Provide a string_view of all successfully written characters. + std::string_view view() const; + + /// Provide a @c string_view of all successfully written characters as a user conversion. + operator std::string_view() const; + + /// Output the buffer contents to the @a stream. + std::ostream &operator>>(std::ostream &stream) const override; + + // Overrides for co-variance + template self_type &print(TextView fmt, Rest &&... rest); + + template self_type &printv(TextView fmt, std::tuple const &args); + + template self_type &print(bwf::Format const &fmt, Args &&... args); + + template self_type &printv(bwf::Format const &fmt, std::tuple 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. + size_t _restriction = 0; ///< Restricted capacity. +}; + +/** 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 class LocalBufferWriter : public FixedBufferWriter +{ + using self_type = LocalBufferWriter; + using super_type = FixedBufferWriter; + +public: + /// Construct an empty writer. + LocalBufferWriter(); + LocalBufferWriter(const LocalBufferWriter &that) = delete; + LocalBufferWriter &operator=(const LocalBufferWriter &that) = delete; + +protected: + char _arr[N]; ///< output buffer. +}; + +// --------------- Implementation -------------------- + +inline BufferWriter::~BufferWriter() {} + +inline BufferWriter & +BufferWriter::write(const void *data, size_t length) +{ + const char *d = static_cast(data); + + while (length--) { + this->write(*(d++)); + } + return *this; +} + +inline BufferWriter & +BufferWriter::write(const std::string_view &sv) +{ + return this->write(sv.data(), sv.size()); +} + +inline char * +BufferWriter::aux_data() +{ + return nullptr; +} + +inline size_t +BufferWriter::size() const +{ + return std::min(this->extent(), this->capacity()); +} + +inline size_t +BufferWriter::remaining() const +{ + return this->capacity() - this->size(); +} + +// --- FixedBufferWriter --- +inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buf(buffer), _capacity(capacity) +{ + if (_capacity != 0 && buffer == nullptr) { + throw(std::invalid_argument{"FixedBufferWriter created with null buffer and non-zero size."}); + }; +} + +inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buf(nullptr), _capacity(0) {} + +inline FixedBufferWriter & +FixedBufferWriter::write(char c) +{ + if (_attempted < _capacity) { + _buf[_attempted] = c; + } + ++_attempted; + + return *this; +} + +inline FixedBufferWriter & +FixedBufferWriter::write(const void *data, size_t length) +{ + 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; +} + +/// Return the output buffer. +inline const char * +FixedBufferWriter::data() const +{ + return _buf; +} + +inline bool +FixedBufferWriter::error() const +{ + return _attempted > _capacity; +} + +inline char * +FixedBufferWriter::aux_data() +{ + return error() ? nullptr : _buf + _attempted; +} + +inline MemSpan +FixedBufferWriter::aux_span() +{ + return error() ? MemSpan{} : MemSpan{_buf + _attempted, static_cast(this->remaining())}; +} + +inline auto +FixedBufferWriter::commit(size_t n) -> self_type & +{ + _attempted += n; + + return *this; +} + +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; + _restriction += n; + + return *this; +} + +inline auto +FixedBufferWriter::restore(size_t n) -> self_type & +{ + if (error()) { + _attempted = _capacity; + } + n = std::min(n, _restriction); + + _capacity += n; + _restriction -= 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(_capacity, _attempted); // max offset of region possible. + MemSpan src_span{_buf + src, std::min(limit, src + n)}; + MemSpan dst_span{_buf + dst, std::min(limit, dst + n)}; + std::memmove(dst_span.data(), src_span.data(), std::min(dst_span.size(), src_span.size())); + return *this; +} + +inline std::string_view +FixedBufferWriter::view() const +{ + return std::string_view(_buf, size()); +} + +/// Provide a @c string_view of all successfully written characters as a user conversion. +inline FixedBufferWriter::operator std::string_view() const +{ + return this->view(); +} + +// --- LocalBufferWriter --- +template LocalBufferWriter::LocalBufferWriter() : super_type(_arr, N) {} + +} // namespace ts + +namespace std +{ +inline ostream & +operator<<(ostream &s, ts::BufferWriter const &w) +{ + return w >> s; +} +} // end namespace std diff --git a/include/tscore/IntrusiveDList.h b/include/tscpp/util/IntrusiveDList.h similarity index 60% rename from include/tscore/IntrusiveDList.h rename to include/tscpp/util/IntrusiveDList.h index 2db6415bcb0..eae1f0e9186 100644 --- a/include/tscore/IntrusiveDList.h +++ b/include/tscpp/util/IntrusiveDList.h @@ -2,73 +2,66 @@ Intrusive double linked list container. - This provide support for a doubly linked list container for an - arbitrary class that uses the class directly and not wrapped. It - requires the class to provide the list pointers. + This provides support for a doubly linked list container. Items in the list must provide links + inside the class and accessor functions for those links. @note This is a header only library. - @note Due to bugs in either the C++ standard or gcc (or both), the - link members @b must be declared in the class used for the - list. If they are declared in a super class you will get "could - not convert template argument" errors, even though it should - work. This is because @c &T::m is of type @c S::* if @c S is a - super class of @c T and @c m is declared in @c S. My view is that - if I write "&T::m" I want a "T::*" and the compiler shouldn't go - rummaging through the class hierarchy for some other type. For - MSVC you can @c static_cast the template arguments as a - workaround, but not in gcc. - @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 + 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 + 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 -/// FreeBSD doesn't like just declaring the tag struct we need so we have to include the file. +/// Clang doesn't like just declaring the tag struct we need so we have to include the file. #include #include +namespace ts +{ /** Intrusive doubly linked list container. - This holds items in a doubly linked list using links in the items. Items are placed in the list by changing the - pointers. An item can be in only one list for a set of links, but an item can contain multiple sets of links. This - requires different specializations of this template because link access is part of the type specification. Memory - for items is not managed by this class - instances must be allocated and released elsewhere. In particular removing - an item from the list does not destruct or free the item. + This holds items in a doubly linked list using links in the items. Items are placed in the list + by changing the pointers. An item can be in only one list for a set of links, but an item can + contain multiple sets of links. This requires different specializations of this template because + link access is part of the type specification. Memory for items is not managed by this class - + instances must be allocated and released elsewhere. In particular removing an item from the list + does not destruct or free the item. - Access to the links is described by a linkage class which is required to contain the following members: + Access to the links is described by a linkage class which is required to contain the following + members: - The static method @c next_ptr which returns a reference to the pointer to the next item. - The static method @c prev_ptr which returns a reference to the pointer to the previous item. - The pointer methods take a single argument of @c Item* and must return a reference to a pointer instance. This - type is deduced from the methods and is not explicitly specified. It must be cheaply copyable and stateless. + The pointer methods take a single argument of @c Item* and must return a reference to a pointer + instance. This type is deduced from the methods and is not explicitly specified. It must be + cheaply copyable and stateless. + + It is the responsibility of the item class to initialize the link pointers. When an item is + removed from the list the link pointers are set to @c nullptr. An example declaration woudl be @code // Item in the list. struct Thing { - Thing* _next; - Thing* _prev; + Thing* _next {nullptr}; + Thing* _prev {nullptr}; Data _payload; // Linkage descriptor. @@ -78,12 +71,14 @@ }; }; - using ThingList = IntrusiveDList; + using ThingList = ts::IntrusiveDList; @endcode - Element access is done by using either STL style iteration, or direct access to the member pointers. A client can - have its own mechanism for getting an element to start, or use the @c head and/or @c tail methods to get the - first and last elements in the list respectively. Note if the list is empty then @c Linkage::NIL will be returned. + Item access is done by using either STL style iteration, or direct access to the member + pointers. A client can have its own mechanism for getting an element to start, or use the @c + head and/or @c tail methods to get the first and last elements in the list respectively. Note if + the list is empty then @c nullptr will be returned. There are simple and fast conversions + between item pointers and iterators. */ template class IntrusiveDList @@ -95,8 +90,7 @@ template class IntrusiveDList /// The list item type. using value_type = typename std::remove_pointer::type>::type; - /** Const iterator for the list. - */ + /// Const iterator. class const_iterator { using self_type = const_iterator; ///< Self reference type. @@ -163,8 +157,7 @@ template class IntrusiveDList const_iterator(const list_type *list, value_type *v); }; - /** Iterator for the list. - */ + /// Iterator for the list. class iterator : public const_iterator { using self_type = iterator; ///< Self reference type. @@ -212,7 +205,7 @@ template class IntrusiveDList value_type *operator->() const; /// Convenience conversion to pointer type - /// Because of how this list is normally used, being able to pass an iterator as a pointer is quite convienent. + /// Because of how this list is normally used, being able to pass an iterator as a pointer is quite convenient. /// If the iterator isn't valid, it converts to @c nullptr. operator value_type *() const; @@ -221,13 +214,24 @@ template class IntrusiveDList iterator(list_type *list, value_type *v); }; + /// Construct to empty list. + IntrusiveDList() = default; + + /// Move list to @a this and leave @a that empty. + IntrusiveDList(self_type &&that); + + /// No copy assignment because items can't be in two lists and can't copy items. + self_type &operator=(const self_type &that) = delete; + /// Move @a that to @a this. + self_type &operator=(self_type &&that); + /// Empty check. /// @return @c true if the list is empty. bool empty() const; /// Presence check (linear time). /// @return @c true if @a v is in the list, @c false if not. - bool contains(value_type *v) const; + bool contains(const value_type *v) const; /// Add @a elt as the first element in the list. /// @return This container. @@ -300,8 +304,9 @@ template class IntrusiveDList /** Get an iterator for the item @a v. * - * It is the responsibility of the caller that @a v is in the list. The purpose is to make iteration starting - * at a specific element easier (i.e. all of the link manipulation and checking is done by the iterator). + * It is the responsibility of the caller that @a v is in the list. The purpose is to make + * iteration starting at a specific element easier (i.e. all of the link manipulation and checking + * is done by the iterator). * * @return An @c iterator that refers to @a v. */ @@ -310,9 +315,16 @@ template class IntrusiveDList /// Get the first element. value_type *head(); + const value_type *head() const; /// Get the last element. value_type *tail(); + const value_type *tail() const; + + /** Apply a functor to every element in the list. + * This iterates over the list correctly even if the functor destroys or removes elements. + */ + template self_type &apply(F &&f); protected: value_type *_head{nullptr}; ///< First element in list. @@ -320,9 +332,50 @@ template class IntrusiveDList size_t _count{0}; ///< # of elements in list. }; -namespace ts +/** Utility class to provide intrusive links. + * + * @tparam T Class to link. + * + * The normal use is to declare this as a member to provide the links and the linkage functions. + * @code + * class Thing { + * // blah blah + * Thing* _next{nullptr}; + * Thing* _prev{nullptr}; + * using Linkage = ts::IntrusiveLinkage; + * }; + * using ThingList = ts::ts::IntrusiveDList; + * @endcode + * The template will default to the names '_next' and '_prev' therefore in the example it could + * have been done as + * @code + * using Linkage = ts::IntrusiveLinkage; + * @endcode + */ +template struct IntrusiveLinkage { + static T *&next_ptr(T *thing); ///< Retrieve reference to next pointer. + static T *&prev_ptr(T *thing); ///< Retrive reference to previous pointer. +}; + +template +T *& +IntrusiveLinkage::next_ptr(T *thing) +{ + return thing->*NEXT; +} +template +T *& +IntrusiveLinkage::prev_ptr(T *thing) { + return thing->*PREV; +} + /** Utility cast to change the underlying type of a pointer reference. + * + * @tparam T The resulting pointer reference type. + * @tparam P The starting pointer reference type. + * @param p A reference to pointer to @a P. + * @return A reference to the same pointer memory of type @c T*&. * * This changes a reference to a pointer to @a P to a reference to a pointer to @a T. This is useful * for intrusive links that are inherited. For instance @@ -335,16 +388,13 @@ namespace ts * To make @c BetterThing work with an intrusive container without making new link members, * * @code - * static BetterThing*& next_ptr(BetterThing* bt) { return ts::ptr_ref_cast(_next); } + * static BetterThing*& next_ptr(BetterThing* bt) { + * return ts::ptr_ref_cast(_next); + * } * @endcode * * This is both convenient and gets around aliasing warnings from the compiler that can arise from * using @c reinterpret_cast. - * - * @tparam T The resulting pointer reference type. - * @tparam P The starting pointer reference type. - * @param p A reference to pointer to @a P. - * @return A reference to the same pointer memory of type @c T*&. */ template T *& @@ -357,25 +407,23 @@ ptr_ref_cast(P *&p) return *(u._t); }; -} // namespace ts - // --- Implementation --- -template IntrusiveDList::const_iterator::const_iterator() {} +template ts::IntrusiveDList::const_iterator::const_iterator() {} template -IntrusiveDList::const_iterator::const_iterator(const list_type *list, value_type *v) +ts::IntrusiveDList::const_iterator::const_iterator(const list_type *list, value_type *v) : _list(const_cast(list)), _v(const_cast(v)) { } -template IntrusiveDList::iterator::iterator() {} +template ts::IntrusiveDList::iterator::iterator() {} -template IntrusiveDList::iterator::iterator(IntrusiveDList *list, value_type *v) : super_type(list, v) {} +template ts::IntrusiveDList::iterator::iterator(IntrusiveDList *list, value_type *v) : super_type(list, v) {} template auto -IntrusiveDList::const_iterator::operator++() -> self_type & +ts::IntrusiveDList::const_iterator::operator++() -> self_type & { _v = L::next_ptr(_v); return *this; @@ -383,7 +431,7 @@ IntrusiveDList::const_iterator::operator++() -> self_type & template auto -IntrusiveDList::iterator::operator++() -> self_type & +ts::IntrusiveDList::iterator::operator++() -> self_type & { this->super_type::operator++(); return *this; @@ -391,7 +439,7 @@ IntrusiveDList::iterator::operator++() -> self_type & template auto -IntrusiveDList::const_iterator::operator++(int) -> self_type +ts::IntrusiveDList::const_iterator::operator++(int) -> self_type { self_type tmp(*this); ++*this; @@ -400,7 +448,7 @@ IntrusiveDList::const_iterator::operator++(int) -> self_type template auto -IntrusiveDList::iterator::operator++(int) -> self_type +ts::IntrusiveDList::iterator::operator++(int) -> self_type { self_type tmp(*this); ++*this; @@ -409,7 +457,7 @@ IntrusiveDList::iterator::operator++(int) -> self_type template auto -IntrusiveDList::const_iterator::operator--() -> self_type & +ts::IntrusiveDList::const_iterator::operator--() -> self_type & { if (_v) { _v = L::prev_ptr(_v); @@ -421,7 +469,7 @@ IntrusiveDList::const_iterator::operator--() -> self_type & template auto -IntrusiveDList::iterator::operator--() -> self_type & +ts::IntrusiveDList::iterator::operator--() -> self_type & { this->super_type::operator--(); return *this; @@ -429,7 +477,7 @@ IntrusiveDList::iterator::operator--() -> self_type & template auto -IntrusiveDList::const_iterator::operator--(int) -> self_type +ts::IntrusiveDList::const_iterator::operator--(int) -> self_type { self_type tmp(*this); --*this; @@ -438,53 +486,61 @@ IntrusiveDList::const_iterator::operator--(int) -> self_type template auto -IntrusiveDList::iterator::operator--(int) -> self_type +ts::IntrusiveDList::iterator::operator--(int) -> self_type { self_type tmp(*this); --*this; return tmp; } -template auto IntrusiveDList::const_iterator::operator-> () const -> value_type * +template auto ts::IntrusiveDList::const_iterator::operator-> () const -> value_type * { return _v; } -template auto IntrusiveDList::iterator::operator-> () const -> value_type * +template auto ts::IntrusiveDList::iterator::operator-> () const -> value_type * { return super_type::_v; } -template IntrusiveDList::const_iterator::operator value_type *() const +template ts::IntrusiveDList::const_iterator::operator value_type *() const { return _v; } -template auto IntrusiveDList::const_iterator::operator*() const -> value_type & +template auto ts::IntrusiveDList::const_iterator::operator*() const -> value_type & { return *_v; } -template auto IntrusiveDList::iterator::operator*() const -> value_type & +template auto ts::IntrusiveDList::iterator::operator*() const -> value_type & { return *super_type::_v; } -template IntrusiveDList::iterator::operator value_type *() const +template ts::IntrusiveDList::iterator::operator value_type *() const { return super_type::_v; } +/// --- Main class + +template +ts::IntrusiveDList::IntrusiveDList(self_type &&that) : _head(that._head), _tail(that._tail), _count(that._count) +{ + that.clear(); +} + template bool -IntrusiveDList::empty() const +ts::IntrusiveDList::empty() const { return _head == nullptr; } template bool -IntrusiveDList::contains(value_type *v) const +ts::IntrusiveDList::contains(const value_type *v) const { for (auto thing = _head; thing; thing = L::next_ptr(thing)) { if (thing == v) @@ -495,21 +551,21 @@ IntrusiveDList::contains(value_type *v) const template bool -IntrusiveDList::const_iterator::operator==(self_type const &that) const +ts::IntrusiveDList::const_iterator::operator==(self_type const &that) const { return this->_v == that._v; } template bool -IntrusiveDList::const_iterator::operator!=(self_type const &that) const +ts::IntrusiveDList::const_iterator::operator!=(self_type const &that) const { return this->_v != that._v; } template auto -IntrusiveDList::prepend(value_type *v) -> self_type & +ts::IntrusiveDList::prepend(value_type *v) -> self_type & { L::prev_ptr(v) = nullptr; if (nullptr != (L::next_ptr(v) = _head)) { @@ -524,7 +580,7 @@ IntrusiveDList::prepend(value_type *v) -> self_type & template auto -IntrusiveDList::append(value_type *v) -> self_type & +ts::IntrusiveDList::append(value_type *v) -> self_type & { L::next_ptr(v) = nullptr; if (nullptr != (L::prev_ptr(v) = _tail)) { @@ -539,7 +595,7 @@ IntrusiveDList::append(value_type *v) -> self_type & template auto -IntrusiveDList::take_head() -> value_type * +ts::IntrusiveDList::take_head() -> value_type * { value_type *zret = _head; if (_head) { @@ -556,7 +612,7 @@ IntrusiveDList::take_head() -> value_type * template auto -IntrusiveDList::take_tail() -> value_type * +ts::IntrusiveDList::take_tail() -> value_type * { value_type *zret = _tail; if (_tail) { @@ -573,7 +629,7 @@ IntrusiveDList::take_tail() -> value_type * template auto -IntrusiveDList::insert_after(value_type *target, value_type *v) -> self_type & +ts::IntrusiveDList::insert_after(value_type *target, value_type *v) -> self_type & { if (target) { if (nullptr != (L::next_ptr(v) = L::next_ptr(target))) { @@ -593,14 +649,14 @@ IntrusiveDList::insert_after(value_type *target, value_type *v) -> self_type template auto -IntrusiveDList::insert_after(iterator const &target, value_type *v) -> self_type & +ts::IntrusiveDList::insert_after(iterator const &target, value_type *v) -> self_type & { return this->insert_after(target._v, v); } template auto -IntrusiveDList::insert_before(value_type *target, value_type *v) -> self_type & +ts::IntrusiveDList::insert_before(value_type *target, value_type *v) -> self_type & { if (target) { if (nullptr != (L::prev_ptr(v) = L::prev_ptr(target))) { @@ -620,14 +676,14 @@ IntrusiveDList::insert_before(value_type *target, value_type *v) -> self_type template auto -IntrusiveDList::insert_before(iterator const &target, value_type *v) -> self_type & +ts::IntrusiveDList::insert_before(iterator const &target, value_type *v) -> self_type & { return this->insert_before(target._v, v); } template auto -IntrusiveDList::erase(value_type *v) -> value_type * +ts::IntrusiveDList::erase(value_type *v) -> value_type * { value_type *zret{nullptr}; @@ -652,14 +708,14 @@ IntrusiveDList::erase(value_type *v) -> value_type * template auto -IntrusiveDList::erase(const iterator &loc) -> iterator +ts::IntrusiveDList::erase(const iterator &loc) -> iterator { return this->iterator_for(this->erase(loc._v)); }; template auto -IntrusiveDList::erase(const iterator &first, const iterator &limit) -> iterator +ts::IntrusiveDList::erase(const iterator &first, const iterator &limit) -> iterator { value_type *spot = first; value_type *prev{L::prev_ptr(spot)}; @@ -685,74 +741,142 @@ IntrusiveDList::erase(const iterator &first, const iterator &limit) -> iterat return {limit._v, this}; }; +template +auto +ts::IntrusiveDList::operator=(self_type &&that) -> self_type & +{ + if (this != &that) { + this->_head = that._head; + this->_tail = that._tail; + this->_count = that._count; + that.clear(); + } + return *this; +} + template size_t -IntrusiveDList::count() const +ts::IntrusiveDList::count() const { return _count; }; template auto -IntrusiveDList::begin() const -> const_iterator +ts::IntrusiveDList::begin() const -> const_iterator { return const_iterator{this, _head}; }; template auto -IntrusiveDList::begin() -> iterator +ts::IntrusiveDList::begin() -> iterator { return iterator{this, _head}; }; template auto -IntrusiveDList::end() const -> const_iterator +ts::IntrusiveDList::end() const -> const_iterator { return const_iterator{this, nullptr}; }; template auto -IntrusiveDList::end() -> iterator +ts::IntrusiveDList::end() -> iterator { return iterator{this, nullptr}; }; template auto -IntrusiveDList::iterator_for(value_type *v) -> iterator +ts::IntrusiveDList::iterator_for(value_type *v) -> iterator { return iterator{this, v}; }; template auto -IntrusiveDList::iterator_for(const value_type *v) const -> const_iterator +ts::IntrusiveDList::iterator_for(const value_type *v) const -> const_iterator { return const_iterator{this, v}; }; template auto -IntrusiveDList::tail() -> value_type * +ts::IntrusiveDList::tail() -> value_type * +{ + return _tail; +} + +template +auto +ts::IntrusiveDList::tail() const -> const value_type * { return _tail; } template auto -IntrusiveDList::head() -> value_type * +ts::IntrusiveDList::head() -> value_type * +{ + return _head; +} + +template +auto +ts::IntrusiveDList::head() const -> const value_type * { return _head; } template auto -IntrusiveDList::clear() -> self_type & +ts::IntrusiveDList::clear() -> self_type & { _head = _tail = nullptr; _count = 0; return *this; }; + +namespace detail +{ + // Make @c apply more convenient by allowing the function to take a reference type or pointer type + // to the container elements. The pointer type is the base, plus a shim to convert from a reference + // type functor to a pointer pointer type. The complex return type definition forces only one, but + // not both, to be valid for a particular functor. This also must be done via free functions and not + // method overloads because the compiler forces a match up of method definitions and declarations + // before any template instantiation. + + template + auto + Intrusive_DList_Apply(ts::IntrusiveDList &list, F &&f) + -> decltype(f(*static_cast::value_type *>(nullptr)), list) + { + return list.apply([&f](typename ts::IntrusiveDList::value_type *v) { return f(*v); }); + } + + template + auto + Intrusive_DList_Apply(ts::IntrusiveDList &list, F &&f) + -> decltype(f(static_cast::value_type *>(nullptr)), list) + { + auto spot{list.begin()}; + auto limit{list.end()}; + while (spot != limit) { + f(spot++); // post increment means @a spot is updated before @a f is applied. + } + return list; + } +} // namespace detail + +template +template +auto +ts::IntrusiveDList::apply(F &&f) -> self_type & +{ + return detail::Intrusive_DList_Apply(*this, f); +}; + +} // namespace ts diff --git a/include/tscore/IntrusiveHashMap.h b/include/tscpp/util/IntrusiveHashMap.h similarity index 91% rename from include/tscore/IntrusiveHashMap.h rename to include/tscpp/util/IntrusiveHashMap.h index 06c50305dee..87d3500f6d5 100644 --- a/include/tscore/IntrusiveHashMap.h +++ b/include/tscpp/util/IntrusiveHashMap.h @@ -4,21 +4,18 @@ @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 + 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. + 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 @@ -26,8 +23,10 @@ #include #include #include -#include "tscore/IntrusiveDList.h" +#include "tscpp/util/IntrusiveDList.h" +namespace ts +{ /** Intrusive Hash Table. Values stored in this container are not destroyed when the container is destroyed or removed from the container. @@ -626,32 +625,32 @@ IntrusiveHashMap::erase(range const &r) -> iterator namespace detail { -// Make @c apply more convenient by allowing the function to take a reference type or pointer type to the container -// elements. The pointer type is the base, plus a shim to convert from a reference type functor to a pointer pointer -// type. The complex return type definition forces only one, but not both, to be valid for a particular functor. This -// also must be done via free functions and not method overloads because the compiler forces a match up of method -// definitions and declarations before any template instantiation. - -template -auto -IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) - -> decltype(f(*static_cast::value_type *>(nullptr)), map) -{ - return map.apply([&f](typename IntrusiveHashMap::value_type *v) { return f(*v); }); -} + // Make @c apply more convenient by allowing the function to take a reference type or pointer type to the container + // elements. The pointer type is the base, plus a shim to convert from a reference type functor to a pointer pointer + // type. The complex return type definition forces only one, but not both, to be valid for a particular functor. This + // also must be done via free functions and not method overloads because the compiler forces a match up of method + // definitions and declarations before any template instantiation. + + template + auto + IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) + -> decltype(f(*static_cast::value_type *>(nullptr)), map) + { + return map.apply([&f](typename IntrusiveHashMap::value_type *v) { return f(*v); }); + } -template -auto -IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) - -> decltype(f(static_cast::value_type *>(nullptr)), map) -{ - auto spot{map.begin()}; - auto limit{map.end()}; - while (spot != limit) { - f(spot++); // post increment means @a spot is updated before @a f is applied. + template + auto + IntrusiveHashMapApply(IntrusiveHashMap &map, F &&f) + -> decltype(f(static_cast::value_type *>(nullptr)), map) + { + auto spot{map.begin()}; + auto limit{map.end()}; + while (spot != limit) { + f(spot++); // post increment means @a spot is updated before @a f is applied. + } + return map; } - return map; -} } // namespace detail template @@ -727,4 +726,5 @@ IntrusiveHashMap::get_expansion_limit() const { return _expansion_limit; } -/* ---------------------------------------------------------------------------------------------- */ + +} // namespace ts diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am index caeed36daab..51fd7876f9f 100644 --- a/include/tscpp/util/Makefile.am +++ b/include/tscpp/util/Makefile.am @@ -19,5 +19,14 @@ library_includedir=$(includedir)/tscpp/util library_include_HEADERS = \ + BufferWriter.h \ + bwf_printf.h \ + bwf_ex.h \ + bwf_base.h \ + bwf_std.h \ + IntrusiveDList.h \ + IntrusiveHashMap.h \ + MemArena.h \ + MemSpan.h \ PostScript.h \ TextView.h diff --git a/include/tscore/MemArena.h b/include/tscpp/util/MemArena.h similarity index 76% rename from include/tscore/MemArena.h rename to include/tscpp/util/MemArena.h index 3a3519a0b2e..bbfb625ecd3 100644 --- a/include/tscore/MemArena.h +++ b/include/tscpp/util/MemArena.h @@ -4,20 +4,17 @@ @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 + 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 + 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. */ @@ -27,11 +24,11 @@ #include #include #include + #include "tscpp/util/MemSpan.h" -#include "tscore/Scalar.h" -#include +#include "tscpp/util/Scalar.h" +#include "tscpp/util/IntrusiveDList.h" -/// Apache Traffic Server commons. namespace ts { /** A memory arena. @@ -46,19 +43,18 @@ class MemArena { using self_type = MemArena; ///< Self reference type. protected: - struct Block; // Forward declare - using BlockPtr = ts::IntrusivePtr; - friend struct IntrusivePtrPolicy; - /** Simple internal arena block of memory. Maintains the underlying memory. - * - * Intrusive pointer is used to keep all of the memory in this single block. This struct is just - * the header on the full memory block allowing the raw memory and the meta data to be obtained - * in a single memory allocation. - */ - struct Block : public ts::IntrusivePtrCounter { + /// Simple internal arena block of memory. Maintains the underlying memory. + struct Block { size_t size; ///< Actual block size. size_t allocated{0}; ///< Current allocated (in use) bytes. - BlockPtr next; ///< List of previous blocks. + struct Linkage { + Block *_next{nullptr}; + Block *_prev{nullptr}; + + static Block *&next_ptr(Block *); + + static Block *&prev_ptr(Block *); + } _link; /** Construct to have @a n bytes of available storage. * @@ -104,6 +100,8 @@ class MemArena static void operator delete(void *ptr); }; + using BlockList = IntrusiveDList; + public: /** Construct with reservation hint. * @@ -120,6 +118,16 @@ class MemArena */ explicit MemArena(size_t n = DEFAULT_BLOCK_SIZE); + /// no copying + MemArena(self_type const &that) = delete; + MemArena(self_type &&that) = default; + + /// Destructor. + ~MemArena(); + + self_type &operator=(self_type const &that) = delete; + self_type &operator=(self_type &&that) = default; + /** Allocate @a n bytes of storage. Returns a span of memory within the arena. alloc() is self expanding but DOES NOT self @@ -183,7 +191,7 @@ class MemArena size_t remaining() const; /// @returns the remaining contiguous space in the active generation. - MemSpan remnant() const; + MemSpan remnant(); /// @returns the total number of bytes allocated within the arena. size_t allocated_size() const; @@ -206,10 +214,14 @@ class MemArena * @param n Size of block to allocate. * @return */ - BlockPtr make_block(size_t n); + Block *make_block(size_t n); + /// Clean up the frozen list. + void destroy_frozen(); + /// Clean up the active list + void destroy_active(); - using Page = ts::Scalar<4096>; ///< Size for rounding block sizes. - using Paragraph = ts::Scalar<16>; ///< Minimum unit of memory allocation. + using Page = Scalar<4096>; ///< Size for rounding block sizes. + using Paragraph = Scalar<16>; ///< Minimum unit of memory allocation. static constexpr size_t ALLOC_HEADER_SIZE = 16; ///< Guess of overhead of @c malloc /// Initial block size to allocate if not specified via API. @@ -218,20 +230,32 @@ class MemArena size_t _active_allocated = 0; ///< Total allocations in the active generation. size_t _active_reserved = 0; ///< Total current reserved memory. /// Total allocations in the previous generation. This is only non-zero while the arena is frozen. - size_t _prev_allocated = 0; + size_t _frozen_allocated = 0; /// Total frozen reserved memory. - size_t _prev_reserved = 0; + size_t _frozen_reserved = 0; /// Minimum free space needed in the next allocated block. /// This is not zero iff @c reserve was called. size_t _reserve_hint = 0; - BlockPtr _prev; ///< Previous generation, frozen memory. - BlockPtr _active; ///< Current generation. Allocate here. + BlockList _frozen; ///< Previous generation, frozen memory. + BlockList _active; ///< Current generation. Allocate here. }; // Implementation +inline auto +MemArena::Block::Linkage::next_ptr(Block *b) -> Block *& +{ + return b->_link._next; +} + +inline auto +MemArena::Block::Linkage::prev_ptr(Block *b) -> Block *& +{ + return b->_link._prev; +} + inline MemArena::Block::Block(size_t n) : size(n) {} inline char * @@ -262,7 +286,9 @@ MemArena::Block::remaining() const inline MemSpan MemArena::Block::alloc(size_t n) { - ink_assert(n <= this->remaining()); + if (n > this->remaining()) { + throw(std::invalid_argument{"MemArena::Block::alloc size is more than remaining."}); + } MemSpan zret = this->remnant().prefix(n); allocated += n; return zret; @@ -292,25 +318,25 @@ MemArena::size() const inline size_t MemArena::allocated_size() const { - return _prev_allocated + _active_allocated; + return _frozen_allocated + _active_allocated; } inline size_t MemArena::remaining() const { - return _active ? _active->remaining() : 0; + return _active.empty() ? 0 : _active.head()->remaining(); } inline MemSpan -MemArena::remnant() const +MemArena::remnant() { - return _active ? _active->remnant() : MemSpan{}; + return _active.empty() ? MemSpan() : _active.head()->remnant(); } inline size_t MemArena::reserved_size() const { - return _active_reserved + _prev_reserved; + return _active_reserved + _frozen_reserved; } } // namespace ts diff --git a/include/tscore/Scalar.h b/include/tscpp/util/Scalar.h similarity index 92% rename from include/tscore/Scalar.h rename to include/tscpp/util/Scalar.h index e71a0824da0..62755da83e7 100644 --- a/include/tscore/Scalar.h +++ b/include/tscpp/util/Scalar.h @@ -8,21 +8,18 @@ @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 + 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. + 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 @@ -31,7 +28,8 @@ #include #include #include -#include "tscore/BufferWriter.h" + +#include "tscpp/util/ts_meta.h" namespace tag { @@ -895,70 +893,45 @@ Scalar::minus(Counter n) const -> self namespace detail { - // These classes exist only to create distinguishable overloads. - struct tag_label_A { - }; - struct tag_label_B : public tag_label_A { - }; - // The purpose is to print a label for a tagged type only if the tag class defines a member that - // is the label. This creates a base function that always works and does nothing. The second - // function creates an overload if the tag class has a member named 'label' that has an stream IO - // output operator. When invoked with a second argument of B then the second overload exists and - // is used, otherwise only the first exists and that is used. The critical technology is the use - // of 'auto' and 'decltype' which effectively checks if the code inside 'decltype' compiles. - template - inline std::ostream & - tag_label(std::ostream &s, tag_label_A const &) - { - return s; - } template - inline BufferWriter & - tag_label(BufferWriter &w, BWFSpec const &, tag_label_A const &) + auto + tag_label(std::ostream &, const meta::CaseTag<0> &) -> void { - return w; } + template - inline auto - tag_label(std::ostream &s, tag_label_B const &) -> decltype(s << T::label, s) + auto + tag_label(std::ostream &w, const meta::CaseTag<1> &) -> decltype(T::label, meta::CaseVoidFunc()) { - return s << T::label; + w << T::label; } + template - inline auto - tag_label(BufferWriter &w, BWFSpec const &spec, tag_label_B const &) -> decltype(bwformat(w, spec, T::label), w) + inline std::ostream & + tag_label(std::ostream &w) { - return bwformat(w, spec, T::label); + tag_label(w, meta::CaseArg); + return w; } } // namespace detail - -template -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, Scalar const &x) -{ - static constexpr ts::detail::tag_label_B b{}; - bwformat(w, spec, x.value()); - return ts::detail::tag_label(w, spec, b); -} - } // namespace ts namespace std { +/// Compute common type of two scalars. +/// In `std` to overload the base definition. This yields a type that has the common type of the +/// counter type and a scale that is the GCF of the input scales. +template struct common_type, ts::Scalar> { + using R = std::ratio; + using type = ts::Scalar::type, T>; +}; + template ostream & operator<<(ostream &s, ts::Scalar const &x) { - static ts::detail::tag_label_B b; // Can't be const or the compiler gets upset. s << x.value(); - return ts::detail::tag_label(s, b); + return ts::detail::tag_label(s); } -/// Compute common type of two scalars. -/// In `std` to overload the base definition. This yields a type that has the common type of the -/// counter type and a scale that is the GCF of the input scales. -template struct common_type, ts::Scalar> { - typedef std::ratio R; - typedef ts::Scalar::type, T> type; -}; } // namespace std diff --git a/include/tscpp/util/bwf_base.h b/include/tscpp/util/bwf_base.h new file mode 100644 index 00000000000..9f0b23c70de --- /dev/null +++ b/include/tscpp/util/bwf_base.h @@ -0,0 +1,963 @@ +/** @file + + Basic formatting support for @c 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tscpp/util/TextView.h" +#include "tscpp/util/MemSpan.h" +#include "tscpp/util/MemArena.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/ts_meta.h" + +namespace ts +{ +namespace bwf +{ + /** Parsed version of a format specifier. + * + * Literals are represented as an instance of this class, with the type set to + * @c LITERAL_TYPE and the literal text in the @a _ext field. + */ + struct Spec { + using self_type = Spec; ///< Self reference type. + + static constexpr char DEFAULT_TYPE = 'g'; ///< Default format type. + static constexpr char INVALID_TYPE = 0; ///< Type for missing or invalid specifier. + static constexpr char LITERAL_TYPE = '"'; ///< Internal type to mark a literal. + static constexpr char CAPTURE_TYPE = 1; ///< Internal type to mark a capture. + + static constexpr char SIGN_ALWAYS = '+'; ///< Always print a sign character. + static constexpr char SIGN_NEVER = ' '; ///< Never print a sign character. + static constexpr char SIGN_NEG = '-'; ///< Print a sign character only for negative values (default). + + /// Constructor a default instance. + constexpr Spec() {} + + /// Construct by parsing @a fmt. + Spec(const TextView &fmt); + /// Parse a specifier + bool parse(TextView fmt); + + char _fill = ' '; ///< Fill character. + char _sign = SIGN_NEG; ///< Numeric sign style. + 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 = DEFAULT_TYPE; ///< 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 = std::numeric_limits::max(); ///< Maximum width + int _idx = -1; ///< Positional "name" of the specification. + std::string_view _name; ///< Name of the specification. + std::string_view _ext; ///< Extension if provided. + + /// Global default instance for use in situations where a format specifier isn't available. + static const self_type DEFAULT; + + /// Validate @a c is a specifier type indicator. + static bool is_type(char c); + + /// Check if the type flag is numeric. + static bool is_numeric_type(char c); + + /// Check if the type is an upper case variant. + static bool is_upper_case_type(char c); + + /// Check if the type @a in @a this is numeric. + bool has_numeric_type() const; + + /// Check if the type in @a this is an upper case variant. + bool has_upper_case_type() const; + + /// Check if the type is a raw pointer. + bool has_pointer_type() const; + + /// Check if the type is valid. + bool has_valid_type() const; + + 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); + + /// Handrolled initialization the character syntactic property data. + static const struct Property { + Property(); ///< Default constructor, creates initialized flag set. + /// Flag storage, indexed by character value. + uint8_t _data[0x100]; + /// Flag mask values. + static constexpr uint8_t ALIGN_MASK = 0x0F; ///< Alignment type. + static constexpr uint8_t TYPE_CHAR = 0x10; ///< A valid type character. + static constexpr uint8_t UPPER_TYPE_CHAR = 0x20; ///< Upper case flag. + static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output. + static constexpr uint8_t SIGN_CHAR = 0x80; ///< Is sign character. + } _prop; + }; + + /** Format string support. + * + * This contains the parsing logic for format strings and also serves as the type for pre-compiled + * format string. + * + * When used by the print formatting logic, there is an abstraction layer, "extraction", which + * performs the equivalent of the @c parse method. This allows the formatting to treat pre-compiled + * or immediately parsed format strings the same. It also enables providing any parser that can + * deliver literals and @c Spec instances. + */ + class Format + { + public: + /// Construct from a format string @a fmt. + Format(TextView fmt); + + /// Extraction support for TextView. + struct TextViewExtractor { + TextView _fmt; + explicit operator bool() const; + bool operator()(std::string_view &literal_v, Spec &spec); + + /** 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); + }; + /// Wrap the format string in an extractor. + static TextViewExtractor bind(TextView fmt); + + /// Extraction support for pre-parsed format strings. + struct FormatExtractor { + const std::vector &_fmt; ///< Parsed format string. + int _idx = 0; ///< Element index. + explicit operator bool() const; + bool operator()(std::string_view &literal_v, Spec &spec); + }; + /// Wrap the format instance in an extractor. + FormatExtractor bind() const; + + protected: + /// Default constructor for use by subclasses with alternate formatting. + Format() = default; + + std::vector _items; ///< Items from format string. + }; + + // Name binding - support for having format specifier names. + + /// Generic name generator signature. + using BoundNameSignature = BufferWriter &(BufferWriter &, Spec const &); + + /** Protocol class for handling bound names. + * + * This is an API facade for names that are fully bound and do not need any data / context + * beyond that of the @c BufferWriter. It is expected other name collections will subclass + * this to pass to the formatting logic. + */ + class BoundNames + { + public: + virtual ~BoundNames(); + /** Generate output text for @a name on the output @a w using the format specifier @a spec. + * This must match the @c BoundNameSignature type. + * + * @param w Output stream. + * @param spec Parsed format specifier. + * + * @note The tag name can be found in @c spec._name. + * + * @return + */ + virtual BufferWriter &operator()(BufferWriter &w, Spec const &spec) const = 0; + + /** Capture an argument. + * + * @param w Output. + * @param spec Capturing specifier. + * @param arg The captured argument. + * + * @note This is really for C / printf support where some specifiers are dependent on values + * passed in other arguments. + */ + virtual void capture(BufferWriter &w, Spec const &spec, std::any const &arg) const; + + protected: + /// Write missing name output. + BufferWriter &err_invalid_name(BufferWriter &w, Spec const &) const; + }; + + /// Empty bound names - used for where no name binding is available or desired. + /// Throws if any name is used. + class NilBoundNames : public BoundNames + { + public: + BufferWriter &operator()(BufferWriter &, Spec const &) const override; + }; + + /** Binding names to generators. + * @tparam F The function signature for generators in this container. + * + * This is a base class used by different types of name containers. It is not expected to be used + * directly. + */ + template class NameBinding + { + private: + using self_type = NameBinding; ///< self reference type. + public: + using Generator = std::function; + + /// Construct an empty name set. + NameBinding(); + /// Construct and assign the names and generators in @a list + NameBinding(std::initializer_list> list); + + /** Assign the @a generator to the @a name. + * + * @param name Name associated with the @a generator. + * @param generator The generator function. + */ + self_type &assign(std::string_view name, const Generator &generator); + + protected: + /// Copy @a name in to local storage and return a view of it. + std::string_view localize(std::string_view name); + + using Map = std::unordered_map; + Map _map; ///< Mapping of name -> generator + MemArena _arena{1024}; ///< Local name storage. + }; + + /** A class to hold global name bindings. + * + * These names access global data and therefore have no context. An instance of this is used + * as the default if no explicit name set is provided. + */ + class GlobalNames : public NameBinding + { + using self_type = GlobalNames; + using super_type = NameBinding; + using Map = super_type::Map; + + public: + using super_type::super_type; + /// Provide an accessor for formatting. + BoundNames &bind(); + + protected: + class Binding : public BoundNames + { + public: + Binding(const super_type::Map &map); + BufferWriter &operator()(BufferWriter &w, const Spec &spec) const override; + + protected: + const Map &_map; + } _binding{super_type::_map}; + }; + + /** Binding for context based names. + * + * @tparam T The context type. This is used directly. If the context needs to be @c const + * then this parameter should make that explicit, e.g. @c Names. This + * paramater is accessible via the @c context_type alias. + * + * This enables named format specifications, such as "{tag}", generated from a context of type @a + * T. Each supported tag requires a @a Generator which is a functor of type + * + * @code + * BufferWriter & generator(BufferWriter & w, const Spec & spec, T & context); + * @endcode + */ + template class ContextNames : public NameBinding + { + private: + using self_type = ContextNames; ///< self reference type. + using super_type = NameBinding; + + public: + using context_type = T; ///< Export for external convenience. + /// Functional type for a generator. + using Generator = typename super_type::Generator; + using BoundGenerator = std::function; + + using super_type::super_type; // inherit @c super_type constructors. + + /** Assign the bound generator @a bg to @a name. + * + * This is used for generators in the namespace that do not require the context. + * + * @param name Name associated with the generator. + * @param bg A bound generator that requires no context. + * @return @c *this + */ + self_type &assign(std::string_view name, const BoundGenerator &bg); + + /// Inherit unbound generator assignment from the @c super_type. + using super_type::assign; + + /** Bind the names to a specific @a context. + * + * @param context The instance of @a T to use in the generators. + * @return A reference to an internal instance of a subclass of the protocol class @c BoundNames. + */ + const BoundNames &bind(context_type &context); + + protected: + using Map = typename super_type::Map; + /// Subclass of @a BoundNames used to bind this set of names to a context. + class Binding : public BoundNames + { + using self_type = Binding; + using super_type = BoundNames; + + public: + /// Invoke the generator for @a name. + BufferWriter &operator()(BufferWriter &w, const Spec &spec) const override; + + protected: + Binding(Map const &map); ///< Must have a map reference. + self_type &assign(context_type *); + + Map const &_map; ///< The mapping for name look ups. + context_type *_ctx = nullptr; ///< Context for generators. + + friend ContextNames; + } _binding{super_type::_map}; + }; + + /** Default global names. + * This nameset is used if no other is provided. Therefore bindings added to this nameset will be + * available in the default formatting use. + */ + extern GlobalNames Global_Names; + + // --------------- Implementation -------------------- + /// --- Spec --- + + inline Spec::Align + Spec::align_of(char c) + { + return static_cast(_prop._data[static_cast(c)] & Property::ALIGN_MASK); + } + + inline bool + Spec::is_sign(char c) + { + return _prop._data[static_cast(c)] & Property::SIGN_CHAR; + } + + inline bool + Spec::is_type(char c) + { + return _prop._data[static_cast(c)] & Property::TYPE_CHAR; + } + + inline bool + Spec::is_upper_case_type(char c) + { + return _prop._data[static_cast(c)] & Property::UPPER_TYPE_CHAR; + } + + inline bool + Spec::is_numeric_type(char c) + { + return _prop._data[static_cast(c)] & Property::NUMERIC_TYPE_CHAR; + } + + inline bool + Spec::has_numeric_type() const + { + return _prop._data[static_cast(_type)] & Property::NUMERIC_TYPE_CHAR; + } + + inline bool + Spec::has_upper_case_type() const + { + return _prop._data[static_cast(_type)] & Property::UPPER_TYPE_CHAR; + } + + inline bool + Spec::has_pointer_type() const + { + return _type == 'p' || _type == 'P'; + } + + inline bool + Spec::has_valid_type() const + { + return _type != INVALID_TYPE; + } + + inline auto + Format::bind(ts::TextView fmt) -> TextViewExtractor + { + return {fmt}; + } + + inline auto + Format::bind() const -> FormatExtractor + { + return {_items}; + } + + inline Format::TextViewExtractor::operator bool() const { return !_fmt.empty(); } + inline Format::FormatExtractor::operator bool() const { return _idx < static_cast(_fmt.size()); } + + /// --- Names / Generators --- + + // Base implementation does nothing as this is rarely used. + inline void + BoundNames::capture(BufferWriter &, ts::bwf::Spec const &, std::any const &) const + { + } + + inline BufferWriter & + BoundNames::err_invalid_name(BufferWriter &w, const Spec &spec) const + { + return w.print("{{~{}~}}", spec._name); + } + + inline BufferWriter & + NilBoundNames::operator()(BufferWriter &, bwf::Spec const &) const + { + throw std::runtime_error("Use of nil bound names in BW formating"); + } + + template inline ContextNames::Binding::Binding(Map const &map) : _map(map) {} + + template + inline const BoundNames & + ContextNames::bind(context_type &ctx) + { + return _binding.assign(&ctx); + } + + template + inline auto + ContextNames::Binding::assign(context_type *ctx) -> self_type & + { + _ctx = ctx; + return *this; + } + + template + BufferWriter & + ContextNames::Binding::operator()(BufferWriter &w, const Spec &spec) const + { + if (!spec._name.empty()) { + if (auto spot = _map.find(spec._name); spot != _map.end()) { + spot->second(w, spec, *_ctx); + } else { + this->err_invalid_name(w, spec); + } + } + return w; + } + + template NameBinding::NameBinding() {} + + template NameBinding::NameBinding(std::initializer_list> list) + { + for (auto &&[name, generator] : list) { + this->assign(name, generator); + } + } + + template + std::string_view + NameBinding::localize(std::string_view name) + { + auto span = _arena.alloc(name.size()); + memcpy(span.data(), name.data(), name.size()); + return span.view(); + } + + template + auto + NameBinding::assign(std::string_view name, const Generator &generator) -> self_type & + { + name = this->localize(name); + _map[name] = generator; + return *this; + } + + inline GlobalNames::Binding::Binding(const super_type::Map &map) : _map(map) {} + + inline BufferWriter & + GlobalNames::Binding::operator()(BufferWriter &w, const Spec &spec) const + { + if (!spec._name.empty()) { + if (auto spot = _map.find(spec._name); spot != _map.end()) { + spot->second(w, spec); + } else { + this->err_invalid_name(w, spec); + } + } + return w; + } + + inline BoundNames & + GlobalNames::bind() + { + return _binding; + } + + /// --- Formatting --- + + /// Internal signature for template generated formatting. + /// @a args is a forwarded tuple of arguments to be processed. + template using ArgFormatterSignature = BufferWriter &(*)(BufferWriter &w, Spec 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 + BufferWriter & + Arg_Formatter(BufferWriter &w, Spec const &spec, TUPLE const &args) + { + return bwformat(w, spec, std::get(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 + /// 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 + ArgFormatterSignature * + Get_Arg_Formatter_Array(std::index_sequence) + { + static ArgFormatterSignature fa[sizeof...(N)] = {&bwf::Arg_Formatter...}; + return fa; + } + + /// Perform alignment adjustments / fill on @a w of the content in @a lw. + /// This is the normal mechanism, in cases where the length can be known or limited before + /// conversion, it can be more efficient to work in a temporary local buffer and copy out + /// as neeed without moving data in the output buffer. + void Adjust_Alignment(BufferWriter &aux, Spec const &spec); + + /// Generic integral conversion. + BufferWriter &Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t n, bool negative_p); + + /// Generic floating point conversion. + BufferWriter &Format_Float(BufferWriter &w, Spec const &spec, double n, bool negative_p); + + /* Capture support, which allows format extractors to capture arguments and consume them. + * This was built in order to support C style formatting, which needs to capture arguments + * to set the minimum width and/or the precision of other arguments. + * + * The key component is the ability to dynamically access an element of a tuple using + * @c std::any. + * + * Note: Much of this was originally in the meta support but it caused problems in use if + * the tuple header wasn't also included. I was unable to determine why, as this code doesn't + * depend on tuple explicitly. + */ + /// The signature for accessing an element of a tuple. + template using TupleAccessorSignature = std::any (*)(T const &t); + /// Template access method. + template + std::any + TupleAccessor(T const &t) + { + return std::any(&std::get(t)); + } + /// Create and return an array of specialized accessors, indexed by tuple index. + template + std::array, sizeof...(N)> & + Tuple_Accessor_Array(std::index_sequence) + { + static std::array, sizeof...(N)> accessors = {&TupleAccessor...}; + return accessors; + } + /// Get the Nth element of the tuple as @c std::any. + template + std::any + Tuple_Nth(T const &t, size_t idx) + { + return Tuple_Accessor_Array(std::make_index_sequence::value>())[idx](t); + } + /// If capture is used, the format extractor must provide a @c capture method. This isn't required + /// so make it compile time optional, but throw if the extractor sets up for capture and didn't + /// provide one. + template + auto + arg_capture(F &&f, BufferWriter &, Spec const &, std::any &&, ts::meta::CaseTag<0>) -> void + { + throw std::runtime_error("Capture specification used in format extractor that does not support capture"); + } + template + auto + arg_capture(F &&f, BufferWriter &w, Spec const &spec, std::any &&value, ts::meta::CaseTag<1>) + -> decltype(f.capture(w, spec, value)) + { + return f.capture(w, spec, value); + } + +} // namespace bwf + +/* [Need to clip this out and put it in Sphinx + * + * The format parser :arg:`F` performs parsing of the format specifier, which is presumed to be + * bound to this instance of :arg:`F`. The parser is called as a functor and must have a function + * method with the signature + * + * bool (string_view & literal_v, bwf::Spec & spec) + * + * The parser must parse out to the next specifier in the format, or the end of the format if there + * are no more specifiers. If the format is exhausted this should return @c false. A return of @c true + * indicates there is either a literal, a specifier, or both. + * + * When a literal is found, it should be returned in :arg:`literal_v`. If a specifier is found and + * parsed, it should be put in :arg:`spec`. Both of these *must* be cleared if the corresponding + * data is not found in the incremental parse of the format. + */ + +// This is the real printing logic, all other variants pack up their arguments and send them here. +template +BufferWriter & +BufferWriter::print_nv(bwf::BoundNames const &names, F &&f, std::tuple const &args) +{ + using namespace std::literals; + static constexpr int N = sizeof...(Args); // used as loop limit + static const auto fa = bwf::Get_Arg_Formatter_Array(std::index_sequence_for{}); + int arg_idx = 0; // the next argument index to be processed. + + // Parser is required to return @c false if there's no more data, @c true if something was parsed. + while (f) { + std::string_view lit_v; + bwf::Spec spec; + bool spec_p = f(lit_v, spec); + if (lit_v.size()) { + this->write(lit_v); + } + + if (spec_p) { + size_t width = this->remaining(); + if (spec._max < width) { + width = spec._max; + } + FixedBufferWriter lw{this->aux_data(), width}; + + if (spec._name.size() == 0) { + spec._idx = arg_idx++; + } + if (0 <= spec._idx) { + if (spec._idx < N) { + if (spec._type == bwf::Spec::CAPTURE_TYPE) { + bwf::arg_capture(f, lw, spec, bwf::Tuple_Nth(args, static_cast(spec._idx)), ts::meta::CaseArg); + } else { + fa[spec._idx](lw, spec, args); + } + } else { + bwf::Err_Bad_Arg_Index(lw, spec._idx, N); + } + } else if (spec._name.size()) { + names(lw, spec); + } + if (lw.extent()) { + bwf::Adjust_Alignment(lw, spec); + this->commit(lw.extent()); + } + } + } + return *this; +} + +template +BufferWriter & +BufferWriter::print(const TextView &fmt, Args &&... args) +{ + return this->print_nv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), std::forward_as_tuple(args...)); +} + +template +BufferWriter & +BufferWriter::print(bwf::Format const &fmt, Args &&... args) +{ + return this->print_nv(bwf::Global_Names.bind(), fmt.bind(), std::forward_as_tuple(args...)); +} + +template +BufferWriter & +BufferWriter::printv(TextView const &fmt, std::tuple const &args) +{ + return this->print_nv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), args); +} + +template +BufferWriter & +BufferWriter::printv(const bwf::Format &fmt, const std::tuple &args) +{ + return this->print_nv(bwf::Global_Names.bind(), fmt.bind(), args); +} + +template +BufferWriter & +BufferWriter::print_nv(const bwf::BoundNames &names, F &&f) +{ + return print_nv(names, f, std::make_tuple()); +} + +// ---- Formatting for specific types. + +// Pointers that are not specialized. +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr) +{ + bwf::Spec ptr_spec{spec}; + ptr_spec._radix_lead_p = true; + if (ptr_spec._type == bwf::Spec::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 bwf::Format_Integer(w, ptr_spec, reinterpret_cast(ptr), false); +} + +// MemSpan +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan const &span); + +// -- Common formatters -- + +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv); + +template +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, const char (&a)[N]) +{ + return bwformat(w, spec, std::string_view(a, N - 1)); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, const char *v) +{ + if (spec._type == 'x' || spec._type == 'X') { + bwformat(w, spec, static_cast(v)); + } else { + bwformat(w, spec, std::string_view(v)); + } + return w; +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, TextView tv) +{ + return bwformat(w, spec, static_cast(tv)); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, std::string const &s) +{ + return bwformat(w, spec, std::string_view{s}); +} + +template +auto +bwformat(BufferWriter &w, bwf::Spec const &spec, F &&f) -> + typename std::enable_if::type>::value, BufferWriter &>::type +{ + return f < 0 ? bwf::Format_Float(w, spec, -f, true) : bwf::Format_Float(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 +auto +bwformat(BufferWriter &w, bwf::Spec const &spec, I &&i) -> + typename std::enable_if::type>::value && + std::is_integral::type>::value, + BufferWriter &>::type +{ + return bwf::Format_Integer(w, spec, i, false); +} + +template +auto +bwformat(BufferWriter &w, bwf::Spec const &spec, I &&i) -> + typename std::enable_if::type>::value && + std::is_integral::type>::value, + BufferWriter &>::type +{ + bool neg_p = false; + uintmax_t n = static_cast(i); + if (i < 0) { + n = static_cast(-i); + neg_p = true; + } + return bwf::Format_Integer(w, spec, n, neg_p); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &, char c) +{ + return w.write(c); +} + +inline BufferWriter & +bwformat(BufferWriter &w, bwf::Spec 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 { + bwf::Format_Integer(w, spec, static_cast(f), false); + } + return w; +} + +// Generically a stream operator is a formatter with the default specification. +template +BufferWriter & +operator<<(BufferWriter &w, V &&v) +{ + return bwformat(w, bwf::Spec::DEFAULT, std::forward(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 +std::string & +bwprintv(std::string &s, TextView fmt, std::tuple const &args) +{ + auto len = s.size(); // remember initial size + size_t n = FixedBufferWriter(const_cast(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. + FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, std::move(args)); + } + return s; +} + +template +std::string & +bwprint(std::string &s, TextView fmt, Args &&... args) +{ + return bwprintv(s, fmt, std::forward_as_tuple(args...)); +} + +template +inline auto +FixedBufferWriter::print(TextView fmt, Args &&... args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); +} + +template +inline auto +FixedBufferWriter::printv(TextView fmt, std::tuple const &args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, args)); +} + +template +inline auto +FixedBufferWriter::print(bwf::Format const &fmt, Args &&... args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, std::forward_as_tuple(args...))); +} + +template +inline auto +FixedBufferWriter::printv(bwf::Format const &fmt, std::tuple const &args) -> self_type & +{ + return static_cast(this->super_type::printv(fmt, args)); +} + +// Special case support for @c Scalar, because @c Scalar is a base utility for some other utilities +// there can be some unpleasant cirularities if @c Scalar includes BufferWriter formatting. If the +// support is here then it's fine because anything using BWF for @c Scalar must include this header. +template class Scalar; +namespace detail +{ + template + auto + tag_label(BufferWriter &w, const bwf::Spec &, meta::CaseTag<0>) -> void + { + } + + template + auto + tag_label(BufferWriter &w, const bwf::Spec &, meta::CaseTag<1>) -> decltype(T::label, meta::CaseVoidFunc()) + { + w.print("{}", T::label); + } +} // namespace detail + +template +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, Scalar const &x) +{ + bwformat(w, spec, x.value()); + if (!spec.has_numeric_type()) { + detail::tag_label(w, spec, meta::CaseArg); + } + return w; +} + +} // namespace ts diff --git a/include/tscore/bwf_std_format.h b/include/tscpp/util/bwf_ex.h similarity index 86% rename from include/tscore/bwf_std_format.h rename to include/tscpp/util/bwf_ex.h index ce7f4f7080a..b2685b797f4 100644 --- a/include/tscore/bwf_std_format.h +++ b/include/tscpp/util/bwf_ex.h @@ -23,21 +23,10 @@ #pragma once -#include #include #include -#include "tscpp/util/TextView.h" -#include "tscore/BufferWriterForward.h" -namespace std -{ -template -ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, atomic const &v) -{ - return ts::bwformat(w, spec, v.load()); -} -} // end namespace std +#include "tscpp/util/bwf_base.h" namespace ts { @@ -45,6 +34,13 @@ namespace bwf { using namespace std::literals; // enable ""sv + /** Output @a text @a n times. + * + */ + struct Pattern { + int _n; ///< # of instances of @a pattern. + std::string_view _text; ///< output text. + }; /** Format wrapper for @c errno. * This stores a copy of the argument or @c errno if an argument isn't provided. The output * is then formatted with the short, long, and numeric value of @c errno. If the format specifier @@ -123,8 +119,9 @@ namespace bwf }; } // namespace bwf -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Errno const &e); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Date const &date); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::OptionalAffix const &opts); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::OptionalAffix const &opts); } // namespace ts diff --git a/include/tscpp/util/bwf_printf.h b/include/tscpp/util/bwf_printf.h new file mode 100644 index 00000000000..061a1790b75 --- /dev/null +++ b/include/tscpp/util/bwf_printf.h @@ -0,0 +1,79 @@ +/** @file + + BufferWriter formatting in snprintf style. + + @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 + +#include "tscpp/util/TextView.h" +#include "tscpp/util/bwf_base.h" + +namespace ts +{ +namespace bwf +{ + /** C / printf style formatting for BufferWriter. + * + * This is a wrapper style class, it is not for use in a persistent context. The general use pattern + * will be to pass a temporary instance in to the @c BufferWriter formatting. E.g + * + * @code + * void bwprintf(BufferWriter& w, TextView fmt, arg1, arg2, arg3, ...) { + * w.print_v(C_Format(fmt), std::forward_as_tuple(args)); + * @endcode + */ + class C_Format + { + public: + /// Construct for @a fmt. + C_Format(TextView const &fmt); + + /// Check if there is any more format to process. + explicit operator bool() const; + + /// Get the next pieces of the format. + bool operator()(std::string_view &literal, Spec &spec); + + void capture(BufferWriter &w, Spec const &spec, std::any const &value); + + protected: + TextView _fmt; + Spec _saved; // spec for which the width and/or prec is needed. + bool _saved_p{false}; // flag for having a saved _spec. + bool _prec_p{false}; // need the precision captured? + }; + + // ---- Implementation ---- + inline C_Format::C_Format(TextView const &fmt) : _fmt(fmt) {} + + inline C_Format::operator bool() const { return _saved_p || !_fmt.empty(); } + +} // namespace bwf + +template +int +bwprintf(BufferWriter &w, TextView const &fmt, Args &&... args) +{ + size_t n = w.size(); + w.print_nv(bwf::NilBoundNames(), bwf::C_Format(fmt), std::forward_as_tuple(args...)); + return static_cast(w.size() - n); +} + +} // namespace ts diff --git a/include/tscpp/util/bwf_std.h b/include/tscpp/util/bwf_std.h new file mode 100644 index 00000000000..e032befe551 --- /dev/null +++ b/include/tscpp/util/bwf_std.h @@ -0,0 +1,37 @@ +/** @file + + BufferWriter formatters for types in the std namespace. + + @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 +#include "tscpp/util/bwf_base.h" + +namespace std +{ +template +ts::BufferWriter & +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, atomic const &v) +{ + return ts::bwformat(w, spec, v.load()); +} +} // end namespace std diff --git a/iocore/eventsystem/IOBuffer.cc b/iocore/eventsystem/IOBuffer.cc index ff8658dc0a6..eb534d6b6d5 100644 --- a/iocore/eventsystem/IOBuffer.cc +++ b/iocore/eventsystem/IOBuffer.cc @@ -424,25 +424,3 @@ MIOBufferWriter::operator>>(std::ostream &stream) const } return stream; } - -ssize_t -MIOBufferWriter::operator>>(int fd) const -{ - ssize_t zret = 0; - IOBufferReader *reader = _miob->alloc_reader(); - if (reader) { - IOBufferBlock *b; - while (nullptr != (b = reader->get_current_block())) { - auto n = b->read_avail(); - auto r = ::write(fd, b->start(), n); - if (r <= 0) { - break; - } else { - reader->consume(r); - zret += r; - } - } - _miob->dealloc_reader(reader); - } - return zret; -} diff --git a/iocore/eventsystem/I_MIOBufferWriter.h b/iocore/eventsystem/I_MIOBufferWriter.h index 639f4615482..1a5a974931c 100644 --- a/iocore/eventsystem/I_MIOBufferWriter.h +++ b/iocore/eventsystem/I_MIOBufferWriter.h @@ -27,7 +27,7 @@ #include #include "tscore/ink_assert.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #if !defined(UNIT_TEST_BUFFER_WRITER) #include @@ -59,7 +59,7 @@ class MIOBufferWriter : public ts::BufferWriter } char * - auxBuffer() override + aux_data() override { IOBufferBlock *iobbPtr = _miob->first_write_block(); @@ -86,7 +86,7 @@ class MIOBufferWriter : public ts::BufferWriter // This function should not be called if no auxiliary buffer is available. // self_type & - fill(size_t n) override + commit(size_t n) override { if (n) { IOBufferBlock *iobbPtr = _miob->first_write_block(); @@ -117,11 +117,11 @@ class MIOBufferWriter : public ts::BufferWriter // Not useful in this derived class. // - self_type &clip(size_t) override { return *this; } + self_type &restrict(size_t) override { return *this; } // Not useful in this derived class. // - self_type &extend(size_t) override { return *this; } + self_type &restore(size_t) override { return *this; } // This must not be called for this derived class. // @@ -137,7 +137,6 @@ class MIOBufferWriter : public ts::BufferWriter std::ostream &operator>>(std::ostream &stream) const override; /// Output the buffer contents to the file for file descriptor @a fd. /// @return The number of bytes written. - ssize_t operator>>(int fd) const override; protected: MIOBuffer *_miob; diff --git a/iocore/eventsystem/Makefile.am b/iocore/eventsystem/Makefile.am index e1b83484485..cd884f9d350 100644 --- a/iocore/eventsystem/Makefile.am +++ b/iocore/eventsystem/Makefile.am @@ -70,8 +70,7 @@ libinkevent_a_SOURCES = \ UnixEvent.cc \ UnixEventProcessor.cc -check_PROGRAMS = test_Buffer test_Event \ - test_MIOBufferWriter +check_PROGRAMS = test_Buffer test_Event test_LD_FLAGS = \ @AM_LDFLAGS@ \ @@ -121,11 +120,7 @@ test_Buffer_LDADD = $(test_LD_ADD) test_Event_LDADD = $(test_LD_ADD) -test_MIOBufferWriter_SOURCES = unit_tests/test_MIOBufferWriter.cc -test_MIOBufferWriter_CPPFLAGS = $(test_CPP_FLAGS) -I$(abs_top_srcdir)/tests/include -test_MIOBufferWriter_LDFLAGS = $(test_LD_FLAGS) -test_MIOBufferWriter_LDADD = $(test_LD_ADD) include $(top_srcdir)/build/tidy.mk diff --git a/mgmt/LocalManager.cc b/mgmt/LocalManager.cc index 4b94d9cdc65..baa05480f59 100644 --- a/mgmt/LocalManager.cc +++ b/mgmt/LocalManager.cc @@ -36,8 +36,8 @@ #include #include #include "tscpp/util/TextView.h" -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_ex.h" #if TS_USE_POSIX_CAP #include @@ -901,7 +901,7 @@ LocalManager::startProxy(const char *onetime_options) char options_buffer[OPTIONS_SIZE]; ts::FixedBufferWriter w{options_buffer, OPTIONS_SIZE}; - w.clip(1); + w.restrict(1); w.print("{}{}", ts::bwf::OptionalAffix(proxy_options), ts::bwf::OptionalAffix(onetime_options)); // Make sure we're starting the proxy in mgmt mode @@ -936,7 +936,7 @@ LocalManager::startProxy(const char *onetime_options) } } - w.extend(1); + w.restore(1); w.write('\0'); // null terminate. Debug("lm", "[LocalManager::startProxy] Launching %s '%s'", absolute_proxy_binary, w.data()); diff --git a/proxy/IPAllow.cc b/proxy/IPAllow.cc index ed208d99b72..4e3d6243818 100644 --- a/proxy/IPAllow.cc +++ b/proxy/IPAllow.cc @@ -26,7 +26,7 @@ #include #include "IPAllow.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" extern char *readIntoBuffer(const char *file_path, const char *module_name, int *read_size_ptr); @@ -215,7 +215,7 @@ IpAllow::BuildTable() TextView src(file_buff, file_size); TextView line; auto err_prefix = [&]() -> ts::BufferWriter & { - return bw_err.reset().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file_path, line_num); + return bw_err.clear().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file_path, line_num); }; while (!(line = src.take_prefix_at('\n')).empty()) { @@ -280,7 +280,7 @@ IpAllow::BuildTable() if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) { nonstandard_methods.push_back(method_name); Debug("ip-allow", "%s", - bw_err.reset().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data()); + bw_err.clear().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data()); } else { // valid method. acl_method_mask |= ACL::MethodIdxToMask(method_idx); } diff --git a/proxy/http/ForwardedConfig.cc b/proxy/http/ForwardedConfig.cc index 09caffc8320..feec2b97b91 100644 --- a/proxy/http/ForwardedConfig.cc +++ b/proxy/http/ForwardedConfig.cc @@ -45,14 +45,14 @@ class BadOptionsErrMsg add(ts::TextView badOpt) { if (_count == 0) { - _err << "\"Forwarded\" configuration: "; + _err.write("\"Forwarded\" configuration: "); _addQuoted(badOpt); _count = 1; } else if (_count == 1) { _saveLast = badOpt; _count = 2; } else { - _err << ", "; + _err.write(", "); _addQuoted(_saveLast); _saveLast = badOpt; ++_count; @@ -69,12 +69,12 @@ class BadOptionsErrMsg } if (_count == 1) { - _err << " is a bad option."; + _err.write(" is a bad option."); } else if (_count != 0) { - _err << " and "; + _err.write(" and "); _addQuoted(_saveLast); - _err << " are bad options."; + _err.write(" are bad options."); } return true; } @@ -83,7 +83,7 @@ class BadOptionsErrMsg void _addQuoted(ts::TextView sv) { - _err << '\"' << sv << '\"'; + _err.write('\"').write(sv).write('\"'); } ts::FixedBufferWriter &_err; diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index fd61d074243..cf3b5d0fe81 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -47,7 +47,7 @@ #include "tscore/IpMap.h" #include "tscore/Regex.h" #include "string_view" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include "HttpProxyAPIEnums.h" #include "ProxyConfig.h" #include "records/P_RecProcess.h" diff --git a/proxy/http/HttpConnectionCount.cc b/proxy/http/HttpConnectionCount.cc index 7eae991ffa4..852952a2f86 100644 --- a/proxy/http/HttpConnectionCount.cc +++ b/proxy/http/HttpConnectionCount.cc @@ -24,8 +24,8 @@ #include #include #include "HttpConnectionCount.h" -#include "tscore/bwf_std_format.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_ex.h" +#include "tscpp/util/BufferWriter.h" using namespace std::literals; @@ -262,9 +262,9 @@ OutboundConnTrack::to_json_string() { std::string text; size_t extent = 0; - static const ts::BWFormat header_fmt{R"({{"count": {}, "list": [ + static const ts::bwf::Format header_fmt{R"({{"count": {}, "list": [ )"}; - static const ts::BWFormat item_fmt{ + static const ts::bwf::Format item_fmt{ R"( {{"type": "{}", "ip": "{}", "fqdn": "{}", "current": {}, "max": {}, "blocked": {}, "queued": {}, "alert": {}}}, )"}; static const std::string_view trailer{" \n]}"}; @@ -288,12 +288,12 @@ OutboundConnTrack::to_json_string() text.resize(extent); ts::FixedBufferWriter w(const_cast(text.data()), text.size()); - w.clip(trailer.size()); + w.restrict(trailer.size()); w.print(header_fmt, groups.size()); for (auto g : groups) { printer(w, g); } - w.extend(trailer.size()); + w.restore(trailer.size()); w.write(trailer); return text; } @@ -363,7 +363,7 @@ OutboundConnTrack::Warning_Bad_Match_Type(std::string_view tag) w.write(n); w.write("',"sv); } - w.auxBuffer()[-1] = '\0'; // clip trailing comma and null terminate. + w.aux_data()[-1] = '\0'; // clip trailing comma and null terminate. Warning("%s", w.data()); } @@ -407,7 +407,18 @@ OutboundConnTrack::TxnState::Warn_Blocked(TxnConfig *config, int64_t sm_id, int namespace ts { BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group::Key const &key) +bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::MatchType type) +{ + if (spec.has_numeric_type()) { + bwformat(w, spec, static_cast(type)); + } else { + bwformat(w, spec, OutboundConnTrack::MATCH_TYPE_NAME[type]); + } + return w; +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group::Key const &key) { switch (key._match_type) { case OutboundConnTrack::MATCH_BOTH: @@ -427,7 +438,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group::Key con } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group const &g) +bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group const &g) { switch (g._match_type) { case OutboundConnTrack::MATCH_BOTH: diff --git a/proxy/http/HttpConnectionCount.h b/proxy/http/HttpConnectionCount.h index 291e12acc56..9f6b189070e 100644 --- a/proxy/http/HttpConnectionCount.h +++ b/proxy/http/HttpConnectionCount.h @@ -33,10 +33,10 @@ #include "tscore/ink_config.h" #include "tscore/ink_mutex.h" #include "tscore/ink_inet.h" -#include "tscore/IntrusiveHashMap.h" +#include "tscpp/util/IntrusiveHashMap.h" #include "tscore/Diags.h" #include "tscore/CryptoHash.h" -#include "tscore/BufferWriterForward.h" +#include "tscpp/util/BufferWriter.h" #include "tscpp/util/TextView.h" #include #include "HttpProxyAPIEnums.h" @@ -270,8 +270,8 @@ class OutboundConnTrack /// Internal implementation class instance. struct Imp { - IntrusiveHashMap _table; ///< Hash table of upstream groups. - std::mutex _mutex; ///< Lock for insert & find. + ts::IntrusiveHashMap _table; ///< Hash table of upstream groups. + std::mutex _mutex; ///< Lock for insert & find. }; static Imp _imp; @@ -430,17 +430,7 @@ Action *register_ShowConnectionCount(Continuation *, HTTPHdr *); namespace ts { -inline BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::MatchType type) -{ - if (spec.has_numeric_type()) { - bwformat(w, spec, static_cast(type)); - } else { - bwformat(w, spec, OutboundConnTrack::MATCH_TYPE_NAME[type]); - } - return w; -} - -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group::Key const &key); -BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group const &g); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::MatchType type); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group::Key const &key); +BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, OutboundConnTrack::Group const &g); } // namespace ts diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc index b18f1fd99d9..23622ba789e 100644 --- a/proxy/http/HttpDebugNames.cc +++ b/proxy/http/HttpDebugNames.cc @@ -28,6 +28,7 @@ #include "Transform.h" #include "HttpSM.h" #include "HttpUpdateSM.h" +#include "tscpp/util/bwf_base.h" //---------------------------------------------------------------------------- const char * @@ -480,3 +481,43 @@ HttpDebugNames::get_api_hook_name(TSHttpHookID t) return "unknown hook"; } + +ts::BufferWriter & +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::ServerState_t state) +{ + if (spec.has_numeric_type()) { + return bwformat(w, spec, static_cast(state)); + } else { + return bwformat(w, spec, HttpDebugNames::get_server_state_name(state)); + } +} + +ts::BufferWriter & +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::CacheAction_t state) +{ + if (spec.has_numeric_type()) { + return bwformat(w, spec, static_cast(state)); + } else { + return bwformat(w, spec, HttpDebugNames::get_cache_action_name(state)); + } +} + +ts::BufferWriter & +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::StateMachineAction_t state) +{ + if (spec.has_numeric_type()) { + return bwformat(w, spec, static_cast(state)); + } else { + return bwformat(w, spec, HttpDebugNames::get_action_name(state)); + } +} + +ts::BufferWriter & +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, TSHttpHookID id) +{ + if (spec.has_numeric_type()) { + return bwformat(w, spec, static_cast(id)); + } else { + return bwformat(w, spec, HttpDebugNames::get_api_hook_name(id)); + } +} diff --git a/proxy/http/HttpDebugNames.h b/proxy/http/HttpDebugNames.h index d94f1418e04..7631f5c4465 100644 --- a/proxy/http/HttpDebugNames.h +++ b/proxy/http/HttpDebugNames.h @@ -24,7 +24,7 @@ #pragma once #include "HttpTransact.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" class HttpDebugNames { @@ -37,42 +37,7 @@ class HttpDebugNames static const char *get_server_state_name(HttpTransact::ServerState_t state); }; -inline ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::ServerState_t state) -{ - if (spec.has_numeric_type()) { - return bwformat(w, spec, static_cast(state)); - } else { - return bwformat(w, spec, HttpDebugNames::get_server_state_name(state)); - } -} - -inline ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::CacheAction_t state) -{ - if (spec.has_numeric_type()) { - return bwformat(w, spec, static_cast(state)); - } else { - return bwformat(w, spec, HttpDebugNames::get_cache_action_name(state)); - } -} - -inline ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::StateMachineAction_t state) -{ - if (spec.has_numeric_type()) { - return bwformat(w, spec, static_cast(state)); - } else { - return bwformat(w, spec, HttpDebugNames::get_action_name(state)); - } -} - -inline ts::BufferWriter & -bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, TSHttpHookID id) -{ - if (spec.has_numeric_type()) { - return bwformat(w, spec, static_cast(id)); - } else { - return bwformat(w, spec, HttpDebugNames::get_api_hook_name(id)); - } -} +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::ServerState_t state); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::CacheAction_t state); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, HttpTransact::StateMachineAction_t state); +ts::BufferWriter &bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, TSHttpHookID id); diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc index a342e276f31..487aae5f248 100644 --- a/proxy/http/HttpProxyServerMain.cc +++ b/proxy/http/HttpProxyServerMain.cc @@ -39,6 +39,7 @@ #include "http2/Http2SessionAccept.h" #include "HttpConnectionCount.h" #include "HttpProxyServerMain.h" +#include "tscpp/util/bwf_base.h" #include @@ -58,22 +59,22 @@ extern int num_accept_threads; /// Global BufferWriter format name functions. namespace { -void -TS_bwf_thread(ts::BufferWriter &w, ts::BWFSpec const &spec) +ts::BufferWriter & +TS_bwf_thread(ts::BufferWriter &w, ts::bwf::Spec const &spec) { - bwformat(w, spec, this_thread()); + return bwformat(w, spec, this_thread()); } -void -TS_bwf_ethread(ts::BufferWriter &w, ts::BWFSpec const &spec) +ts::BufferWriter & +TS_bwf_ethread(ts::BufferWriter &w, ts::bwf::Spec const &spec) { - bwformat(w, spec, this_ethread()); + return bwformat(w, spec, this_ethread()); } } // namespace // File / process scope initializations static bool HTTP_SERVER_INITIALIZED __attribute__((unused)) = []() -> bool { - ts::bwf_register_global("ts-thread", &TS_bwf_thread); - ts::bwf_register_global("ts-ethread", &TS_bwf_ethread); + ts::bwf::Global_Names.assign("ts-thread", &TS_bwf_thread); + ts::bwf::Global_Names.assign("ts-ethread", &TS_bwf_ethread); return true; }(); diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index d339068a7da..e70fb05ba20 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -43,7 +43,7 @@ #include "HttpPages.h" #include "IPAllow.h" #include "tscore/I_Layout.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/bwf_ex.h" #include #include diff --git a/proxy/http/HttpServerSession.cc b/proxy/http/HttpServerSession.cc index cadbcc0e085..8988c454f8c 100644 --- a/proxy/http/HttpServerSession.cc +++ b/proxy/http/HttpServerSession.cc @@ -29,8 +29,8 @@ ****************************************************************************/ #include "tscore/ink_config.h" -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_std.h" #include "tscore/Allocator.h" #include "HttpServerSession.h" #include "HttpSessionManager.h" diff --git a/proxy/http/HttpSessionManager.h b/proxy/http/HttpSessionManager.h index f83196aad0e..594ca2051f2 100644 --- a/proxy/http/HttpSessionManager.h +++ b/proxy/http/HttpSessionManager.h @@ -34,7 +34,7 @@ #include "P_EventSystem.h" #include "HttpServerSession.h" -#include "tscore/IntrusiveHashMap.h" +#include "tscpp/util/IntrusiveHashMap.h" class ProxyClientTransaction; class HttpSM; @@ -67,8 +67,8 @@ class ServerSessionPool : public Continuation static bool validate_sni(HttpSM *sm, NetVConnection *netvc); protected: - using IPTable = IntrusiveHashMap; - using FQDNTable = IntrusiveHashMap; + using IPTable = ts::IntrusiveHashMap; + using FQDNTable = ts::IntrusiveHashMap; public: /** Check if a session matches address and host name. diff --git a/proxy/http/HttpTransactHeaders.cc b/proxy/http/HttpTransactHeaders.cc index 95f662626c7..bf326f0c317 100644 --- a/proxy/http/HttpTransactHeaders.cc +++ b/proxy/http/HttpTransactHeaders.cc @@ -27,7 +27,7 @@ #include #include "tscore/ink_platform.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include "HttpTransact.h" #include "HttpTransactHeaders.h" @@ -1026,80 +1026,80 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP if (optSet[HttpForwarded::FOR] and ats_is_ip(&s->client_info.src_addr.sa)) { // NOTE: The logic within this if statement assumes that hdr is empty at this point. - hdr << "for="; + hdr.write("for="); bool is_ipv6 = ats_is_ip6(&s->client_info.src_addr.sa); if (is_ipv6) { - hdr << "\"["; + hdr.write("\"["); } - if (ats_ip_ntop(&s->client_info.src_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) { + if (ats_ip_ntop(&s->client_info.src_addr.sa, hdr.aux_data(), hdr.remaining()) == nullptr) { Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed"); return; } // Fail-safe. - hdr.auxBuffer()[hdr.remaining() - 1] = '\0'; + hdr.aux_data()[hdr.remaining() - 1] = '\0'; - hdr.fill(strlen(hdr.auxBuffer())); + hdr.commit(strlen(hdr.aux_data())); if (is_ipv6) { - hdr << "]\""; + hdr.write("]\""); } } if (optSet[HttpForwarded::BY_UNKNOWN]) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by=unknown"; + hdr.write("by=unknown"); } if (optSet[HttpForwarded::BY_SERVER_NAME]) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by=" << s->http_config_param->proxy_hostname; + hdr.write("by=").write(s->http_config_param->proxy_hostname); } const Machine &m = *Machine::instance(); if (optSet[HttpForwarded::BY_UUID] and m.uuid.valid()) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by=_" << m.uuid.getString(); + hdr.write("by=_").write(m.uuid.getString()); } if (optSet[HttpForwarded::BY_IP] and (m.ip_string_len > 0)) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "by="; + hdr.write("by="); bool is_ipv6 = ats_is_ip6(&s->client_info.dst_addr.sa); if (is_ipv6) { - hdr << "\"["; + hdr.write("\"["); } - if (ats_ip_ntop(&s->client_info.dst_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) { + if (ats_ip_ntop(&s->client_info.dst_addr.sa, hdr.aux_data(), hdr.remaining()) == nullptr) { Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed"); return; } // Fail-safe. - hdr.auxBuffer()[hdr.remaining() - 1] = '\0'; + hdr.aux_data()[hdr.remaining() - 1] = '\0'; - hdr.fill(strlen(hdr.auxBuffer())); + hdr.commit(strlen(hdr.aux_data())); if (is_ipv6) { - hdr << "]\""; + hdr.write("]\""); } } @@ -1118,15 +1118,15 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP if (optSet[HttpForwarded::PROTO] and (n_proto > 0)) { if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "proto="; + hdr.write("proto="); - int numChars = HttpTransactHeaders::write_hdr_protocol_stack(hdr.auxBuffer(), hdr.remaining(), ProtocolStackDetail::Compact, + int numChars = HttpTransactHeaders::write_hdr_protocol_stack(hdr.aux_data(), hdr.remaining(), ProtocolStackDetail::Compact, protoBuf.data(), n_proto, '-'); if (numChars > 0) { - hdr.fill(size_t(numChars)); + hdr.commit(size_t(numChars)); } } @@ -1139,41 +1139,35 @@ HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP bool needsDoubleQuotes = hSV.find(':') != std::string_view::npos; if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "host="; + hdr.write("host="); if (needsDoubleQuotes) { - hdr << '"'; + hdr.write('"'); } - hdr << hSV; + hdr.write(hSV); if (needsDoubleQuotes) { - hdr << '"'; + hdr.write('"'); } } } if (n_proto > 0) { auto Conn = [&](HttpForwarded::Option opt, HttpTransactHeaders::ProtocolStackDetail detail) -> void { - if (optSet[opt]) { - int revert = hdr.size(); + if (optSet[opt] && hdr.remaining() > 0) { + ts::FixedBufferWriter lw{hdr.aux_data(), hdr.remaining()}; if (hdr.size()) { - hdr << ';'; + hdr.write(';'); } - hdr << "connection="; + hdr.write("connection="); int numChars = - HttpTransactHeaders::write_hdr_protocol_stack(hdr.auxBuffer(), hdr.remaining(), detail, protoBuf.data(), n_proto, '-'); - if (numChars > 0) { - hdr.fill(size_t(numChars)); - } - - if ((numChars <= 0) or (hdr.size() >= hdr.capacity())) { - // Remove parameter with potentially incomplete value. - // - hdr.reduce(revert); + HttpTransactHeaders::write_hdr_protocol_stack(lw.aux_data(), lw.remaining(), detail, protoBuf.data(), n_proto, '-'); + if (numChars > 0 && !lw.commit(size_t(numChars)).error()) { + hdr.commit(lw.size()); } } }; diff --git a/proxy/http/unit_tests/test_ForwardedConfig.cc b/proxy/http/unit_tests/test_ForwardedConfig.cc index 7835648ff6c..dbb593dee20 100644 --- a/proxy/http/unit_tests/test_ForwardedConfig.cc +++ b/proxy/http/unit_tests/test_ForwardedConfig.cc @@ -97,7 +97,7 @@ test(const char *spec, const char *reqErr, OptionBitSet bS) { ts::LocalBufferWriter<1024> error; - error << "cheese"; + error.write("cheese"); REQUIRE(bS == optStrToBitset(XS(spec), error)); std::size_t len = std::strlen(reqErr); diff --git a/src/traffic_cache_tool/CacheDefs.cc b/src/traffic_cache_tool/CacheDefs.cc index 642f7c2f773..bcafe6d5ac7 100644 --- a/src/traffic_cache_tool/CacheDefs.cc +++ b/src/traffic_cache_tool/CacheDefs.cc @@ -22,8 +22,10 @@ */ #include "CacheDefs.h" +#include #include #include +#include "tscpp/util/bwf_base.h" using namespace std; using namespace ts; @@ -415,13 +417,13 @@ dir_compare_tag(const CacheDirEntry *e, const CryptoHash *key) return (dir_tag(e) == DIR_MASK_TAG(key->slice32(2))); } -TS_INLINE int +int vol_in_phase_valid(Stripe *d, CacheDirEntry *e) { return (dir_offset(e) - 1 < ((d->_meta[0][0].write_pos + d->agg_buf_pos - d->_start) / CACHE_BLOCK_SIZE)); } -TS_INLINE int +int vol_out_of_phase_valid(Stripe *d, CacheDirEntry *e) { return (dir_offset(e) - 1 >= ((d->_meta[0][0].agg_pos - d->_start) / CACHE_BLOCK_SIZE)); diff --git a/src/traffic_cache_tool/CacheDefs.h b/src/traffic_cache_tool/CacheDefs.h index 40695b11261..1dfea3c420b 100644 --- a/src/traffic_cache_tool/CacheDefs.h +++ b/src/traffic_cache_tool/CacheDefs.h @@ -25,7 +25,7 @@ #include #include #include "tscore/I_Version.h" -#include "tscore/Scalar.h" +#include "tscpp/util/Scalar.h" #include "tscore/Regex.h" #include #include "tscpp/util/TextView.h" @@ -400,7 +400,7 @@ namespace ct #define dir_in_seg(_s, _i) ((CacheDirEntry *)(((char *)(_s)) + (SIZEOF_DIR * (_i)))) -TS_INLINE CacheDirEntry * +inline CacheDirEntry * dir_from_offset(int64_t i, CacheDirEntry *seg) { #if DIR_DEPTH < 5 @@ -413,26 +413,26 @@ dir_from_offset(int64_t i, CacheDirEntry *seg) #endif } -TS_INLINE CacheDirEntry * +inline CacheDirEntry * dir_bucket(int64_t b, CacheDirEntry *seg) { return dir_in_seg(seg, b * DIR_DEPTH); } -TS_INLINE CacheDirEntry * +inline CacheDirEntry * next_dir(CacheDirEntry *d, CacheDirEntry *seg) { int i = dir_next(d); return dir_from_offset(i, seg); } -TS_INLINE CacheDirEntry * +inline CacheDirEntry * dir_bucket_row(CacheDirEntry *b, int64_t i) { return dir_in_seg(b, i); } -TS_INLINE int64_t +inline int64_t dir_to_offset(const CacheDirEntry *d, const CacheDirEntry *seg) { #if DIR_DEPTH < 5 @@ -555,12 +555,12 @@ struct Stripe { // This is because the freelist is not being copied to _metap[2][2] correctly. // need to do something about it .. hmmm :-? int dir_freelist_length(int s); - TS_INLINE CacheDirEntry * + inline CacheDirEntry * vol_dir_segment(int s) { return (CacheDirEntry *)(((char *)this->dir) + (s * this->_buckets) * DIR_DEPTH * SIZEOF_DIR); } - TS_INLINE CacheDirEntry * + inline CacheDirEntry * dir_segment(int s) { return vol_dir_segment(s); @@ -568,7 +568,7 @@ struct Stripe { Bytes stripe_offset(CacheDirEntry *e); // offset w.r.t the stripe content size_t vol_dirlen(); - TS_INLINE int + inline int vol_headerlen() { return ROUND_TO_STORE_BLOCK(sizeof(StripeMeta) + sizeof(uint16_t) * (this->_segments - 1)); diff --git a/src/traffic_cache_tool/CacheScan.cc b/src/traffic_cache_tool/CacheScan.cc index 06be5355217..023ddebdc5a 100644 --- a/src/traffic_cache_tool/CacheScan.cc +++ b/src/traffic_cache_tool/CacheScan.cc @@ -26,6 +26,7 @@ #include "../../proxy/hdrs/HdrHeap.h" #include "../../proxy/hdrs/MIME.h" #include "../../proxy/hdrs/URL.h" +#include "tscpp/util/bwf_base.h" // using namespace ct; diff --git a/src/traffic_cache_tool/CacheTool.cc b/src/traffic_cache_tool/CacheTool.cc index f16a1e86a6e..12b5176f373 100644 --- a/src/traffic_cache_tool/CacheTool.cc +++ b/src/traffic_cache_tool/CacheTool.cc @@ -38,7 +38,7 @@ #include "tscore/ink_memory.h" #include "tscore/ink_file.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" #include "tscore/CryptoHash.h" #include "tscore/ArgParser.h" #include diff --git a/src/traffic_cache_tool/Makefile.inc b/src/traffic_cache_tool/Makefile.inc index 82063805082..8cc7d16f5bd 100644 --- a/src/traffic_cache_tool/Makefile.inc +++ b/src/traffic_cache_tool/Makefile.inc @@ -41,7 +41,8 @@ traffic_cache_tool_traffic_cache_tool_LDADD = \ $(top_builddir)/src/tscore/.libs/ink_memory.o \ $(top_builddir)/src/tscore/.libs/ink_mutex.o \ $(top_builddir)/src/tscore/.libs/ink_string.o \ - $(top_builddir)/src/tscore/.libs/BufferWriterFormat.o \ + $(top_builddir)/src/tscpp/util/.libs/MemArena.o \ + $(top_builddir)/src/tscpp/util/.libs/bw_format.o \ $(top_builddir)/src/tscore/.libs/ts_file.o \ $(top_builddir)/src/tscpp/util/.libs/TextView.o \ $(top_builddir)/lib/tsconfig/.libs/Errata.o \ diff --git a/src/traffic_manager/traffic_manager.cc b/src/traffic_manager/traffic_manager.cc index 87be590793b..9e8822eb8ab 100644 --- a/src/traffic_manager/traffic_manager.cc +++ b/src/traffic_manager/traffic_manager.cc @@ -56,7 +56,7 @@ #endif #include #include -#include "tscore/bwf_std_format.h" +#include "tscpp/util/bwf_ex.h" #define FD_THROTTLE_HEADROOM (128 + 64) // TODO: consolidate with THROTTLE_FD_HEADROOM #define DIAGS_LOG_FILENAME "manager.log" diff --git a/src/tscore/BufferWriterFormat.cc b/src/tscore/BufferWriterFormat.cc deleted file mode 100644 index d5152e6f24c..00000000000 --- a/src/tscore/BufferWriterFormat.cc +++ /dev/null @@ -1,1023 +0,0 @@ -/** @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 "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::literals; - -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->assign(src.data(), start); - } - } - return zret; -} -} // namespace - -namespace ts -{ -const BWFSpec BWFSpec::DEFAULT; - -const BWFSpec::Property BWFSpec::_prop; - -#pragma GCC diagnostic ignored "-Wchar-subscripts" -BWFSpec::Property::Property() -{ - memset(_data, 0, sizeof(_data)); - _data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; - _data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['g'] = TYPE_CHAR; - _data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['p'] = TYPE_CHAR; - _data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR; - _data['s'] = TYPE_CHAR; - _data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR; - _data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; - _data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; - - _data[' '] = SIGN_CHAR; - _data['-'] = SIGN_CHAR; - _data['+'] = SIGN_CHAR; - - _data['<'] = static_cast(BWFSpec::Align::LEFT); - _data['>'] = static_cast(BWFSpec::Align::RIGHT); - _data['^'] = static_cast(BWFSpec::Align::CENTER); - _data['='] = static_cast(BWFSpec::Align::SIGN); -} - -/// Parse a format specification. -BWFSpec::BWFSpec(TextView fmt) -{ - TextView num; // temporary for number parsing. - 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(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(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(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(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 extent = lw.extent(); - size_t min = spec._min; - size_t size = lw.size(); - if (extent < min) { - size_t delta = min - extent; - char *base = w.auxBuffer(); // should be first byte of @a lw e.g. lw.data() - avoid const_cast. - char *limit = base + lw.capacity(); // first invalid byte. - char *dst; // used to track memory operation targest; - char *last; // track limit of memory operation. - size_t d2; - switch (spec._align) { - case BWFSpec::Align::RIGHT: - dst = base + delta; // move existing content to here. - if (dst < limit) { - last = dst + size; // amount of data to move. - if (last > limit) { - last = limit; - } - std::memmove(dst, base, last - dst); - } - dst = base; - last = base + delta; - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - break; - case BWFSpec::Align::CENTER: - d2 = (delta + 1) / 2; // always > 0 because min > extent - // Move the original content right to make space to fill on the left. - dst = base + d2; // move existing content to here. - if (dst < limit) { - last = dst + size; // amount of data to move. - if (last > limit) { - last = limit; - } - std::memmove(dst, base, last - dst); // move content. - } - // Left fill. - dst = base; - last = base + d2; - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - // Right fill. - dst += size; - last = dst + delta / 2; // round down - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - break; - default: - // Everything else is equivalent to LEFT - distinction is for more specialized - // types such as integers. - dst = base + size; - last = dst + delta; - if (last > limit) { - last = limit; - } - while (dst < last) { - *dst++ = spec._fill; - } - break; - } - w.fill(min); - } else { - size_t max = spec._max; - if (max < extent) { - extent = max; - } - w.fill(extent); - } - } - - // 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"; - static const std::array POWERS_OF_TEN = { - {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}}; - } // namespace - - /// 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 - 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; - } - - template - void - Write_Aligned(BufferWriter &w, F const &f, BWFSpec::Align align, int width, char fill, char neg) - { - switch (align) { - case BWFSpec::Align::LEFT: - if (neg) { - w.write(neg); - } - f(); - while (width-- > 0) { - w.write(fill); - } - break; - case BWFSpec::Align::RIGHT: - while (width-- > 0) { - w.write(fill); - } - if (neg) { - w.write(neg); - } - f(); - break; - case BWFSpec::Align::CENTER: - for (int i = width / 2; i > 0; --i) { - w.write(fill); - } - if (neg) { - w.write(neg); - } - f(); - for (int i = (width + 1) / 2; i > 0; --i) { - w.write(fill); - } - break; - case BWFSpec::Align::SIGN: - if (neg) { - w.write(neg); - } - while (width-- > 0) { - w.write(fill); - } - f(); - break; - default: - if (neg) { - w.write(neg); - } - f(); - break; - } - } - - BufferWriter & - Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t i, bool neg_p) - { - size_t n = 0; - int width = static_cast(spec._min); // amount left to fill. - char neg = 0; - char prefix1 = spec._radix_lead_p ? '0' : 0; - char prefix2 = 0; - char buff[std::numeric_limits::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(n); - std::string_view digits{buff + sizeof(buff) - n, n}; - - if (spec._align == BWFSpec::Align::SIGN) { // custom for signed case because prefix and digits are seperated. - 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); - } else { // use generic Write_Aligned - Write_Aligned(w, - [&]() { - if (prefix1) { - w.write(prefix1); - if (prefix2) { - w.write(prefix2); - } - } - w.write(digits); - }, - spec._align, width, spec._fill, neg); - } - return w; - } - - /// Format for floating point values. Seperates floating point into a whole number and a - /// fraction. The fraction is converted into an unsigned integer based on the specified - /// precision, spec._prec. ie. 3.1415 with precision two is seperated into two unsigned - /// integers 3 and 14. The different pieces are assembled and placed into the BufferWriter. - /// The default is two decimal places. ie. X.XX. The value is always written in base 10. - /// - /// format: whole.fraction - /// or: left.right - BufferWriter & - Format_Floating(BufferWriter &w, BWFSpec const &spec, double f, bool neg_p) - { - static const std::string_view infinity_bwf{"Inf"}; - static const std::string_view nan_bwf{"NaN"}; - static const std::string_view zero_bwf{"0"}; - static const std::string_view subnormal_bwf{"subnormal"}; - static const std::string_view unknown_bwf{"unknown float"}; - - // Handle floating values that are not normal - if (!std::isnormal(f)) { - std::string_view unnormal; - switch (std::fpclassify(f)) { - case FP_INFINITE: - unnormal = infinity_bwf; - break; - case FP_NAN: - unnormal = nan_bwf; - break; - case FP_ZERO: - unnormal = zero_bwf; - break; - case FP_SUBNORMAL: - unnormal = subnormal_bwf; - break; - default: - unnormal = unknown_bwf; - } - - w.write(unnormal); - return w; - } - - uint64_t whole_part = static_cast(f); - if (whole_part == f || spec._prec == 0) { // integral - return Format_Integer(w, spec, whole_part, neg_p); - } - - static constexpr char dec = '.'; - double frac; - size_t l = 0; - size_t r = 0; - char whole[std::numeric_limits::digits10 + 1]; - char fraction[std::numeric_limits::digits10 + 1]; - char neg = 0; - int width = static_cast(spec._min); // amount left to fill. - unsigned int precision = (spec._prec == BWFSpec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2 - - frac = f - whole_part; // split the number - - if (neg_p) { - neg = '-'; - } else if (spec._sign != '-') { - neg = spec._sign; - } - - // Shift the floating point based on the precision. Used to convert - // trailing fraction into an integer value. - uint64_t shift; - if (precision < POWERS_OF_TEN.size()) { - shift = POWERS_OF_TEN[precision]; - } else { // not precomputed. - shift = POWERS_OF_TEN.back(); - for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) { - shift *= 10; - } - } - - uint64_t frac_part = static_cast(frac * shift + 0.5 /* rounding */); - - l = bw_fmt::To_Radix<10>(whole_part, whole, sizeof(whole), bw_fmt::LOWER_DIGITS); - r = bw_fmt::To_Radix<10>(frac_part, fraction, sizeof(fraction), bw_fmt::LOWER_DIGITS); - - // Clip fill width - if (neg) { - --width; - } - width -= static_cast(l); - --width; // '.' - width -= static_cast(r); - - std::string_view whole_digits{whole + sizeof(whole) - l, l}; - std::string_view frac_digits{fraction + sizeof(fraction) - r, r}; - - Write_Aligned(w, - [&]() { - w.write(whole_digits); - w.write(dec); - w.write(frac_digits); - }, - spec._align, width, spec._fill, neg); - - return w; - } - - /// Write out the @a data as hexadecimal, using @a digits as the conversion. - void - Hex_Dump(BufferWriter &w, std::string_view data, const char *digits) - { - const char *ptr = data.data(); - for (auto n = data.size(); n > 0; --n) { - char c = *ptr++; - w.write(digits[(c >> 4) & 0xF]); - w.write(digits[c & 0xf]); - } - } - -} // namespace bw_fmt - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv) -{ - int width = static_cast(spec._min); // amount left to fill. - if (spec._prec > 0) { - sv.remove_prefix(spec._prec); - } - - if ('x' == spec._type || 'X' == spec._type) { - const char *digits = 'x' == spec._type ? bw_fmt::LOWER_DIGITS : bw_fmt::UPPER_DIGITS; - width -= sv.size() * 2; - if (spec._radix_lead_p) { - w.write('0'); - w.write(spec._type); - width -= 2; - } - bw_fmt::Write_Aligned(w, [&w, &sv, digits]() { bw_fmt::Hex_Dump(w, sv, digits); }, spec._align, width, spec._fill, 0); - } else { - width -= sv.size(); - bw_fmt::Write_Aligned(w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0); - } - return w; -} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span) -{ - static const BWFormat default_fmt{"{:#x}@{:p}"}; - if (spec._ext.size() && 'd' == spec._ext.front()) { - const char *digits = 'X' == spec._type ? bw_fmt::UPPER_DIGITS : bw_fmt::LOWER_DIGITS; - if (spec._radix_lead_p) { - w.write('0'); - w.write(digits[33]); - } - bw_fmt::Hex_Dump(w, span.view(), digits); - } else { - w.print(default_fmt, span.size(), span.data()); - } - return w; -} - -/// Preparse format string for later use. -BWFormat::BWFormat(ts::TextView fmt) -{ - BWFSpec lit_spec{BWFSpec::DEFAULT}; - int arg_idx = 0; - - while (fmt) { - std::string_view lit_str; - std::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) { // no name provided, use implicit index. - parsed_spec._idx = arg_idx; - } - if (parsed_spec._idx < 0) { // name wasn't missing or a valid index, assume global name. - gf = bw_fmt::Global_Table_Find(parsed_spec._name); - } else { - ++arg_idx; // bump this if not a global name. - } - _items.emplace_back(parsed_spec, gf); - } - } -} - -BWFormat::~BWFormat() {} - -/// Parse out the next literal and/or format specifier from the format string. -/// Pass the results back in @a literal and @a specifier as appropriate. -/// Update @a fmt to strip the parsed text. -bool -BWFormat::parse(ts::TextView &fmt, std::string_view &literal, std::string_view &specifier) -{ - TextView::size_type off; - - // Check for brace delimiters. - off = fmt.find_if([](char c) { return '{' == c || '}' == c; }); - if (off == TextView::npos) { - // not found, it's a literal, ship it. - literal = fmt; - fmt.remove_prefix(literal.size()); - return false; - } - - // Processing for braces that don't enclose specifiers. - if (fmt.size() > off + 1) { - char c1 = fmt[off]; - char c2 = fmt[off + 1]; - if (c1 == c2) { - // double braces count as literals, but must tweak to out only 1 brace. - literal = fmt.take_prefix_at(off + 1); - return false; - } else if ('}' == c1) { - throw std::invalid_argument("BWFormat:: Unopened } in format string."); - } else { - literal = std::string_view{fmt.data(), off}; - fmt.remove_prefix(off + 1); - } - } else { - throw std::invalid_argument("BWFormat: Invalid trailing character in format string."); - } - - 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("BWFormat: Unclosed { in format string"); - } - 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(std::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; -} - -std::ostream & -FixedBufferWriter::operator>>(std::ostream &s) const -{ - return s << this->view(); -} - -ssize_t -FixedBufferWriter::operator>>(int fd) const -{ - return ::write(fd, this->data(), this->size()); -} - -bool -bwf_register_global(std::string_view name, BWGlobalNameSignature formatter) -{ - return ts::bw_fmt::BWF_GLOBAL_TABLE.emplace(name, formatter).second; -} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Errno const &e) -{ - // Hand rolled, might not be totally compliant everywhere, but probably close enough. - // The long string will be locally accurate. - // Clang requires the double braces. Why, Turing only knows. - static const std::array SHORT_NAME = {{ - "SUCCESS: ", - "EPERM: ", - "ENOENT: ", - "ESRCH: ", - "EINTR: ", - "EIO: ", - "ENXIO: ", - "E2BIG ", - "ENOEXEC: ", - "EBADF: ", - "ECHILD: ", - "EAGAIN: ", - "ENOMEM: ", - "EACCES: ", - "EFAULT: ", - "ENOTBLK: ", - "EBUSY: ", - "EEXIST: ", - "EXDEV: ", - "ENODEV: ", - "ENOTDIR: ", - "EISDIR: ", - "EINVAL: ", - "ENFILE: ", - "EMFILE: ", - "ENOTTY: ", - "ETXTBSY: ", - "EFBIG: ", - "ENOSPC: ", - "ESPIPE: ", - "EROFS: ", - "EMLINK: ", - "EPIPE: ", - "EDOM: ", - "ERANGE: ", - "EDEADLK: ", - "ENAMETOOLONG: ", - "ENOLCK: ", - "ENOSYS: ", - "ENOTEMPTY: ", - "ELOOP: ", - "EWOULDBLOCK: ", - "ENOMSG: ", - "EIDRM: ", - "ECHRNG: ", - "EL2NSYNC: ", - "EL3HLT: ", - "EL3RST: ", - "ELNRNG: ", - "EUNATCH: ", - "ENOCSI: ", - "EL2HTL: ", - "EBADE: ", - "EBADR: ", - "EXFULL: ", - "ENOANO: ", - "EBADRQC: ", - "EBADSLT: ", - "EDEADLOCK: ", - "EBFONT: ", - "ENOSTR: ", - "ENODATA: ", - "ETIME: ", - "ENOSR: ", - "ENONET: ", - "ENOPKG: ", - "EREMOTE: ", - "ENOLINK: ", - "EADV: ", - "ESRMNT: ", - "ECOMM: ", - "EPROTO: ", - "EMULTIHOP: ", - "EDOTDOT: ", - "EBADMSG: ", - "EOVERFLOW: ", - "ENOTUNIQ: ", - "EBADFD: ", - "EREMCHG: ", - "ELIBACC: ", - "ELIBBAD: ", - "ELIBSCN: ", - "ELIBMAX: ", - "ELIBEXEC: ", - "EILSEQ: ", - "ERESTART: ", - "ESTRPIPE: ", - "EUSERS: ", - "ENOTSOCK: ", - "EDESTADDRREQ: ", - "EMSGSIZE: ", - "EPROTOTYPE: ", - "ENOPROTOOPT: ", - "EPROTONOSUPPORT: ", - "ESOCKTNOSUPPORT: ", - "EOPNOTSUPP: ", - "EPFNOSUPPORT: ", - "EAFNOSUPPORT: ", - "EADDRINUSE: ", - "EADDRNOTAVAIL: ", - "ENETDOWN: ", - "ENETUNREACH: ", - "ENETRESET: ", - "ECONNABORTED: ", - "ECONNRESET: ", - "ENOBUFS: ", - "EISCONN: ", - "ENOTCONN: ", - "ESHUTDOWN: ", - "ETOOMANYREFS: ", - "ETIMEDOUT: ", - "ECONNREFUSED: ", - "EHOSTDOWN: ", - "EHOSTUNREACH: ", - "EALREADY: ", - "EINPROGRESS: ", - "ESTALE: ", - "EUCLEAN: ", - "ENOTNAM: ", - "ENAVAIL: ", - "EISNAM: ", - "EREMOTEIO: ", - "EDQUOT: ", - "ENOMEDIUM: ", - "EMEDIUMTYPE: ", - "ECANCELED: ", - "ENOKEY: ", - "EKEYEXPIRED: ", - "EKEYREVOKED: ", - "EKEYREJECTED: ", - "EOWNERDEAD: ", - "ENOTRECOVERABLE: ", - "ERFKILL: ", - "EHWPOISON: ", - }}; - // This provides convenient safe access to the errno short name array. - auto short_name = [](int n) { return n < static_cast(SHORT_NAME.size()) ? SHORT_NAME[n] : "Unknown: "sv; }; - static const BWFormat number_fmt{"[{}]"sv}; // numeric value format. - if (spec.has_numeric_type()) { // if numeric type, print just the numeric part. - w.print(number_fmt, e._e); - } else { - w.write(short_name(e._e)); - w.write(strerror(e._e)); - if (spec._type != 's' && spec._type != 'S') { - w.write(' '); - w.print(number_fmt, e._e); - } - } - return w; -} - -bwf::Date::Date(std::string_view fmt) : _epoch(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), _fmt(fmt) {} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Date const &date) -{ - if (spec.has_numeric_type()) { - bwformat(w, spec, date._epoch); - } else { - struct tm t; - auto r = w.remaining(); - size_t n{0}; - // Verify @a fmt is null terminated, even outside the bounds of the view. - ink_assert(date._fmt.data()[date._fmt.size() - 1] == 0 || date._fmt.data()[date._fmt.size()] == 0); - // Get the time, GMT or local if specified. - if (spec._ext == "local"sv) { - localtime_r(&date._epoch, &t); - } else { - gmtime_r(&date._epoch, &t); - } - // Try a direct write, faster if it works. - if (r > 0) { - n = strftime(w.auxBuffer(), r, date._fmt.data(), &t); - } - if (n > 0) { - w.fill(n); - } else { - // Direct write didn't work. Unfortunately need to write to a temporary buffer or the sizing - // isn't correct if @a w is clipped because @c strftime returns 0 if the buffer isn't large - // enough. - char buff[256]; // hope for the best - no real way to resize appropriately on failure. - n = strftime(buff, sizeof(buff), date._fmt.data(), &t); - w.write(buff, n); - } - } - return w; -} - -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, bwf::OptionalAffix const &opts) -{ - return w.write(opts._prefix).write(opts._text).write(opts._suffix); -} - -} // namespace ts - -namespace -{ -void -BWF_Timestamp(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - // Unfortunately need to write to a temporary buffer or the sizing isn't correct if @a w is clipped - // because @c strftime returns 0 if the buffer isn't large enough. - char buff[32]; - std::time_t t = std::time(nullptr); - auto n = strftime(buff, sizeof(buff), "%Y %b %d %H:%M:%S", std::localtime(&t)); - w.write(buff, n); -} - -void -BWF_Now(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - bwformat(w, spec, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); -} - -void -BWF_Tick(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - bwformat(w, spec, std::chrono::high_resolution_clock::now().time_since_epoch().count()); -} - -void -BWF_ThreadID(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ - bwformat(w, spec, pthread_self()); -} - -void -BWF_ThreadName(ts::BufferWriter &w, ts::BWFSpec const &spec) -{ -#if defined(__FreeBSD_version) - bwformat(w, spec, "thread"sv); // no thread names in FreeBSD. -#else - char name[32]; // manual says at least 16, bump that up a bit. - pthread_getname_np(pthread_self(), name, sizeof(name)); - bwformat(w, spec, std::string_view{name}); -#endif -} - -static bool BW_INITIALIZED __attribute__((unused)) = []() -> bool { - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("now", &BWF_Now); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("tick", &BWF_Tick); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("timestamp", &BWF_Timestamp); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-id", &BWF_ThreadID); - ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-name", &BWF_ThreadName); - return true; -}(); - -} // namespace - -namespace std -{ -ostream & -operator<<(ostream &s, ts::FixedBufferWriter &w) -{ - return s << w.view(); -} -} // namespace std diff --git a/src/tscore/CryptoHash.cc b/src/tscore/CryptoHash.cc index f10eca0d2e1..46d6a684a10 100644 --- a/src/tscore/CryptoHash.cc +++ b/src/tscore/CryptoHash.cc @@ -29,6 +29,7 @@ #include "tscore/ink_code.h" #include "tscore/CryptoHash.h" #include "tscore/SHA256.h" +#include "tscpp/util/bwf_base.h" #if TS_ENABLE_FIPS == 1 CryptoContext::HashType CryptoContext::Setting = CryptoContext::SHA256; @@ -107,3 +108,15 @@ CryptoHash::toHexStr(char buffer[(CRYPTO_HASH_SIZE * 2) + 1]) const { return ink_code_to_hex_str(buffer, u8); } + +namespace ats +{ +ts::BufferWriter & +bwformat(ts::BufferWriter &w, ts::bwf::Spec const &spec, ats::CryptoHash const &hash) +{ + ts::bwf::Spec local_spec{spec}; + if ('X' != local_spec._type) + local_spec._type = 'x'; + return bwformat(w, local_spec, std::string_view(reinterpret_cast(hash.u8), CRYPTO_HASH_SIZE)); +} +} // namespace ats diff --git a/src/tscore/Diags.cc b/src/tscore/Diags.cc index 8d2cd02a185..fba1510e73f 100644 --- a/src/tscore/Diags.cc +++ b/src/tscore/Diags.cc @@ -42,7 +42,7 @@ #include "tscore/ink_time.h" #include "tscore/ink_hrtime.h" #include "tscore/ink_thread.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include "tscore/Diags.h" int diags_on_for_plugins = 0; @@ -220,7 +220,7 @@ Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocat LocalBufferWriter<1024> format_writer; // Save room for optional newline and terminating NUL bytes. - format_writer.clip(2); + format_writer.restrict(2); ////////////////////// // append timestamp // @@ -245,8 +245,8 @@ Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocat /////////////////////// // add the thread id // /////////////////////// - format_writer.fill( - snprintf(format_writer.auxBuffer(), format_writer.remaining(), "{0x%" PRIx64 "} ", (uint64_t)ink_thread_self())); + format_writer.commit( + snprintf(format_writer.aux_data(), format_writer.remaining(), "{0x%" PRIx64 "} ", (uint64_t)ink_thread_self())); ////////////////////////////////// // append the diag level prefix // @@ -283,7 +283,7 @@ Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocat ////////////////////////////////////////////////////// format_writer.write(format_string, strlen(format_string)); - format_writer.extend(2); + format_writer.restore(2); if (format_writer.data()[format_writer.size() - 1] != '\n') { format_writer.write('\n'); } diff --git a/src/tscore/IntrusivePtrTest.cc b/src/tscore/IntrusivePtrTest.cc index 7edd6acbcb1..c7cb58c3f1c 100644 --- a/src/tscore/IntrusivePtrTest.cc +++ b/src/tscore/IntrusivePtrTest.cc @@ -22,7 +22,7 @@ */ #include "tscore/IntrusivePtr.h" -#include "tscore/IntrusiveDList.h" +#include "tscpp/util/IntrusiveDList.h" #include "tscore/TestBox.h" namespace diff --git a/src/tscore/IpMap.cc b/src/tscore/IpMap.cc index 7013029be55..ed206572c3c 100644 --- a/src/tscore/IpMap.cc +++ b/src/tscore/IpMap.cc @@ -282,8 +282,8 @@ namespace detail return n->_prev; } }; - using NodeList = IntrusiveDList; - // typedef IntrusiveDList NodeList; + using NodeList = ts::IntrusiveDList; + // typedef ts::IntrusiveDList NodeList; /// This keeps track of all allocated nodes in order. /// Iteration depends on this list being maintained. NodeList _list; diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am index af29d3a6549..57fcac40bf2 100644 --- a/src/tscore/Makefile.am +++ b/src/tscore/Makefile.am @@ -60,7 +60,6 @@ libtscore_la_SOURCES = \ BaseLogFile.h \ BufferWriter.h \ BufferWriterForward.h \ - BufferWriterFormat.cc \ ConsistentHash.cc \ ConsistentHash.h \ ContFlags.cc \ @@ -160,7 +159,6 @@ libtscore_la_SOURCES = \ ink_time.h \ ink_uuid.cc \ ink_uuid.h \ - IntrusiveDList.h \ IpMap.cc \ IpMapConf.cc \ IpMapConf.h \ @@ -175,9 +173,6 @@ libtscore_la_SOURCES = \ Map.h \ MatcherUtils.cc \ MatcherUtils.h \ - MemSpan.h \ - MemArena.cc \ - MemArena.h \ MMH.cc \ MMH.h \ MT_hashtable.h \ @@ -252,24 +247,18 @@ test_tscore_SOURCES = \ unit_tests/test_AcidPtr.cc \ unit_tests/test_arena.cc \ unit_tests/test_ArgParser.cc \ - unit_tests/test_BufferWriter.cc \ - unit_tests/test_BufferWriterFormat.cc \ unit_tests/test_Extendible.cc \ unit_tests/test_History.cc \ unit_tests/test_ink_inet.cc \ - unit_tests/test_IntrusiveDList.cc \ - unit_tests/test_IntrusiveHashMap.cc \ unit_tests/test_IntrusivePtr.cc \ unit_tests/test_IpMap.cc \ unit_tests/test_layout.cc \ unit_tests/test_Map.cc \ unit_tests/test_List.cc \ - unit_tests/test_MemArena.cc \ unit_tests/test_MT_hashtable.cc \ unit_tests/test_PriorityQueue.cc \ unit_tests/test_Ptr.cc \ unit_tests/test_Regex.cc \ - unit_tests/test_Scalar.cc \ unit_tests/test_scoped_resource.cc \ unit_tests/test_ts_file.cc \ unit_tests/test_Vec.cc diff --git a/src/tscore/SourceLocation.cc b/src/tscore/SourceLocation.cc index c586935b6ab..e9fa79890cb 100644 --- a/src/tscore/SourceLocation.cc +++ b/src/tscore/SourceLocation.cc @@ -25,8 +25,8 @@ #include #include "tscore/SourceLocation.h" #include "tscore/ink_defs.h" -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_ex.h" // This method takes a SourceLocation source location data structure and // converts it to a human-readable representation, in the buffer @@ -57,7 +57,7 @@ SourceLocation::str(char *buf, int buflen) const } ts::BufferWriter & -SourceLocation::print(ts::BufferWriter &w, ts::BWFSpec const &) const +SourceLocation::print(ts::BufferWriter &w, ts::bwf::Spec const &) const { if (this->valid()) { ts::TextView base{ts::TextView{file, strlen(file)}.take_suffix_at('/')}; diff --git a/src/tscore/ink_inet.cc b/src/tscore/ink_inet.cc index 12524878b77..130b2e99df3 100644 --- a/src/tscore/ink_inet.cc +++ b/src/tscore/ink_inet.cc @@ -31,6 +31,7 @@ #include "tscore/ink_assert.h" #include "ts/apidefs.h" #include "tscpp/util/TextView.h" +#include "tscpp/util/bwf_base.h" #include "tscore/ink_inet.h" IpAddr const IpAddr::INVALID; @@ -692,10 +693,10 @@ ats_tcp_somaxconn() namespace ts { BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, in_addr_t addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, in_addr_t addr) { uint8_t *ptr = reinterpret_cast(&addr); - BWFSpec local_spec{spec}; // Format for address elements. + bwf::Spec local_spec{spec}; // Format for address elements. bool align_p = false; if (spec._ext.size()) { @@ -710,7 +711,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in_addr_t addr) if (align_p) { local_spec._min = 3; - local_spec._align = BWFSpec::Align::RIGHT; + local_spec._align = bwf::Spec::Align::RIGHT; } else { local_spec._min = 0; } @@ -726,10 +727,10 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in_addr_t addr) } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, in6_addr const &addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, in6_addr const &addr) { using QUAD = uint16_t const; - BWFSpec local_spec{spec}; // Format for address elements. + bwf::Spec local_spec{spec}; // Format for address elements. uint8_t const *ptr = addr.s6_addr; uint8_t const *limit = ptr + sizeof(addr.s6_addr); QUAD *lower = nullptr; // the best zero range @@ -748,7 +749,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in6_addr const &addr) if (align_p) { local_spec._min = 4; - local_spec._align = BWFSpec::Align::RIGHT; + local_spec._align = bwf::Spec::Align::RIGHT; } else { local_spec._min = 0; // do 0 compression if there's no internal fill. @@ -794,9 +795,9 @@ bwformat(BufferWriter &w, BWFSpec const &spec, in6_addr const &addr) } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, IpAddr const &addr) { - BWFSpec local_spec{spec}; // Format for address elements and port. + bwf::Spec local_spec{spec}; // Format for address elements and port. bool addr_p{true}; bool family_p{false}; @@ -848,9 +849,9 @@ bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr) } BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr) +bwformat(BufferWriter &w, bwf::Spec const &spec, sockaddr const *addr) { - BWFSpec local_spec{spec}; // Format for address elements and port. + bwf::Spec local_spec{spec}; // Format for address elements and port. bool port_p{true}; bool addr_p{true}; bool family_p{false}; @@ -918,7 +919,7 @@ bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr) if (local_numeric_fill_p) { local_spec._min = 5; local_spec._fill = local_numeric_fill_char; - local_spec._align = BWFSpec::Align::RIGHT; + local_spec._align = bwf::Spec::Align::RIGHT; } else { local_spec._min = 0; } diff --git a/src/tscore/unit_tests/test_History.cc b/src/tscore/unit_tests/test_History.cc index 3e699139da0..05234b46fcd 100644 --- a/src/tscore/unit_tests/test_History.cc +++ b/src/tscore/unit_tests/test_History.cc @@ -24,7 +24,7 @@ #include #include "tscore/History.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" #include "catch.hpp" using std::string_view; @@ -73,7 +73,7 @@ TEST_CASE("History", "[libts][History]") w.print("{}", sm->history[0].location); REQUIRE(w.view() == "test_History.cc:69 (____C_A_T_C_H____T_E_S_T____0)"); - w.reset().print("{}", sm->history[1].location); + w.clear().print("{}", sm->history[1].location); REQUIRE(w.view() == "test_History.cc:70 (____C_A_T_C_H____T_E_S_T____0)"); REQUIRE(sm->history[0].event == 1); @@ -105,10 +105,10 @@ TEST_CASE("History", "[libts][History]") REQUIRE(sm2->history.size() == 2); REQUIRE(sm2->history.overflowed() == true); - w.reset().print("{}", sm2->history[0].location); + w.clear().print("{}", sm2->history[0].location); REQUIRE(w.view() == "test_History.cc:103 (____C_A_T_C_H____T_E_S_T____0)"); - w.reset().print("{}", sm2->history[1].location); + w.clear().print("{}", sm2->history[1].location); REQUIRE(w.view() == "test_History.cc:98 (____C_A_T_C_H____T_E_S_T____0)"); sm2->history.clear(); diff --git a/src/tscore/unit_tests/test_ink_inet.cc b/src/tscore/unit_tests/test_ink_inet.cc index bd5749818b8..75466c152d8 100644 --- a/src/tscore/unit_tests/test_ink_inet.cc +++ b/src/tscore/unit_tests/test_ink_inet.cc @@ -22,13 +22,13 @@ */ #include "tscpp/util/TextView.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" #include "tscore/ink_inet.h" #include #include #include "ts/apidefs.h" #include "tscore/ink_inet.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/bwf_base.h" using namespace std::literals; @@ -164,87 +164,87 @@ TEST_CASE("inet formatting", "[libts][ink_inet][bwformat]") REQUIRE(0 == ats_ip_pton(addr_1, &ep.sa)); w.print("{}", ep); REQUIRE(w.view() == addr_1); - w.reset().print("{::p}", ep); + w.clear().print("{::p}", ep); REQUIRE(w.view() == "8080"); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == addr_1.substr(1, 24)); // check the brackets are dropped. - w.reset().print("[{::a}]", ep); + w.clear().print("[{::a}]", ep); REQUIRE(w.view() == addr_1.substr(0, 26)); // check the brackets are dropped. - w.reset().print("[{0::a}]:{0::p}", ep); + w.clear().print("[{0::a}]:{0::p}", ep); REQUIRE(w.view() == addr_1); // check the brackets are dropped. - w.reset().print("{::=a}", ep); + w.clear().print("{::=a}", ep); REQUIRE(w.view() == "ffee:0000:0000:0000:24c3:3349:3cee:0143"); - w.reset().print("{:: =a}", ep); + w.clear().print("{:: =a}", ep); REQUIRE(w.view() == "ffee: 0: 0: 0:24c3:3349:3cee: 143"); ep.setToLoopback(AF_INET6); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "::1"); REQUIRE(0 == ats_ip_pton(addr_3, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "1337:ded:beef::"); REQUIRE(0 == ats_ip_pton(addr_4, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "1337::ded:beef"); REQUIRE(0 == ats_ip_pton(addr_5, &ep.sa)); - w.reset().print("{:X:a}", ep); + w.clear().print("{:X:a}", ep); REQUIRE(w.view() == "1337::DED:BEEF:0:0:956"); REQUIRE(0 == ats_ip_pton(addr_6, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "1337:0:0:ded:beef::"); REQUIRE(0 == ats_ip_pton(addr_null, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "::"); REQUIRE(0 == ats_ip_pton(addr_2, &ep.sa)); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == addr_2.substr(0, 13)); - w.reset().print("{0::a}", ep); + w.clear().print("{0::a}", ep); REQUIRE(w.view() == addr_2.substr(0, 13)); - w.reset().print("{::ap}", ep); + w.clear().print("{::ap}", ep); REQUIRE(w.view() == addr_2); - w.reset().print("{::f}", ep); + w.clear().print("{::f}", ep); REQUIRE(w.view() == IP_PROTO_TAG_IPV4); - w.reset().print("{::fpa}", ep); + w.clear().print("{::fpa}", ep); REQUIRE(w.view() == "172.17.99.231:23995 ipv4"); - w.reset().print("{0::a} .. {0::p}", ep); + w.clear().print("{0::a} .. {0::p}", ep); REQUIRE(w.view() == "172.17.99.231 .. 23995"); - w.reset().print("<+> {0::a} <+> {0::p}", ep); + w.clear().print("<+> {0::a} <+> {0::p}", ep); REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995"); - w.reset().print("<+> {0::a} <+> {0::p} <+>", ep); + w.clear().print("<+> {0::a} <+> {0::p} <+>", ep); REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995 <+>"); - w.reset().print("{:: =a}", ep); + w.clear().print("{:: =a}", ep); REQUIRE(w.view() == "172. 17. 99.231"); - w.reset().print("{::=a}", ep); + w.clear().print("{::=a}", ep); REQUIRE(w.view() == "172.017.099.231"); // Documentation examples REQUIRE(0 == ats_ip_pton(addr_7, &ep.sa)); - w.reset().print("To {}", ep); + w.clear().print("To {}", ep); REQUIRE(w.view() == "To 172.19.3.105:4951"); - w.reset().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice. + w.clear().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice. REQUIRE(w.view() == "To 172.19.3.105 on port 4951"); - w.reset().print("To {::=}", ep); + w.clear().print("To {::=}", ep); REQUIRE(w.view() == "To 172.019.003.105:04951"); - w.reset().print("{::a}", ep); + w.clear().print("{::a}", ep); REQUIRE(w.view() == "172.19.3.105"); - w.reset().print("{::=a}", ep); + w.clear().print("{::=a}", ep); REQUIRE(w.view() == "172.019.003.105"); - w.reset().print("{::0=a}", ep); + w.clear().print("{::0=a}", ep); REQUIRE(w.view() == "172.019.003.105"); - w.reset().print("{:: =a}", ep); + w.clear().print("{:: =a}", ep); REQUIRE(w.view() == "172. 19. 3.105"); - w.reset().print("{:>20:a}", ep); + w.clear().print("{:>20:a}", ep); REQUIRE(w.view() == " 172.19.3.105"); - w.reset().print("{:>20:=a}", ep); + w.clear().print("{:>20:=a}", ep); REQUIRE(w.view() == " 172.019.003.105"); - w.reset().print("{:>20: =a}", ep); + w.clear().print("{:>20: =a}", ep); REQUIRE(w.view() == " 172. 19. 3.105"); - w.reset().print("{:<20:a}", ep); + w.clear().print("{:<20:a}", ep); REQUIRE(w.view() == "172.19.3.105 "); - w.reset().print("{:p}", reinterpret_cast(0x1337beef)); + w.clear().print("{:p}", reinterpret_cast(0x1337beef)); REQUIRE(w.view() == "0x1337beef"); } diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am index 67ca3be9763..782b7ec0d91 100644 --- a/src/tscpp/util/Makefile.am +++ b/src/tscpp/util/Makefile.am @@ -27,6 +27,12 @@ AM_CPPFLAGS += -I$(abs_top_srcdir)/include libtscpputil_la_LDFLAGS = -no-undefined -version-info @TS_LIBTOOL_VERSION@ libtscpputil_la_SOURCES = \ + bw_format.cc \ + IntrusiveDList.h \ + IntrusiveHashMap.h \ + MemArena.h \ + MemArena.cc \ + MemSpan.h \ PostScript.h \ TextView.h TextView.cc @@ -37,8 +43,13 @@ test_tscpputil_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS) test_tscpputil_LDADD = libtscpputil.la test_tscpputil_SOURCES = \ unit_tests/unit_test_main.cc \ + unit_tests/test_IntrusiveDList.cc \ + unit_tests/test_IntrusiveHashMap.cc \ + unit_tests/test_MemArena.cc \ + unit_tests/test_bw_format.cc \ unit_tests/test_MemSpan.cc \ unit_tests/test_PostScript.cc \ + unit_tests/test_Scalar.cc \ unit_tests/test_TextView.cc \ unit_tests/test_ts_meta.cc diff --git a/src/tscore/MemArena.cc b/src/tscpp/util/MemArena.cc similarity index 58% rename from src/tscore/MemArena.cc rename to src/tscpp/util/MemArena.cc index 3dfcb0cfed5..e156c2c28ec 100644 --- a/src/tscore/MemArena.cc +++ b/src/tscpp/util/MemArena.cc @@ -23,10 +23,7 @@ */ #include - -#include "tscore/MemArena.h" -#include "tscore/ink_memory.h" -#include "tscore/ink_assert.h" +#include "tscpp/util/MemArena.h" using namespace ts; @@ -36,15 +33,15 @@ MemArena::Block::operator delete(void *ptr) ::free(ptr); } -MemArena::BlockPtr +MemArena::Block * MemArena::make_block(size_t n) { // If there's no reservation hint, use the extent. This is transient because the hint is cleared. if (_reserve_hint == 0) { if (_active_reserved) { _reserve_hint = _active_reserved; - } else if (_prev_allocated) { - _reserve_hint = _prev_allocated; + } else if (_frozen_allocated) { + _reserve_hint = _frozen_allocated; } } @@ -63,53 +60,40 @@ MemArena::make_block(size_t n) // Easier to use malloc and override @c delete. auto free_space = n - sizeof(Block); _active_reserved += free_space; - return BlockPtr(new (::malloc(n)) Block(free_space)); + return new (::malloc(n)) Block(free_space); } MemSpan MemArena::alloc(size_t n) { - MemSpan zret; - _active_allocated += n; - - if (!_active) { - _active = this->make_block(n); - zret = _active->alloc(n); - } else if (n > _active->remaining()) { // too big, need another block - BlockPtr block = this->make_block(n); - // For the new @a _active, pick the block which will have the most free space after taking - // the request space out of the new block. - zret = block->alloc(n); - if (block->remaining() > _active->remaining()) { - block->next = _active; - _active = block; -#if 0 - // Defeat another clang analyzer false positive. Unit tests validate the code is correct. - ink_assert(_active.use_count() > 1); -#endif + Block *block = _active.head(); + + if (nullptr == block) { + block = this->make_block(n); + _active.prepend(block); + } else if (n > block->remaining()) { // too big, need another block + block = this->make_block(n); + // For the resulting active allocation block, pick the block which will have the most free space + // after taking the request space out of the new block. + if (block->remaining() - n > _active.head()->remaining()) { + _active.prepend(block); } else { - block->next = _active->next; - _active->next = block; -#if 0 - // Defeat another clang analyzer false positive. Unit tests validate the code is correct. - ink_assert(block.use_count() > 1); -#endif + _active.insert_after(_active.head(), block); } - } else { - zret = _active->alloc(n); } - return zret; + _active_allocated += n; + return block->alloc(n); } MemArena & MemArena::freeze(size_t n) { - _prev = _active; - _active.reset(); // it's in _prev now, start fresh. + this->destroy_frozen(); + _frozen = std::move(_active); // Update the meta data. - _prev_allocated = _active_allocated; + _frozen_allocated = _active_allocated; _active_allocated = 0; - _prev_reserved = _active_reserved; + _frozen_reserved = _active_reserved; _active_reserved = 0; _reserve_hint = n; @@ -120,36 +104,58 @@ MemArena::freeze(size_t n) MemArena & MemArena::thaw() { - _prev.reset(); - _prev_reserved = _prev_allocated = 0; + this->destroy_frozen(); + _frozen_reserved = _frozen_allocated = 0; return *this; } bool MemArena::contains(const void *ptr) const { - for (Block *b = _active.get(); b; b = b->next.get()) { - if (b->contains(ptr)) { - return true; - } - } - for (Block *b = _prev.get(); b; b = b->next.get()) { - if (b->contains(ptr)) { - return true; - } - } + auto pred = [ptr](const Block &b) -> bool { return b.contains(ptr); }; - return false; + return std::any_of(_active.begin(), _active.end(), pred) || std::any_of(_frozen.begin(), _frozen.end(), pred); +} + +void +MemArena::destroy_active() +{ + _active.apply([](Block *b) { delete b; }).clear(); +} + +void +MemArena::destroy_frozen() +{ + _frozen.apply([](Block *b) { delete b; }).clear(); } MemArena & MemArena::clear(size_t n) { - _reserve_hint = n ? n : _prev_allocated + _active_allocated; - _prev.reset(); - _prev_reserved = _prev_allocated = 0; - _active.reset(); + _reserve_hint = n ? n : _frozen_allocated + _active_allocated; + _frozen_reserved = _frozen_allocated = 0; _active_reserved = _active_allocated = 0; + this->destroy_frozen(); + this->destroy_active(); return *this; } + +MemArena::~MemArena() +{ + // Destruct in a way that makes it safe for the instance to be in one of its own memory blocks. + Block *ba = _active.head(); + Block *bf = _frozen.head(); + _active.clear(); + _frozen.clear(); + while (bf) { + Block *b = bf; + bf = bf->_link._next; + delete b; + } + while (ba) { + Block *b = ba; + ba = ba->_link._next; + delete b; + } +} diff --git a/src/tscpp/util/TextView.cc b/src/tscpp/util/TextView.cc index aa96a2cb81a..b6b1135eb62 100644 --- a/src/tscpp/util/TextView.cc +++ b/src/tscpp/util/TextView.cc @@ -1,31 +1,31 @@ /** @file - Class for handling "views" of a buffer. Views presume the memory for the buffer is managed - elsewhere and allow efficient access to segments of the buffer without copies. Views are read - only as the view doesn't own the memory. Along with generic buffer methods are specialized - methods to support better string parsing, particularly token based parsing. + Class for handling "views" of a buffer. Views presume the memory for the + buffer is managed elsewhere and allow efficient access to segments of the + buffer without copies. Views are read only as the view doesn't own the + memory. Along with generic buffer methods are specialized methods to support + better string parsing, particularly token based parsing. @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 + 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. + 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 "tscpp/util/TextView.h" -#include #include +#include int ts::memcmp(TextView const &lhs, TextView const &rhs) @@ -64,31 +64,31 @@ strcasecmp(const std::string_view &lhs, const std::string_view &rhs) return zret; } +const int8_t ts::svtoi_convert[256] = { + /* [can't do this nicely because clang format won't allow exdented comments] + 0 1 2 3 4 5 6 7 8 9 A B C D E F + */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30 + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 40 + 25, 26, 27, 28, 20, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 50 + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 60 + 25, 26, 27, 28, 20, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 70 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // B0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // D0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // F0 +}; + intmax_t ts::svtoi(TextView src, TextView *out, int base) { - static const int8_t convert[256] = { - /* [can't do this nicely because clang format won't allow exdented comments] - 0 1 2 3 4 5 6 7 8 9 A B C D E F - */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30 - -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 40 - 25, 26, 27, 28, 20, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 50 - -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 60 - 25, 26, 27, 28, 20, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 70 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // B0 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // D0 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // F0 - }; - intmax_t zret = 0; if (out) { @@ -117,10 +117,26 @@ ts::svtoi(TextView src, TextView *out, int base) } } } - while (src.size() && (0 <= (v = convert[static_cast(*src)])) && v < base) { - zret = zret * base + v; - ++src; + + // For performance in common cases, use the templated conversion. + switch (base) { + case 8: + zret = svto_radix<8>(src); + break; + case 10: + zret = svto_radix<10>(src); + break; + case 16: + zret = svto_radix<16>(src); + break; + default: + while (src.size() && (0 <= (v = svtoi_convert[static_cast(*src)])) && v < base) { + zret = zret * base + v; + ++src; + } + break; } + if (out && (src.data() > (neg ? start + 1 : start))) { out->assign(start, src.data()); } diff --git a/src/tscpp/util/bw_format.cc b/src/tscpp/util/bw_format.cc new file mode 100644 index 00000000000..e12852616cb --- /dev/null +++ b/src/tscpp/util/bw_format.cc @@ -0,0 +1,1104 @@ +/** @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 +#include +#include +#include +#include +#include +#include + +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_base.h" +#include "tscpp/util/bwf_ex.h" +#include "tscpp/util/bwf_printf.h" +#include "tscpp/util/ts_meta.h" + +using namespace std::literals; + +ts::bwf::GlobalNames ts::bwf::Global_Names; + +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 a little faster. This version handles only positive integers in decimal. + +inline unsigned +radix10(ts::TextView src, ts::TextView &out) +{ + unsigned zret = 0; + + out.clear(); + src.ltrim_if(&isspace); + if (src.size()) { + auto start = src.data(); + zret = ts::svto_radix<10>(src); + if (start != src.data()) { + out.assign(start, src.data()); + } + } + return zret; +} +} // namespace + +namespace ts +{ +namespace bwf +{ + const Spec Spec::DEFAULT; + + const Spec::Property Spec::_prop; + +#pragma GCC diagnostic ignored "-Wchar-subscripts" + Spec::Property::Property() + { + memset(_data, 0, sizeof(_data)); + _data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; + _data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['g'] = TYPE_CHAR; + _data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['p'] = TYPE_CHAR; + _data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR; + _data['s'] = TYPE_CHAR; + _data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR; + _data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR; + _data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR; + + _data[SIGN_NEVER] = SIGN_CHAR; + _data[SIGN_NEG] = SIGN_CHAR; + _data[SIGN_ALWAYS] = SIGN_CHAR; + + _data['<'] = static_cast(Spec::Align::LEFT); + _data['>'] = static_cast(Spec::Align::RIGHT); + _data['^'] = static_cast(Spec::Align::CENTER); + _data['='] = static_cast(Spec::Align::SIGN); + } + + Spec::Spec(const TextView &fmt) { this->parse(fmt); } + /// Parse a format specification. + bool + Spec::parse(TextView fmt) + { + TextView num; // temporary for number parsing. + intmax_t n; + + _name = fmt.take_prefix_at(':'); + // if it's parsable as a number, treat it as an index. + n = radix10(_name, num); + if (num.size() == _name.size()) { + _idx = static_cast(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 true; + } + // sign + if (is_sign(*sz)) { + _sign = *sz; + if (!(++sz).size()) { + return true; + } + } + // radix prefix + if ('#' == *sz) { + _radix_lead_p = true; + if (!(++sz).size()) { + return true; + } + } + // 0 fill for integers + if ('0' == *sz) { + if (Align::NONE == _align) { + _align = Align::SIGN; + } + _fill = '0'; + ++sz; + } + n = radix10(sz, num); + if (num.size()) { + _min = static_cast(n); + sz.remove_prefix(num.size()); + if (!sz.size()) { + return true; + } + } + // precision + if ('.' == *sz) { + n = radix10(++sz, num); + if (num.size()) { + _prec = static_cast(n); + sz.remove_prefix(num.size()); + if (!sz.size()) { + return true; + } + } else { + throw std::invalid_argument("Precision mark without precision"); + } + } + // style (type). Hex, octal, etc. + if (is_type(*sz)) { + _type = *sz; + if (!(++sz).size()) { + return true; + } + } + // maximum width + if (',' == *sz) { + n = radix10(++sz, num); + if (num.size()) { + _max = static_cast(n); + sz.remove_prefix(num.size()); + if (!sz.size()) { + return true; + } + } 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 true; + } + } + } + } + } + return true; + } + + /// Parse out the next literal and/or format specifier from the format string. + /// Pass the results back in @a literal and @a specifier as appropriate. + /// Update @a fmt to strip the parsed text. + /// @return @c true if a specifier was parsed, @c false if not. + bool + Format::TextViewExtractor::parse(TextView &fmt, std::string_view &literal, std::string_view &specifier) + { + TextView::size_type off; + + // Check for brace delimiters. + off = fmt.find_if([](char c) { return '{' == c || '}' == c; }); + if (off == TextView::npos) { + // not found, it's a literal, ship it. + literal = fmt; + fmt.remove_prefix(literal.size()); + return false; + } + + // Processing for braces that don't enclose specifiers. + if (fmt.size() > off + 1) { + char c1 = fmt[off]; + char c2 = fmt[off + 1]; + if (c1 == c2) { + // double braces count as literals, but must tweak to output only 1 brace. + literal = fmt.take_prefix_at(off + 1); + return false; + } else if ('}' == c1) { + throw std::invalid_argument("Unopened } in format string."); + } else { + literal = std::string_view{fmt.data(), off}; + fmt.remove_prefix(off + 1); + } + } else { + throw std::invalid_argument("Invalid trailing character in format string."); + } + + 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("BWFormat: Unclosed { in format string"); + } + specifier = fmt.take_prefix_at(off); + return true; + } + return false; + } + + bool + Format::TextViewExtractor::operator()(std::string_view &literal_v, Spec &spec) + { + if (!_fmt.empty()) { + std::string_view spec_v; + if (parse(_fmt, literal_v, spec_v)) { + return spec.parse(spec_v); + } + } + return false; + } + + bool + Format::FormatExtractor::operator()(std::string_view &literal_v, ts::bwf::Spec &spec) + { + literal_v = {}; + if (_idx < int(_fmt.size()) && _fmt[_idx]._type == Spec::LITERAL_TYPE) { + literal_v = _fmt[_idx++]._ext; + } + if (_idx < int(_fmt.size()) && _fmt[_idx]._type != Spec::LITERAL_TYPE) { + spec = _fmt[_idx++]; + return true; + } + return false; + } + + void + Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n) + { + static const Format 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 + Adjust_Alignment(BufferWriter &aux, Spec const &spec) + { + size_t extent = aux.extent(); + size_t min = spec._min; + if (extent < min) { + size_t delta = min - extent; + size_t left_delta = 0, right_delta = delta; // left justify values + if (Spec::Align::RIGHT == spec._align) { + left_delta = delta; + right_delta = 0; + } else if (Spec::Align::CENTER == spec._align) { + left_delta = delta / 2; + right_delta = (delta + 1) / 2; + } + if (left_delta > 0) { + size_t work_area = extent + left_delta; + aux.commit(left_delta); // cover work area. + aux.copy(left_delta, 0, extent); // move to create space for left fill. + aux.discard(work_area); // roll back to write the left fill. + for (int i = left_delta; i > 0; --i) { + aux.write(spec._fill); + } + aux.commit(extent); + } + for (int i = right_delta; i > 0; --i) { + aux.write(spec._fill); + } + + } else { + size_t max = spec._max; + if (max < extent) { + aux.discard(extent - max); + } + } + } + + // 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"; + static const std::array POWERS_OF_TEN = { + {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}}; + } // namespace + + /// 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 + 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; + } + + template + void + Write_Aligned(BufferWriter &w, F const &f, Spec::Align align, int width, char fill, char neg) + { + switch (align) { + case Spec::Align::LEFT: + if (neg) { + w.write(neg); + } + f(); + while (width-- > 0) { + w.write(fill); + } + break; + case Spec::Align::RIGHT: + while (width-- > 0) { + w.write(fill); + } + if (neg) { + w.write(neg); + } + f(); + break; + case Spec::Align::CENTER: + for (int i = width / 2; i > 0; --i) { + w.write(fill); + } + if (neg) { + w.write(neg); + } + f(); + for (int i = (width + 1) / 2; i > 0; --i) { + w.write(fill); + } + break; + case Spec::Align::SIGN: + if (neg) { + w.write(neg); + } + while (width-- > 0) { + w.write(fill); + } + f(); + break; + default: + if (neg) { + w.write(neg); + } + f(); + break; + } + } + + BufferWriter & + Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t i, bool neg_p) + { + size_t n = 0; + int width = static_cast(spec._min); // amount left to fill. + char neg = 0; + char prefix1 = spec._radix_lead_p ? '0' : 0; + char prefix2 = 0; + char buff[std::numeric_limits::digits + 1]; + + if (spec._sign != Spec::SIGN_NEVER) { + if (neg_p) { + neg = '-'; + } else if (spec._sign == Spec::SIGN_ALWAYS) { + neg = spec._sign; + } + } + + switch (spec._type) { + case 'x': + prefix2 = 'x'; + n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); + break; + case 'X': + prefix2 = 'X'; + n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::UPPER_DIGITS); + break; + case 'b': + prefix2 = 'b'; + n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); + break; + case 'B': + prefix2 = 'B'; + n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::UPPER_DIGITS); + break; + case 'o': + n = bwf::To_Radix<8>(i, buff, sizeof(buff), bwf::LOWER_DIGITS); + break; + default: + prefix1 = 0; + n = bwf::To_Radix<10>(i, buff, sizeof(buff), bwf::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(n); + std::string_view digits{buff + sizeof(buff) - n, n}; + + if (spec._align == Spec::Align::SIGN) { // custom for signed case because + // prefix and digits are seperated. + 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); + } else { // use generic Write_Aligned + Write_Aligned(w, + [&]() { + if (prefix1) { + w.write(prefix1); + if (prefix2) { + w.write(prefix2); + } + } + w.write(digits); + }, + spec._align, width, spec._fill, neg); + } + return w; + } + + /// Format for floating point values. Seperates floating point into a whole + /// number and a fraction. The fraction is converted into an unsigned integer + /// based on the specified precision, spec._prec. ie. 3.1415 with precision two + /// is seperated into two unsigned integers 3 and 14. The different pieces are + /// assembled and placed into the BufferWriter. The default is two decimal + /// places. ie. X.XX. The value is always written in base 10. + /// + /// format: whole.fraction + /// or: left.right + BufferWriter & + Format_Float(BufferWriter &w, Spec const &spec, double f, bool neg_p) + { + static const std::string_view infinity_bwf{"Inf"}; + static const std::string_view nan_bwf{"NaN"}; + static const std::string_view zero_bwf{"0"}; + static const std::string_view subnormal_bwf{"subnormal"}; + static const std::string_view unknown_bwf{"unknown float"}; + + // Handle floating values that are not normal + if (!std::isnormal(f)) { + std::string_view unnormal; + switch (std::fpclassify(f)) { + case FP_INFINITE: + unnormal = infinity_bwf; + break; + case FP_NAN: + unnormal = nan_bwf; + break; + case FP_ZERO: + unnormal = zero_bwf; + break; + case FP_SUBNORMAL: + unnormal = subnormal_bwf; + break; + default: + unnormal = unknown_bwf; + } + + w.write(unnormal); + return w; + } + + uint64_t whole_part = static_cast(f); + if (whole_part == f || spec._prec == 0) { // integral + return Format_Integer(w, spec, whole_part, neg_p); + } + + static constexpr char dec = '.'; + double frac; + size_t l = 0; + size_t r = 0; + char whole[std::numeric_limits::digits10 + 1]; + char fraction[std::numeric_limits::digits10 + 1]; + char neg = 0; + int width = static_cast(spec._min); // amount left to fill. + unsigned int precision = (spec._prec == Spec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2 + + frac = f - whole_part; // split the number + + if (neg_p) { + neg = '-'; + } else if (spec._sign != '-') { + neg = spec._sign; + } + + // Shift the floating point based on the precision. Used to convert + // trailing fraction into an integer value. + uint64_t shift; + if (precision < POWERS_OF_TEN.size()) { + shift = POWERS_OF_TEN[precision]; + } else { // not precomputed. + shift = POWERS_OF_TEN.back(); + for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) { + shift *= 10; + } + } + + uint64_t frac_part = static_cast(frac * shift + 0.5 /* rounding */); + + l = bwf::To_Radix<10>(whole_part, whole, sizeof(whole), bwf::LOWER_DIGITS); + r = bwf::To_Radix<10>(frac_part, fraction, sizeof(fraction), bwf::LOWER_DIGITS); + + // Clip fill width + if (neg) { + --width; + } + width -= static_cast(l); + --width; // '.' + width -= static_cast(r); + + std::string_view whole_digits{whole + sizeof(whole) - l, l}; + std::string_view frac_digits{fraction + sizeof(fraction) - r, r}; + + Write_Aligned(w, + [&]() { + w.write(whole_digits); + w.write(dec); + w.write(frac_digits); + }, + spec._align, width, spec._fill, neg); + + return w; + } + + /// Write out the @a data as hexadecimal, using @a digits as the conversion. + void + Hex_Dump(BufferWriter &w, std::string_view data, const char *digits) + { + const char *ptr = data.data(); + for (auto n = data.size(); n > 0; --n) { + char c = *ptr++; + w.write(digits[(c >> 4) & 0xF]); + w.write(digits[c & 0xf]); + } + } + + /// Preparse format string for later use. + Format::Format(TextView fmt) + { + Spec lit_spec; + int arg_idx = 0; + auto ex{bind(fmt)}; + std::string_view literal_v; + + lit_spec._type = Spec::LITERAL_TYPE; + + while (ex) { + Spec spec; + bool spec_p = ex(literal_v, spec); + + if (literal_v.size()) { + lit_spec._ext = literal_v; + _items.emplace_back(lit_spec); + } + + if (spec_p) { + if (spec._name.size() == 0) { // no name provided, use implicit index. + spec._idx = arg_idx++; + } + if (spec._idx >= 0) { + ++arg_idx; + } + _items.emplace_back(spec); + } + } + } + + BoundNames::~BoundNames() {} +} // namespace bwf + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv) +{ + int width = static_cast(spec._min); // amount left to fill. + if (spec._prec > 0) { + sv = sv.substr(0, spec._prec); + } + + if ('x' == spec._type || 'X' == spec._type) { + const char *digits = 'x' == spec._type ? bwf::LOWER_DIGITS : bwf::UPPER_DIGITS; + width -= sv.size() * 2; + if (spec._radix_lead_p) { + w.write('0'); + w.write(spec._type); + width -= 2; + } + bwf::Write_Aligned(w, [&w, &sv, digits]() { bwf::Hex_Dump(w, sv, digits); }, spec._align, width, spec._fill, 0); + } else { + width -= sv.size(); + bwf::Write_Aligned(w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0); + } + return w; +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan const &span) +{ + static const bwf::Format default_fmt{"{:#x}@{:p}"}; + if (spec._ext.size() && 'd' == spec._ext.front()) { + const char *digits = 'X' == spec._type ? bwf::UPPER_DIGITS : bwf::LOWER_DIGITS; + if (spec._radix_lead_p) { + w.write('0'); + w.write(digits[33]); + } + bwf::Hex_Dump(w, span.view(), digits); + } else { + w.print(default_fmt, span.size(), span.data()); + } + return w; +} + +std::ostream & +FixedBufferWriter::operator>>(std::ostream &s) const +{ + return s << this->view(); +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e) +{ + // Hand rolled, might not be totally compliant everywhere, but probably close + // enough. The long string will be locally accurate. Clang requires the double + // braces. Why, Turing only knows. + static const std::array SHORT_NAME = {{ + "SUCCESS: ", + "EPERM: ", + "ENOENT: ", + "ESRCH: ", + "EINTR: ", + "EIO: ", + "ENXIO: ", + "E2BIG ", + "ENOEXEC: ", + "EBADF: ", + "ECHILD: ", + "EAGAIN: ", + "ENOMEM: ", + "EACCES: ", + "EFAULT: ", + "ENOTBLK: ", + "EBUSY: ", + "EEXIST: ", + "EXDEV: ", + "ENODEV: ", + "ENOTDIR: ", + "EISDIR: ", + "EINVAL: ", + "ENFILE: ", + "EMFILE: ", + "ENOTTY: ", + "ETXTBSY: ", + "EFBIG: ", + "ENOSPC: ", + "ESPIPE: ", + "EROFS: ", + "EMLINK: ", + "EPIPE: ", + "EDOM: ", + "ERANGE: ", + "EDEADLK: ", + "ENAMETOOLONG: ", + "ENOLCK: ", + "ENOSYS: ", + "ENOTEMPTY: ", + "ELOOP: ", + "EWOULDBLOCK: ", + "ENOMSG: ", + "EIDRM: ", + "ECHRNG: ", + "EL2NSYNC: ", + "EL3HLT: ", + "EL3RST: ", + "ELNRNG: ", + "EUNATCH: ", + "ENOCSI: ", + "EL2HTL: ", + "EBADE: ", + "EBADR: ", + "EXFULL: ", + "ENOANO: ", + "EBADRQC: ", + "EBADSLT: ", + "EDEADLOCK: ", + "EBFONT: ", + "ENOSTR: ", + "ENODATA: ", + "ETIME: ", + "ENOSR: ", + "ENONET: ", + "ENOPKG: ", + "EREMOTE: ", + "ENOLINK: ", + "EADV: ", + "ESRMNT: ", + "ECOMM: ", + "EPROTO: ", + "EMULTIHOP: ", + "EDOTDOT: ", + "EBADMSG: ", + "EOVERFLOW: ", + "ENOTUNIQ: ", + "EBADFD: ", + "EREMCHG: ", + "ELIBACC: ", + "ELIBBAD: ", + "ELIBSCN: ", + "ELIBMAX: ", + "ELIBEXEC: ", + "EILSEQ: ", + "ERESTART: ", + "ESTRPIPE: ", + "EUSERS: ", + "ENOTSOCK: ", + "EDESTADDRREQ: ", + "EMSGSIZE: ", + "EPROTOTYPE: ", + "ENOPROTOOPT: ", + "EPROTONOSUPPORT: ", + "ESOCKTNOSUPPORT: ", + "EOPNOTSUPP: ", + "EPFNOSUPPORT: ", + "EAFNOSUPPORT: ", + "EADDRINUSE: ", + "EADDRNOTAVAIL: ", + "ENETDOWN: ", + "ENETUNREACH: ", + "ENETRESET: ", + "ECONNABORTED: ", + "ECONNRESET: ", + "ENOBUFS: ", + "EISCONN: ", + "ENOTCONN: ", + "ESHUTDOWN: ", + "ETOOMANYREFS: ", + "ETIMEDOUT: ", + "ECONNREFUSED: ", + "EHOSTDOWN: ", + "EHOSTUNREACH: ", + "EALREADY: ", + "EINPROGRESS: ", + "ESTALE: ", + "EUCLEAN: ", + "ENOTNAM: ", + "ENAVAIL: ", + "EISNAM: ", + "EREMOTEIO: ", + "EDQUOT: ", + "ENOMEDIUM: ", + "EMEDIUMTYPE: ", + "ECANCELED: ", + "ENOKEY: ", + "EKEYEXPIRED: ", + "EKEYREVOKED: ", + "EKEYREJECTED: ", + "EOWNERDEAD: ", + "ENOTRECOVERABLE: ", + "ERFKILL: ", + "EHWPOISON: ", + }}; + // This provides convenient safe access to the errno short name array. + auto short_name = [](int n) { return n < int(SHORT_NAME.size()) ? SHORT_NAME[n] : "Unknown: "sv; }; + static const bwf::Format number_fmt{"[{}]"sv}; // numeric value format. + if (spec.has_numeric_type()) { // if numeric type, print just the numeric + // part. + w.print(number_fmt, e._e); + } else { + w.write(short_name(e._e)); + w.write(strerror(e._e)); + if (spec._type != 's' && spec._type != 'S') { + w.write(' '); + w.print(number_fmt, e._e); + } + } + return w; +} + +bwf::Date::Date(std::string_view fmt) : _epoch(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), _fmt(fmt) {} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date) +{ + if (spec.has_numeric_type()) { + bwformat(w, spec, date._epoch); + } else { + struct tm t; + auto r = w.remaining(); + size_t n{0}; + // Verify @a fmt is null terminated, even outside the bounds of the view. + if (date._fmt.data()[date._fmt.size() - 1] != 0 && date._fmt.data()[date._fmt.size()] != 0) { + throw(std::invalid_argument{"BWF Date String is not null terminated."}); + } + // Get the time, GMT or local if specified. + if (spec._ext == "local"sv) { + localtime_r(&date._epoch, &t); + } else { + gmtime_r(&date._epoch, &t); + } + // Try a direct write, faster if it works. + if (r > 0) { + n = strftime(w.aux_data(), r, date._fmt.data(), &t); + } + if (n > 0) { + w.commit(n); + } else { + // Direct write didn't work. Unfortunately need to write to a temporary + // buffer or the sizing isn't correct if @a w is clipped because @c + // strftime returns 0 if the buffer isn't large enough. + char buff[256]; // hope for the best - no real way to resize appropriately on failure. + n = strftime(buff, sizeof(buff), date._fmt.data(), &t); + w.write(buff, n); + } + } + return w; +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::OptionalAffix const &opts) +{ + return w.write(opts._prefix).write(opts._text).write(opts._suffix); +} + +BufferWriter & +bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern) +{ + auto limit = std::min(spec._max, pattern._text.size() * pattern._n); + decltype(limit) n = 0; + while (n < limit) { + w.write(pattern._text); + n += pattern._text.size(); + } + return w; +} + +} // namespace ts + +namespace std +{ +ostream & +operator<<(ostream &s, ts::FixedBufferWriter &w) +{ + return s << w.view(); +} +} // namespace std + +// --- C_Format / printf support ---- + +namespace ts +{ +namespace bwf +{ + void + C_Format::capture(BufferWriter &w, Spec const &spec, std::any const &value) + { + unsigned v; + if (typeid(int *) == value.type()) + v = static_cast(*std::any_cast(value)); + else if (typeid(unsigned *) == value.type()) + v = *std::any_cast(value); + else if (typeid(size_t *) == value.type()) + v = static_cast(*std::any_cast(value)); + else + return; + + if (spec._ext == "w") + _saved._min = v; + if (spec._ext == "p") { + _saved._prec = v; + } + } + + bool + C_Format::operator()(std::string_view &literal, Spec &spec) + { + TextView parsed; + + // clean up any old business from a previous specifier. + if (_prec_p) { + spec._type = Spec::CAPTURE_TYPE; + spec._ext = "p"; + _prec_p = false; + return true; + } else if (_saved_p) { + spec = _saved; + _saved_p = false; + return true; + } + + if (!_fmt.empty()) { + bool width_p = false; + literal = _fmt.take_prefix_at('%'); + if (_fmt.empty()) { + return false; + } + if (!_fmt.empty()) { + if ('%' == *_fmt) { + literal = {literal.data(), literal.size() + 1}; + ++_fmt; + return false; + } + } + + spec._align = Spec::Align::RIGHT; // default unless overridden. + do { + char c = *_fmt; + if ('-' == c) { + spec._align = Spec::Align::LEFT; + } else if ('+' == c) { + spec._sign = Spec::SIGN_ALWAYS; + } else if (' ' == c) { + spec._sign = Spec::SIGN_NEVER; + } else if ('#' == c) { + spec._radix_lead_p = true; + } else if ('0' == c) { + spec._fill = '0'; + } else { + break; + } + ++_fmt; + } while (!_fmt.empty()); + + if (_fmt.empty()) { + literal = TextView{literal.data(), _fmt.data()}; + return false; + } + + if ('*' == *_fmt) { + width_p = true; // signal need to capture width. + ++_fmt; + } else { + auto width = radix10(_fmt, parsed); + if (!parsed.empty()) { + spec._min = width; + } + } + + if ('.' == *_fmt) { + ++_fmt; + if ('*' == *_fmt) { + _prec_p = true; + ++_fmt; + } else { + auto x = radix10(_fmt, parsed); + if (!parsed.empty()) { + spec._prec = x; + } else { + spec._prec = 0; + } + } + } + + if (_fmt.empty()) { + literal = TextView{literal.data(), _fmt.data()}; + return false; + } + + char c = *_fmt++; + // strip length modifiers. + if ('l' == c || 'h' == c) + c = *_fmt++; + if ('l' == c || 'z' == c || 'j' == c || 't' == c || 'h' == c) + c = *_fmt++; + + switch (c) { + case 'c': + spec._type = c; + break; + case 'i': + case 'd': + case 'j': + case 'z': + spec._type = 'd'; + break; + case 'x': + case 'X': + spec._type = c; + break; + case 'f': + spec._type = 'f'; + break; + case 's': + spec._type = 's'; + break; + case 'p': + spec._type = c; + break; + default: + literal = TextView{literal.data(), _fmt.data()}; + return false; + } + if (width_p || _prec_p) { + _saved_p = true; + _saved = spec; + spec = Spec::DEFAULT; + if (width_p) { + spec._type = Spec::CAPTURE_TYPE; + spec._ext = "w"; + } else if (_prec_p) { + _prec_p = false; + spec._type = Spec::CAPTURE_TYPE; + spec._ext = "p"; + } + } + return true; + } + return false; + } + +} // namespace bwf +} // namespace ts diff --git a/src/tscore/unit_tests/test_BufferWriter.cc b/src/tscpp/util/unit_tests/test_BufferWriter.cc similarity index 99% rename from src/tscore/unit_tests/test_BufferWriter.cc rename to src/tscpp/util/unit_tests/test_BufferWriter.cc index 52678b9aad6..c1d900b617d 100644 --- a/src/tscore/unit_tests/test_BufferWriter.cc +++ b/src/tscpp/util/unit_tests/test_BufferWriter.cc @@ -22,7 +22,7 @@ */ #include "catch.hpp" -#include "tscore/BufferWriter.h" +#include "tscpp/util/BufferWriter.h" #include namespace diff --git a/src/tscore/unit_tests/test_IntrusiveDList.cc b/src/tscpp/util/unit_tests/test_IntrusiveDList.cc similarity index 85% rename from src/tscore/unit_tests/test_IntrusiveDList.cc rename to src/tscpp/util/unit_tests/test_IntrusiveDList.cc index 2a057ba30ad..a5ba49d9873 100644 --- a/src/tscore/unit_tests/test_IntrusiveDList.cc +++ b/src/tscpp/util/unit_tests/test_IntrusiveDList.cc @@ -4,20 +4,17 @@ @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 + 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 + 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. */ @@ -26,10 +23,12 @@ #include #include -#include "tscore/IntrusiveDList.h" -#include "tscore/BufferWriter.h" +#include "tscpp/util/IntrusiveDList.h" +#include "tscpp/util/bwf_base.h" -#include +#include "catch.hpp" + +using ts::IntrusiveDList; // -------------------- // Code for documentation - placed here to guarantee the examples at least compile. @@ -82,7 +81,7 @@ Message::is_in_list() const class Container { using self_type = Container; - using MessageList = IntrusiveDList; + using MessageList = ts::IntrusiveDList; public: ~Container(); @@ -147,7 +146,7 @@ Container::print() const } } -TEST_CASE("IntrusiveDList Example", "[libts][IntrusiveDList]") +TEST_CASE("IntrusiveDList Example", "[[libtscpputil]][IntrusiveDList]") { Container container; @@ -210,10 +209,10 @@ class PrivateThing : protected Thing // If any lines above here are changed, the documentation must be updated. // -------------------- -using ThingList = IntrusiveDList; -using PrivateThingList = IntrusiveDList; +using ThingList = ts::IntrusiveDList; +using PrivateThingList = ts::IntrusiveDList; -TEST_CASE("IntrusiveDList", "[libts][IntrusiveDList]") +TEST_CASE("IntrusiveDList", "[[libtscpputil]][IntrusiveDList]") { ThingList list; int n; diff --git a/src/tscore/unit_tests/test_IntrusiveHashMap.cc b/src/tscpp/util/unit_tests/test_IntrusiveHashMap.cc similarity index 87% rename from src/tscore/unit_tests/test_IntrusiveHashMap.cc rename to src/tscpp/util/unit_tests/test_IntrusiveHashMap.cc index a0521a27a21..9cdbeb45365 100644 --- a/src/tscore/unit_tests/test_IntrusiveHashMap.cc +++ b/src/tscpp/util/unit_tests/test_IntrusiveHashMap.cc @@ -4,20 +4,17 @@ @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 + 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 + 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. */ @@ -26,9 +23,12 @@ #include #include #include -#include -#include -#include "../../../tests/include/catch.hpp" + +#include "tscpp/util/IntrusiveHashMap.h" +#include "tscpp/util/bwf_base.h" +#include "catch.hpp" + +using ts::IntrusiveHashMap; // ------------- // --- TESTS --- diff --git a/src/tscore/unit_tests/test_MemArena.cc b/src/tscpp/util/unit_tests/test_MemArena.cc similarity index 80% rename from src/tscore/unit_tests/test_MemArena.cc rename to src/tscpp/util/unit_tests/test_MemArena.cc index 3492c9964d8..b93993e6e52 100644 --- a/src/tscore/unit_tests/test_MemArena.cc +++ b/src/tscpp/util/unit_tests/test_MemArena.cc @@ -4,32 +4,29 @@ @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 + 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 + 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 - #include -#include "tscore/MemArena.h" +#include "tscpp/util/MemArena.h" +#include "catch.hpp" + using ts::MemSpan; using ts::MemArena; using namespace std::literals; -TEST_CASE("MemArena generic", "[libts][MemArena]") +TEST_CASE("MemArena generic", "[[libtscpputil]][MemArena]") { ts::MemArena arena{64}; REQUIRE(arena.size() == 0); @@ -37,9 +34,11 @@ TEST_CASE("MemArena generic", "[libts][MemArena]") arena.alloc(0); REQUIRE(arena.size() == 0); REQUIRE(arena.reserved_size() >= 64); + REQUIRE(arena.remaining() >= 64); ts::MemSpan span1 = arena.alloc(32); REQUIRE(span1.size() == 32); + REQUIRE(arena.remaining() >= 32); ts::MemSpan span2 = arena.alloc(32); REQUIRE(span2.size() == 32); @@ -52,7 +51,7 @@ TEST_CASE("MemArena generic", "[libts][MemArena]") REQUIRE(extent < arena.reserved_size()); } -TEST_CASE("MemArena freeze and thaw", "[libts][MemArena]") +TEST_CASE("MemArena freeze and thaw", "[[libtscpputil]][MemArena]") { MemArena arena; MemSpan span1{arena.alloc(1024)}; @@ -114,7 +113,7 @@ TEST_CASE("MemArena freeze and thaw", "[libts][MemArena]") REQUIRE(arena.reserved_size() < 2 * 32000); } -TEST_CASE("MemArena helper", "[libts][MemArena]") +TEST_CASE("MemArena helper", "[[libtscpputil]][MemArena]") { struct Thing { int ten{10}; @@ -131,6 +130,7 @@ TEST_CASE("MemArena helper", "[libts][MemArena]") REQUIRE(arena.size() == 0); ts::MemSpan s = arena.alloc(56); REQUIRE(arena.size() == 56); + REQUIRE(arena.remaining() >= 200); void *ptr = s.begin(); REQUIRE(arena.contains((char *)ptr)); @@ -177,7 +177,7 @@ TEST_CASE("MemArena helper", "[libts][MemArena]") REQUIRE(thing_one->name == "Persia"); } -TEST_CASE("MemArena large alloc", "[libts][MemArena]") +TEST_CASE("MemArena large alloc", "[[libtscpputil]][MemArena]") { ts::MemArena arena; ts::MemSpan s = arena.alloc(4000); @@ -204,7 +204,7 @@ TEST_CASE("MemArena large alloc", "[libts][MemArena]") } } -TEST_CASE("MemArena block allocation", "[libts][MemArena]") +TEST_CASE("MemArena block allocation", "[[libtscpputil]][MemArena]") { ts::MemArena arena{64}; ts::MemSpan s = arena.alloc(32); @@ -227,7 +227,7 @@ TEST_CASE("MemArena block allocation", "[libts][MemArena]") REQUIRE((char *)s.begin() + 64 == s3.end()); } -TEST_CASE("MemArena full blocks", "[libts][MemArena]") +TEST_CASE("MemArena full blocks", "[[libtscpputil]][MemArena]") { // couple of large allocations - should be exactly sized in the generation. size_t init_size = 32000; @@ -250,3 +250,17 @@ TEST_CASE("MemArena full blocks", "[libts][MemArena]") REQUIRE(std::all_of(m2.begin(), m2.end(), [](uint8_t c) { return 0xc2 == c; })); REQUIRE(std::all_of(m3.begin(), m3.end(), [](uint8_t c) { return 0x56 == c; })); } + +TEST_CASE("MemArena esoterica", "[[libtscpputil]][MemArena]") +{ + MemArena a1; + MemSpan span; + { + MemArena a2{512}; + span = a2.alloc(128); + REQUIRE(a2.contains(span.data())); + a1 = std::move(a2); + } + REQUIRE(a1.contains(span.data())); + REQUIRE(a1.remaining() >= 384); +} diff --git a/src/tscore/unit_tests/test_Scalar.cc b/src/tscpp/util/unit_tests/test_Scalar.cc similarity index 72% rename from src/tscore/unit_tests/test_Scalar.cc rename to src/tscpp/util/unit_tests/test_Scalar.cc index 3f8ac69ba9b..7de5f5bdd6e 100644 --- a/src/tscore/unit_tests/test_Scalar.cc +++ b/src/tscpp/util/unit_tests/test_Scalar.cc @@ -4,28 +4,23 @@ @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 + 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 + 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 -#include "tscore/Scalar.h" -//#include -//#include -//#include +#include "tscpp/util/Scalar.h" +#include "tscpp/util/bwf_base.h" +#include "catch.hpp" using Bytes = ts::Scalar<1, off_t>; using Paragraphs = ts::Scalar<16, off_t>; @@ -228,58 +223,21 @@ TEST_CASE("Scalar Arithmetic", "[libts][Scalar][arithmetic]") REQUIRE(kb >= b); } -#if 0 struct KBytes_tag { - static std::string const label; + static constexpr std::string_view label{" bytes"}; }; -std::string const KBytes_tag::label(" bytes"); -void -Test_IO() +TEST_CASE("Scalar Formatting", "[libts][Scalar][bwf]") { - typedef ts::Scalar<1024, long int, KBytes_tag> KBytes; - typedef ts::Scalar<1024, int> KiBytes; + using KBytes = ts::Scalar<1024, long int, KBytes_tag>; + using KiBytes = ts::Scalar<1000, int>; KBytes x(12); KiBytes y(12); + ts::LocalBufferWriter<128> w; - std::cout << "Testing" << std::endl; - std::cout << "x is " << x << std::endl; - std::cout << "y is " << y << std::endl; + w.print("x is {}", x); + REQUIRE(w.view() == "x is 12288 bytes"); + w.clear().print("y is {}", y); + REQUIRE(w.view() == "y is 12000"); } - -void -test_Compile() -{ - // These tests aren't normally run, they exist to detect compiler issues. - - typedef ts::Scalar<1024, short> KBytes; - typedef ts::Scalar<1024, int> KiBytes; - int delta = 10; - - KBytes x(12); - KiBytes y(12); - - if (x > 12) { - std::cout << "Operator > works" << std::endl; - } - if (y > 12) { - std::cout << "Operator > works" << std::endl; - } - - (void)(x.inc(10)); - (void)(x.inc(static_cast(10))); - (void)(x.inc(static_cast(10))); - (void)(x.inc(delta)); - (void)(y.inc(10)); - (void)(y.inc(static_cast(10))); - (void)(y.inc(static_cast(10))); - (void)(y.inc(delta)); - - (void)(x.dec(10)); - (void)(x.dec(static_cast(10))); - (void)(x.dec(static_cast(10))); - (void)(x.dec(delta)); -} - -#endif diff --git a/src/tscore/unit_tests/test_BufferWriterFormat.cc b/src/tscpp/util/unit_tests/test_bw_format.cc similarity index 59% rename from src/tscore/unit_tests/test_BufferWriterFormat.cc rename to src/tscpp/util/unit_tests/test_bw_format.cc index e8dee671a08..9a4b16b5eef 100644 --- a/src/tscore/unit_tests/test_BufferWriterFormat.cc +++ b/src/tscpp/util/unit_tests/test_bw_format.cc @@ -4,34 +4,34 @@ @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 + 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 + 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 "../../../tests/include/catch.hpp" #include #include -#include "tscore/BufferWriter.h" -#include "tscore/bwf_std_format.h" +#include + #include "tscpp/util/MemSpan.h" -#include "tscore/INK_MD5.h" -#include "tscore/CryptoHash.h" +#include "tscpp/util/BufferWriter.h" +#include "tscpp/util/bwf_std.h" +#include "tscpp/util/bwf_ex.h" +#include "tscpp/util/bwf_printf.h" + +#include "catch.hpp" using namespace std::literals; +using ts::TextView; TEST_CASE("Buffer Writer << operator", "[bufferwriter][stream]") { @@ -41,7 +41,7 @@ TEST_CASE("Buffer Writer << operator", "[bufferwriter][stream]") REQUIRE(bw.view() == "The quick brown fox"); - bw.reduce(0); + bw.clear(); bw << "x=" << bw.capacity(); REQUIRE(bw.view() == "x=50"); } @@ -53,187 +53,188 @@ TEST_CASE("bwprint basics", "[bwprint]") bw.print(fmt1); REQUIRE(bw.view() == fmt1); - bw.reduce(0); + bw.clear(); bw.print("Arg {}", 1); REQUIRE(bw.view() == "Arg 1"); - bw.reduce(0); + bw.clear(); 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.clear(); bw.print("args {2}{0}{1}", "zero", "one", "two"); REQUIRE(bw.view() == "args twozeroone"); - bw.reduce(0); + bw.clear(); bw.print("left |{:<10}|", "text"); REQUIRE(bw.view() == "left |text |"); - bw.reduce(0); + bw.clear(); bw.print("right |{:>10}|", "text"); REQUIRE(bw.view() == "right | text|"); - bw.reduce(0); + bw.clear(); bw.print("right |{:.>10}|", "text"); REQUIRE(bw.view() == "right |......text|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:.^10}|", "text"); REQUIRE(bw.view() == "center |...text...|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:.^11}|", "text"); REQUIRE(bw.view() == "center |...text....|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:^^10}|", "text"); REQUIRE(bw.view() == "center |^^^text^^^|"); - bw.reduce(0); + bw.clear(); bw.print("center |{:%3A^10}|", "text"); REQUIRE(bw.view() == "center |:::text:::|"); - bw.reduce(0); + bw.clear(); 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.clear(); bw.print("Format |{:>#010x}|", -956); REQUIRE(bw.view() == "Format |0000-0x3bc|"); - bw.reduce(0); + bw.clear(); bw.print("Format |{:<#010x}|", -956); REQUIRE(bw.view() == "Format |-0x3bc0000|"); - bw.reduce(0); + bw.clear(); bw.print("Format |{:#010x}|", -956); REQUIRE(bw.view() == "Format |-0x00003bc|"); - bw.reduce(0); + bw.clear(); 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.clear(); + bw.print("Arg {0} Arg {3}", 0, 1); + REQUIRE(bw.view() == "Arg 0 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.clear().print("{{stuff}} Arg {0} Arg {}", 0, 1, 2); + REQUIRE(bw.view() == "{stuff} Arg 0 Arg 0"); + bw.clear().print("{{stuff}} Arg {0} Arg {} {}", 0, 1, 2); + REQUIRE(bw.view() == "{stuff} Arg 0 Arg 0 1"); + bw.clear(); 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 3 Arg 3 and {stuff}"); + bw.clear().print("Arg {{{0}}} Arg {} and {{stuff}}", 5, 6); + REQUIRE(bw.view() == "Arg {5} Arg 5 and {stuff}"); + bw.clear().print("Arg {{{0}}} Arg {} {1} {} {0} and {{stuff}}", 5, 6); + REQUIRE(bw.view() == "Arg {5} Arg 5 6 6 5 and {stuff}"); + bw.clear(); + bw.print("Arg {0} Arg {{}}{{}} {} and {} {{stuff}}", 7, 8); + REQUIRE(bw.view() == "Arg 7 Arg {}{} 7 and 8 {stuff}"); + bw.clear(); + bw.print("Arg {} Arg {{{{}}}} {} {1} {0}", 9, 10); + REQUIRE(bw.view() == "Arg 9 Arg {{}} 10 10 9"); + + bw.clear(); + bw.print("Arg {} Arg {{{{}}}} {}", 9, 10); REQUIRE(bw.view() == "Arg 9 Arg {{}} 10"); + bw.clear(); - bw.reduce(0); - bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10); - REQUIRE(bw.view() == "Arg 9 Arg {{}} 10"); - bw.reduce(0); - - bw.reset().print("{leif}"); + bw.clear().print("{leif}"); REQUIRE(bw.view() == "{~leif~}"); // expected to be missing. - bw.reset().print("Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}\n", 31267); - // std::cout << bw; - /* - std::cout << ts::LocalBufferWriter<256>().print( - "Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}{}", 31267, '\n'); - */ + // bw.clear().print("Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} !{0}", 31267); + // REQUIRE(ts::TextView(bw.view()).take_suffix_at('!') == "31267"); } TEST_CASE("BWFormat numerics", "[bwprint][bwformat]") { ts::LocalBufferWriter<256> bw; - ts::BWFormat fmt("left >{0:<9}< right >{0:>9}< center >{0:^9}<"); + ts::bwf::Format fmt("left >{0:<9}< right >{0:>9}< center >{0:^9}<"); std::string_view text{"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"}; - bw.reduce(0); - static const ts::BWFormat bad_arg_fmt{"{{BAD_ARG_INDEX:{} of {}}}"}; + bw.clear(); + static const ts::bwf::Format 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.clear(); 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); + bw.clear().print("Text: _{0:20.10}_", text); + REQUIRE(bw.view() == "Text: _0123456789 _"); + bw.clear().print("Text: _{0:>20.10}_", text); + REQUIRE(bw.view() == "Text: _ 0123456789_"); + bw.clear().print("Text: _{0:-<20.10,20}_", text.substr(52)); REQUIRE(bw.view() == "Text: _QRSTUVWXYZ----------_"); void *ptr = reinterpret_cast(0XBADD0956); - bw.reduce(0); + bw.clear(); bw.print("{}", ptr); REQUIRE(bw.view() == "0xbadd0956"); - bw.reduce(0); + bw.clear(); bw.print("{:X}", ptr); REQUIRE(bw.view() == "0XBADD0956"); int *int_ptr = static_cast(ptr); - bw.reduce(0); + bw.clear(); bw.print("{}", int_ptr); REQUIRE(bw.view() == "0xbadd0956"); auto char_ptr = "good"; - bw.reduce(0); + bw.clear(); bw.print("{:x}", static_cast(ptr)); REQUIRE(bw.view() == "0xbadd0956"); - bw.reduce(0); + bw.clear(); bw.print("{}", char_ptr); REQUIRE(bw.view() == "good"); ts::MemSpan span{ptr, 0x200}; - bw.reduce(0); + bw.clear(); bw.print("{}", span); REQUIRE(bw.view() == "0x200@0xbadd0956"); - bw.reduce(0); + bw.clear(); bw.print("{::d}", ts::MemSpan(const_cast(char_ptr), 4)); REQUIRE(bw.view() == "676f6f64"); - bw.reduce(0); + bw.clear(); bw.print("{:#:d}", ts::MemSpan(const_cast(char_ptr), 4)); REQUIRE(bw.view() == "0x676f6f64"); std::string_view sv{"abc123"}; - bw.reduce(0); + bw.clear(); bw.print("{}", sv); REQUIRE(bw.view() == sv); - bw.reduce(0); + bw.clear(); bw.print("{:x}", sv); REQUIRE(bw.view() == "616263313233"); - bw.reduce(0); + bw.clear(); bw.print("{:#x}", sv); REQUIRE(bw.view() == "0x616263313233"); - bw.reduce(0); + bw.clear(); bw.print("|{:16x}|", sv); REQUIRE(bw.view() == "|616263313233 |"); - bw.reduce(0); + bw.clear(); bw.print("|{:>16x}|", sv); REQUIRE(bw.view() == "| 616263313233|"); - bw.reduce(0); + bw.clear(); bw.print("|{:^16x}|", sv); REQUIRE(bw.view() == "| 616263313233 |"); - bw.reduce(0); + bw.clear(); bw.print("|{:>16.2x}|", sv); - REQUIRE(bw.view() == "| 63313233|"); - bw.reduce(0); - bw.print("|{:<0.2,5x}|", sv); - REQUIRE(bw.view() == "|63313|"); - bw.reset().print("|{:<.2,5x}|", sv); - REQUIRE(bw.view() == "|63313|"); - - bw.reduce(0); + REQUIRE(bw.view() == "| 6162|"); + bw.clear().print("|{:<0.4,7x}|", sv); + REQUIRE(bw.view() == "|6162633|"); + bw.clear().print("|{:<5.2,7x}|", sv); + REQUIRE(bw.view() == "|6162 |"); + bw.clear().print("|{:<5.3,7x}|", sv); + REQUIRE(bw.view() == "|616263|"); + bw.clear().print("|{:<7.3x}|", sv); + REQUIRE(bw.view() == "|616263 |"); + + bw.clear(); bw.print("|{}|", true); REQUIRE(bw.view() == "|1|"); - bw.reduce(0); + bw.clear(); bw.print("|{}|", false); REQUIRE(bw.view() == "|0|"); - bw.reduce(0); + bw.clear(); bw.print("|{:s}|", true); REQUIRE(bw.view() == "|true|"); - bw.reduce(0); + bw.clear(); bw.print("|{:S}|", false); REQUIRE(bw.view() == "|FALSE|"); - bw.reduce(0); + bw.clear(); bw.print("|{:>9s}|", false); REQUIRE(bw.view() == "| false|"); - bw.reduce(0); + bw.clear(); bw.print("|{:^10s}|", true); REQUIRE(bw.view() == "| true |"); @@ -241,22 +242,13 @@ TEST_CASE("BWFormat numerics", "[bwprint][bwformat]") ts::LocalBufferWriter<20> bw20; bw20.print("0123456789abc|{:^10s}|", true); REQUIRE(bw20.view() == "0123456789abc| tru"); - bw20.reduce(0); + bw20.clear(); bw20.print("012345|{:^10s}|6789abc", true); REQUIRE(bw20.view() == "012345| true |67"); - INK_MD5 md5; - bw.reduce(0); - bw.print("{}", md5); - REQUIRE(bw.view() == "00000000000000000000000000000000"); - CryptoContext().hash_immediate(md5, sv.data(), sv.size()); - bw.reduce(0); - bw.print("{}", md5); - REQUIRE(bw.view() == "e99a18c428cb38d5f260853678922e03"); - - bw.reset().print("Char '{}'", 'a'); + bw.clear().print("Char '{}'", 'a'); REQUIRE(bw.view() == "Char 'a'"); - bw.reset().print("Byte '{}'", uint8_t{'a'}); + bw.clear().print("Byte '{}'", uint8_t{'a'}); REQUIRE(bw.view() == "Byte '97'"); } @@ -280,7 +272,7 @@ TEST_CASE("bwstring", "[bwprint][bwstring]") char buff[128]; snprintf(buff, sizeof(buff), "|%s|", bw.print("Deep Silent Complete by {}\0", "Nightwish"sv).data()); REQUIRE(std::string_view(buff) == "|Deep Silent Complete by Nightwish|"); - snprintf(buff, sizeof(buff), "|%s|", bw.reset().print("Deep Silent Complete by {}\0elided junk", "Nightwish"sv).data()); + snprintf(buff, sizeof(buff), "|%s|", bw.clear().print("Deep Silent Complete by {}\0elided junk", "Nightwish"sv).data()); REQUIRE(std::string_view(buff) == "|Deep Silent Complete by Nightwish|"); // Special tests for clang analyzer failures - special asserts are needed to make it happy but @@ -311,51 +303,51 @@ TEST_CASE("bwstring", "[bwprint][bwstring]") TEST_CASE("BWFormat integral", "[bwprint][bwformat]") { ts::LocalBufferWriter<256> bw; - ts::BWFSpec spec; + ts::bwf::Spec spec; uint32_t num = 30; int num_neg = -30; // basic bwformat(bw, spec, num); REQUIRE(bw.view() == "30"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, num_neg); REQUIRE(bw.view() == "-30"); - bw.reduce(0); + bw.clear(); // radix - ts::BWFSpec spec_hex; + ts::bwf::Spec spec_hex; spec_hex._radix_lead_p = true; spec_hex._type = 'x'; bwformat(bw, spec_hex, num); REQUIRE(bw.view() == "0x1e"); - bw.reduce(0); + bw.clear(); - ts::BWFSpec spec_dec; + ts::bwf::Spec spec_dec; spec_dec._type = '0'; bwformat(bw, spec_dec, num); REQUIRE(bw.view() == "30"); - bw.reduce(0); + bw.clear(); - ts::BWFSpec spec_bin; + ts::bwf::Spec spec_bin; spec_bin._radix_lead_p = true; spec_bin._type = 'b'; bwformat(bw, spec_bin, num); REQUIRE(bw.view() == "0b11110"); - bw.reduce(0); + bw.clear(); int one = 1; int two = 2; int three_n = -3; // alignment - ts::BWFSpec left; - left._align = ts::BWFSpec::Align::LEFT; + ts::bwf::Spec left; + left._align = ts::bwf::Spec::Align::LEFT; left._min = 5; - ts::BWFSpec right; - right._align = ts::BWFSpec::Align::RIGHT; + ts::bwf::Spec right; + right._align = ts::bwf::Spec::Align::RIGHT; right._min = 5; - ts::BWFSpec center; - center._align = ts::BWFSpec::Align::CENTER; + ts::bwf::Spec center; + center._align = ts::bwf::Spec::Align::CENTER; center._min = 5; bwformat(bw, left, one); @@ -367,105 +359,105 @@ TEST_CASE("BWFormat integral", "[bwprint][bwformat]") REQUIRE(bw.view() == "1 2 2 -3 "); std::atomic ax{0}; - bw.reset().print("ax == {}", ax); + bw.clear().print("ax == {}", ax); REQUIRE(bw.view() == "ax == 0"); ++ax; - bw.reset().print("ax == {}", ax); + bw.clear().print("ax == {}", ax); REQUIRE(bw.view() == "ax == 1"); } TEST_CASE("BWFormat floating", "[bwprint][bwformat]") { ts::LocalBufferWriter<256> bw; - ts::BWFSpec spec; + ts::bwf::Spec spec; - bw.reduce(0); + bw.clear(); bw.print("{}", 3.14); REQUIRE(bw.view() == "3.14"); - bw.reduce(0); + bw.clear(); bw.print("{} {:.2} {:.0} ", 32.7, 32.7, 32.7); REQUIRE(bw.view() == "32.70 32.70 32 "); - bw.reduce(0); + bw.clear(); bw.print("{} neg {:.3}", -123.2, -123.2); REQUIRE(bw.view() == "-123.20 neg -123.200"); - bw.reduce(0); + bw.clear(); bw.print("zero {} quarter {} half {} 3/4 {}", 0, 0.25, 0.50, 0.75); REQUIRE(bw.view() == "zero 0 quarter 0.25 half 0.50 3/4 0.75"); - bw.reduce(0); + bw.clear(); bw.print("long {:.11}", 64.9); REQUIRE(bw.view() == "long 64.90000000000"); - bw.reduce(0); + bw.clear(); double n = 180.278; double neg = -238.47; bwformat(bw, spec, n); REQUIRE(bw.view() == "180.28"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, neg); REQUIRE(bw.view() == "-238.47"); - bw.reduce(0); + bw.clear(); spec._prec = 5; bwformat(bw, spec, n); REQUIRE(bw.view() == "180.27800"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, neg); REQUIRE(bw.view() == "-238.47000"); - bw.reduce(0); + bw.clear(); float f = 1234; float fneg = -1; bwformat(bw, spec, f); REQUIRE(bw.view() == "1234"); - bw.reduce(0); + bw.clear(); bwformat(bw, spec, fneg); REQUIRE(bw.view() == "-1"); - bw.reduce(0); + bw.clear(); f = 1234.5667; spec._prec = 4; bwformat(bw, spec, f); REQUIRE(bw.view() == "1234.5667"); - bw.reduce(0); + bw.clear(); bw << 1234 << .567; REQUIRE(bw.view() == "12340.57"); - bw.reduce(0); + bw.clear(); bw << f; REQUIRE(bw.view() == "1234.57"); - bw.reduce(0); + bw.clear(); bw << n; REQUIRE(bw.view() == "180.28"); - bw.reduce(0); + bw.clear(); bw << f << n; REQUIRE(bw.view() == "1234.57180.28"); - bw.reduce(0); + bw.clear(); double edge = 0.345; spec._prec = 3; bwformat(bw, spec, edge); REQUIRE(bw.view() == "0.345"); - bw.reduce(0); + bw.clear(); edge = .1234; bwformat(bw, spec, edge); REQUIRE(bw.view() == "0.123"); - bw.reduce(0); + bw.clear(); edge = 1.0; bwformat(bw, spec, edge); REQUIRE(bw.view() == "1"); - bw.reduce(0); + bw.clear(); // alignment double first = 1.23; double second = 2.35; double third = -3.5; - ts::BWFSpec left; - left._align = ts::BWFSpec::Align::LEFT; + ts::bwf::Spec left; + left._align = ts::bwf::Spec::Align::LEFT; left._min = 5; - ts::BWFSpec right; - right._align = ts::BWFSpec::Align::RIGHT; + ts::bwf::Spec right; + right._align = ts::bwf::Spec::Align::RIGHT; right._min = 5; - ts::BWFSpec center; - center._align = ts::BWFSpec::Align::CENTER; + ts::bwf::Spec center; + center._align = ts::bwf::Spec::Align::CENTER; center._min = 5; bwformat(bw, left, first); @@ -475,99 +467,209 @@ TEST_CASE("BWFormat floating", "[bwprint][bwformat]") REQUIRE(bw.view() == "1.23 2.35 2.35"); bwformat(bw, center, third); REQUIRE(bw.view() == "1.23 2.35 2.35-3.50"); - bw.reduce(0); + bw.clear(); double over = 1.4444444; - ts::BWFSpec over_min; + ts::bwf::Spec over_min; over_min._prec = 7; over_min._min = 5; bwformat(bw, over_min, over); REQUIRE(bw.view() == "1.4444444"); - bw.reduce(0); + bw.clear(); // Edge bw.print("{}", (1.0 / 0.0)); REQUIRE(bw.view() == "Inf"); - bw.reduce(0); + bw.clear(); double inf = std::numeric_limits::infinity(); bw.print(" {} ", inf); REQUIRE(bw.view() == " Inf "); - bw.reduce(0); + bw.clear(); double nan_1 = std::nan("1"); bw.print("{} {}", nan_1, nan_1); REQUIRE(bw.view() == "NaN NaN"); - bw.reduce(0); + bw.clear(); double z = 0.0; bw.print("{} ", z); REQUIRE(bw.view() == "0 "); - bw.reduce(0); + bw.clear(); } -TEST_CASE("bwstring std formats", "[libts][bwprint]") +TEST_CASE("bwstring std formats", "[[libtscpputil]][bwprint]") { ts::LocalBufferWriter<120> w; w.print("{}", ts::bwf::Errno(13)); REQUIRE(w.view() == "EACCES: Permission denied [13]"sv); - w.reset().print("{}", ts::bwf::Errno(134)); + w.clear().print("{}", ts::bwf::Errno(134)); REQUIRE(w.view().substr(0, 22) == "Unknown: Unknown error"sv); time_t t = 1528484137; // default is GMT - w.reset().print("{} is {}", t, ts::bwf::Date(t)); + w.clear().print("{} is {}", t, ts::bwf::Date(t)); REQUIRE(w.view() == "1528484137 is 2018 Jun 08 18:55:37"); - w.reset().print("{} is {}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); + w.clear().print("{} is {}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 18.55.37"); // OK to be explicit - w.reset().print("{} is {::gmt}", t, ts::bwf::Date(t)); + w.clear().print("{} is {::gmt}", t, ts::bwf::Date(t)); REQUIRE(w.view() == "1528484137 is 2018 Jun 08 18:55:37"); - w.reset().print("{} is {::gmt}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); + w.clear().print("{} is {::gmt}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 18.55.37"); // Local time - set it to something specific or the test will be geographically sensitive. setenv("TZ", "CST6", 1); tzset(); - w.reset().print("{} is {::local}", t, ts::bwf::Date(t)); + w.clear().print("{} is {::local}", t, ts::bwf::Date(t)); REQUIRE(w.view() == "1528484137 is 2018 Jun 08 12:55:37"); - w.reset().print("{} is {::local}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); + w.clear().print("{} is {::local}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S")); REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 12.55.37"); // Verify these compile and run, not really much hope to check output. - w.reset().print("|{}| |{}|", ts::bwf::Date(), ts::bwf::Date("%a, %d %b %Y")); + w.clear().print("|{}| |{}|", ts::bwf::Date(), ts::bwf::Date("%a, %d %b %Y")); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("", "Evil Dave")); + w.clear().print("name = {}", ts::bwf::FirstOf("", "Evil Dave")); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf(nullptr, "Evil Dave")); + w.clear().print("name = {}", ts::bwf::FirstOf(nullptr, "Evil Dave")); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave", "Leif")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave", "Leif")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("Persia", nullptr, "Leif")); + w.clear().print("name = {}", ts::bwf::FirstOf("Persia", nullptr, "Leif")); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf("", nullptr, "Leif")); + w.clear().print("name = {}", ts::bwf::FirstOf("", nullptr, "Leif")); REQUIRE(w.view() == "name = Leif"); const char *empty{nullptr}; std::string s1{"Persia"}; std::string_view s2{"Evil Dave"}; ts::TextView s3{"Leif"}; - w.reset().print("name = {}", ts::bwf::FirstOf(empty, s3)); + w.clear().print("name = {}", ts::bwf::FirstOf(empty, s3)); REQUIRE(w.view() == "name = Leif"); - w.reset().print("name = {}", ts::bwf::FirstOf(s2, s3)); + w.clear().print("name = {}", ts::bwf::FirstOf(s2, s3)); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf(s1, empty, s2)); + w.clear().print("name = {}", ts::bwf::FirstOf(s1, empty, s2)); REQUIRE(w.view() == "name = Persia"); - w.reset().print("name = {}", ts::bwf::FirstOf(empty, s2, s1, s3)); + w.clear().print("name = {}", ts::bwf::FirstOf(empty, s2, s1, s3)); REQUIRE(w.view() == "name = Evil Dave"); - w.reset().print("name = {}", ts::bwf::FirstOf(empty, empty, s3, empty, s2, s1)); + w.clear().print("name = {}", ts::bwf::FirstOf(empty, empty, s3, empty, s2, s1)); REQUIRE(w.view() == "name = Leif"); } +// Test alternate format parsing. +struct AltFormatEx { + AltFormatEx(TextView fmt); + + explicit operator bool() const; + bool operator()(std::string_view &literal, ts::bwf::Spec &spec); + TextView _fmt; +}; + +AltFormatEx::AltFormatEx(TextView fmt) : _fmt{fmt} {} + +AltFormatEx::operator bool() const +{ + return !_fmt.empty(); +} + +bool +AltFormatEx::operator()(std::string_view &literal, ts::bwf::Spec &spec) +{ + if (_fmt.size()) { + literal = _fmt.split_prefix_at('%'); + if (literal.empty()) { + literal = _fmt; + _fmt.clear(); + return false; + } + + if (_fmt.size() >= 1) { + char c = _fmt[0]; + if (c == '%') { + literal = {literal.data(), literal.size() + 1}; + ++_fmt; + } else if (c == '<') { + size_t off = 0; + do { + off = _fmt.find('>', off + 1); + if (off == TextView::npos) { + throw std::invalid_argument("Unclosed leading angle bracket"); + } + } while (':' == _fmt[off - 1]); + spec.parse(_fmt.substr(1, off - 1)); + if (spec._name.empty()) { + throw std::invalid_argument("No name in specifier"); + } + _fmt.remove_prefix(off + 1); + return true; + } + } + } + return false; +} + +struct Header { + TextView + proto() const + { + return "ipv4"; + } + TextView + chi() const + { + return "10.56.128.96"; + } +}; + +using AltNames = ts::bwf::ContextNames
; + +TEST_CASE("bwf alternate", "[[libtscpputil]][bwf]") +{ + using BW = ts::BufferWriter; + using Spec = ts::bwf::Spec; + AltNames names; + Header hdr; + names.assign("proto", [](BW &w, Spec const &spec, Header &hdr) -> BW & { return ts::bwformat(w, spec, hdr.proto()); }); + names.assign("chi", [](BW &w, Spec const &spec, Header &hdr) -> BW & { return ts::bwformat(w, spec, hdr.chi()); }); + + ts::LocalBufferWriter<256> w; + w.print_nv(names.bind(hdr), AltFormatEx("This is chi - %")); + REQUIRE(w.view() == "This is chi - 10.56.128.96"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Use %% for a single")); + REQUIRE(w.view() == "Use % for a single"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Use %% for %, dig?")); + REQUIRE(w.view() == "Use % for ipv4, dig?"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Width |%| dig?")); + REQUIRE(w.view() == "Width |ipv4 | dig?"); + w.clear().print_nv(names.bind(hdr), AltFormatEx("Width |%10>| dig?")); + REQUIRE(w.view() == "Width | ipv4| dig?"); + + ts::bwprintf(w.clear(), "Fifty Six = %d", 56); + REQUIRE(w.view() == "Fifty Six = 56"); + ts::bwprintf(w.clear(), "int is %i", 101); + REQUIRE(w.view() == "int is 101"); + ts::bwprintf(w.clear(), "int is %zd", 102); + REQUIRE(w.view() == "int is 102"); + ts::bwprintf(w.clear(), "int is %ld", 103); + REQUIRE(w.view() == "int is 103"); + ts::bwprintf(w.clear(), "int is %s", 104); + REQUIRE(w.view() == "int is 104"); + ts::bwprintf(w.clear(), "int is %ld", -105); + REQUIRE(w.view() == "int is -105"); + + TextView digits{"0123456789"}; + ts::bwprintf(w.clear(), "Chars |%*s|", 12, digits); + REQUIRE(w.view() == "Chars | 0123456789|"); + ts::bwprintf(w.clear(), "Chars %.*s", 4, digits); + REQUIRE(w.view() == "Chars 0123"); + ts::bwprintf(w.clear(), "Chars |%*.*s|", 12, 5, digits); + REQUIRE(w.view() == "Chars | 01234|"); +} + // 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 @@ -583,25 +685,25 @@ TEST_CASE("bwperf", "[bwprint][performance]") static constexpr std::string_view text{"e99a18c428cb38d5f260853678922e03"sv}; ts::LocalBufferWriter<256> bw; - ts::BWFSpec spec; + ts::bwf::Spec spec; - bw.reduce(0); + bw.clear(); bw.print(fmt, -956, text); REQUIRE(bw.view() == "Format |-0x00003bc| 'e99a18c428cb38d5f260853678922e03'"); start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N_LOOPS; ++i) { - bw.reduce(0); + bw.clear(); bw.print(fmt, -956, text); } delta = std::chrono::high_resolution_clock::now() - start; std::cout << "bw.print() " << delta.count() << "ns or " << std::chrono::duration_cast(delta).count() << "ms" << std::endl; - ts::BWFormat pre_fmt(fmt); + ts::bwf::Format pre_fmt(fmt); start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N_LOOPS; ++i) { - bw.reduce(0); + bw.clear(); bw.print(pre_fmt, -956, text); } delta = std::chrono::high_resolution_clock::now() - start;