This is an automated email from the ASF dual-hosted git repository. bneradt pushed a commit to branch stuff-1 in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git
commit c8d15a23ce0c0759938f2dd80446da30a07d118d Author: Alan M. Carroll <[email protected]> AuthorDate: Mon May 16 07:15:16 2022 -0500 Add vectray, fix svto_radix, array constructor for MemSpan. --- code/CMakeLists.txt | 1 + code/include/swoc/MemSpan.h | 35 ++++ code/include/swoc/TextView.h | 68 +++++--- code/include/swoc/Vectray.h | 370 +++++++++++++++++++++++++++++++++++++++++++ doc/code/Vectray.en.rst | 64 ++++++++ doc/index.rst | 1 + unit_tests/CMakeLists.txt | 1 + unit_tests/ex_UnitParser.cc | 73 +++++---- unit_tests/test_TextView.cc | 30 +++- unit_tests/test_Vectray.cc | 84 ++++++++++ 10 files changed, 670 insertions(+), 57 deletions(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index a7f0656..855e435 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -26,6 +26,7 @@ set(HEADER_FILES include/swoc/swoc_file.h include/swoc/swoc_meta.h include/swoc/string_view.h + include/swoc/Vectray.h ) # These are external but required. diff --git a/code/include/swoc/MemSpan.h b/code/include/swoc/MemSpan.h index f5aad67..57482cd 100644 --- a/code/include/swoc/MemSpan.h +++ b/code/include/swoc/MemSpan.h @@ -13,6 +13,7 @@ #include <iosfwd> #include <iostream> #include <cstddef> +#include <array> #include <string_view> #include <type_traits> #include <ratio> @@ -72,6 +73,37 @@ public: */ template <size_t N> MemSpan(T (&a)[N]); + /** Construct from a @c std::array. + * + * @tparam N Array size. + * @param a Array instance. + */ + template <auto N> constexpr MemSpan(std::array<T, N> const& a); + + /** Construct from a @c std::array. + * + * @tparam N Array size. + * @param a Array instance. + */ + template <auto N> constexpr MemSpan(std::array<T, N> & a); + + /** Construct a span of constant values from a span of non-constant. + * + * @tparam U Span types. + * @tparam META Metaprogramming type to control conversion existence. + * @param that Source span. + * + * This enables the standard conversion from non-const to const. + */ + template < typename U + , typename META = std::enable_if_t< + std::conjunction_v< + std::is_const<T> + , std::is_same<U, std::remove_const_t<T>> + >>> + constexpr MemSpan(MemSpan<U> const& that) : _ptr(that.data()), _count(that.count()) {} + + /** Construct from nullptr. This implicitly makes the length 0. */ @@ -599,6 +631,9 @@ template <typename T> template <size_t N> MemSpan<T>::MemSpan(T (&a)[N]) : _ptr{ template <typename T> constexpr MemSpan<T>::MemSpan(std::nullptr_t) {} +template <typename T> template <auto N> constexpr MemSpan<T>::MemSpan(std::array<T,N> const& a) : _ptr{a.data()} , _count{a.size()} {} +template <typename T> template <auto N> constexpr MemSpan<T>::MemSpan(std::array<T,N> & a) : _ptr{a.data()} , _count{a.size()} {} + template <typename T> MemSpan<T> & MemSpan<T>::assign(T *ptr, size_t count) { diff --git a/code/include/swoc/TextView.h b/code/include/swoc/TextView.h index 2695129..255f9ad 100644 --- a/code/include/swoc/TextView.h +++ b/code/include/swoc/TextView.h @@ -68,7 +68,7 @@ public: * If @a n is @c npos then @c ptr is presumed to be a C string and checked for length. If @c ptr * is @c nullptr the length is 0. Otherwise @c strlen is used to calculate the length. */ - constexpr TextView(const char *ptr, size_t n) noexcept; + constexpr TextView(char const *ptr, size_t n) noexcept; /** Construct from a half open range [first, last). * @@ -113,7 +113,7 @@ public: * * @internal This is a reference because it is otherwise ambiguous with the array constructor. */ - TextView(char *&src) : super_type(src) {} + TextView(char * & src) : super_type(src) {} /** Construct from a const C-string. * @@ -121,7 +121,7 @@ public: * * @internal This is a reference because it is otherwise ambiguous with the array constructor. */ - TextView(char const *&src) : super_type(src) {} + TextView(char const * & src) : super_type(src) {} /** Construct from nullptr. This implicitly makes the length 0. @@ -155,7 +155,7 @@ public: * * @note @c c_str must be a null terminated string. The null byte is not included in the view. */ - self_type &assign(char *&c_str); + self_type &assign(char * & c_str); /** Assign a view of the @a c_str * @@ -164,7 +164,7 @@ public: * * @note @c c_str must be a null terminated string. The null byte is not included in the view. */ - self_type &assign(char const *&c_str); + self_type &assign(char const * & c_str); /// Explicitly set the start @a ptr and size @a n of the view. self_type &assign(char const *ptr, size_t n); @@ -180,6 +180,18 @@ public: /// Explicitly set the view from a @c std::string self_type &assign(std::string const &s); + /** Assign literal string or array. + + * All elements of the array are included in the view unless the last element is nul, in which case it is elided. + * If this is inappropriate then a constructor with an explicit size should be used. + * + * @code + * tv.assign("A literal string"); + * @endcode + * The last character in @a tv will be 'g'. + */ + template <size_t N> self_type & assign(const char (&s)[N]) noexcept; + /** Assign from any character container following STL standards. * * @tparam C Container type. @@ -893,30 +905,30 @@ uintmax_t svtou(TextView src, TextView *parsed = nullptr, int base = 0); * @return The converted numeric value. * * This is a specialized function useful only where conversion performance is critical. It is used - * inside @c svtoi for the common cases of 8, 10, and 16, therefore normally this isn't much more + * inside @c svtoi and @a svtou for the common cases of 8, 10, and 16, therefore normally this isn't much more * performant in those cases than just @c svtoi. Because of this only positive values are parsed. * If determining the radix from the text or signed value parsing is needed, used @c svtoi. * - * @a src is updated in place to indicate what characters were parsed. Parsing stops on the first - * invalid digit, so any leading non-digit characters (e.g. whitespace) must already be removed. - * @a src is updated in place by removing parsed characters. Parsing stops on the first invalid - * digit, so any leading non-digit characters (e.g. whitespace) must already be removed. Overflow - * is detected and the first digit that would overflow is not parsed, and the maximum value is - * returned. + * @a src is updated in place to indicate what characters were parsed by removing them from the view + * Parsing stops on the first invalid digit, so any leading non-digit characters (e.g. whitespace) + * must already be removed. For overflow, all valid digits are consumed and the maximum value returned. */ -template <int N> +template <int RADIX> uintmax_t svto_radix(swoc::TextView &src) { - static_assert(0 < N && N <= 36, "Radix must be in the range 1..36"); - uintmax_t zret{0}; + static_assert(0 < RADIX && RADIX <= 36, "Radix must be in the range 1..36"); + static constexpr auto MAX = std::numeric_limits<uintmax_t>::max(); + static constexpr auto OVERFLOW_LIMIT = MAX / RADIX; + uintmax_t zret = 0; int8_t v; - while (src.size() && (0 <= (v = swoc::svtoi_convert[uint8_t(*src)])) && v < N) { - auto n = zret * N + v; - if (n < zret) { // overflow / wrap - return std::numeric_limits<uintmax_t>::max(); + while (src.size() && (0 <= (v = swoc::svtoi_convert[uint8_t(*src)])) && v < RADIX) { + // Tweaked for performance - need to check range after @a RADIX multiply. + ++src; // Update view iff the character is parsed. + if (zret <= OVERFLOW_LIMIT && uintmax_t(v) <= MAX - (zret *= RADIX)) { + zret += v; + } else { + zret = MAX; // clamp to max - once set will always hit this case for subsequent input. } - zret = n; - ++src; } return zret; } @@ -1006,8 +1018,8 @@ TextView::operator+=(size_t n) { } template <size_t N> -inline TextView::self_type & -TextView::operator=(const char (&s)[N]) { +inline auto +TextView::operator=(const char (&s)[N]) -> self_type & { return *this = self_type{s, s[N - 1] ? N : N - 1}; } @@ -1036,12 +1048,12 @@ TextView::operator=(const std::string &s) { } inline TextView & -TextView::assign(char *&c_str) { +TextView::assign(char * & c_str) { return this->assign(c_str, strlen(c_str)); } inline TextView & -TextView::assign(char const *&c_str) { +TextView::assign(char const * & c_str) { return this->assign(c_str, strlen(c_str)); } @@ -1063,6 +1075,12 @@ TextView::assign(char const *b, char const *e) { return *this; } +template <size_t N> +inline auto +TextView::assign(char const (&s)[N]) noexcept -> self_type & { + return *this = self_type{s, s[N - 1] ? N : N - 1}; +} + inline constexpr TextView TextView::prefix(size_t n) const noexcept { return {this->data(), std::min(n, this->size())}; diff --git a/code/include/swoc/Vectray.h b/code/include/swoc/Vectray.h new file mode 100644 index 0000000..a446880 --- /dev/null +++ b/code/include/swoc/Vectray.h @@ -0,0 +1,370 @@ +#pragma once + +#include <array> +#include <vector> +#include <variant> +#include <new> +#include <cstddef> + +#include <swoc/MemSpan.h> +#include <swoc/swoc_meta.h> + +namespace swoc { inline namespace SWOC_VERSION_NS { + +/** Vectray provides a combination of static and dynamic storage modeled as an array. + * + * @tparam T Type of elements in the array. + * @tparam N Number of statically allocated elements. + * @tparam A Allocator. + * + * The goal is to provide static storage for the common case, avoiding memory allocation, while + * still handling exceptional cases that need more storage. A common case is for @a N == 1 where + * there is almost always a single value, but it is possible to have multiple values. @c Vectray + * makes the single value case require no allocation while transparently handling the multiple + * value case. + * + * The interface is designed to mimic that of @c std::vector. + */ +template < typename T, size_t N, class A = std::allocator<T> > +class Vectray { + using self_type = Vectray; ///< Self reference type. + using vector_type = std::vector<T, A>; + +public: // STL compliance types. + using value_type = T; + using allocator_type = A; + using size_type = typename vector_type::size_type; + using difference_type = typename vector_type::difference_type; + using iterator = typename swoc::MemSpan<T>::iterator; + using const_iterator = typename swoc::MemSpan<const T>::iterator; + +protected: + /// Internal (fixed) storage. + struct FixedStore { + std::array<std::byte, sizeof(T) * N> _raw; ///< Raw memory for element storage. + size_t _count = 0; ///< Number of valid elements. + allocator_type _a; ///< Allocator instance - used for construction. + + FixedStore() = default; ///< Default construct - empty. + + /** Construct with specific allocator @a a. + * + * @param a Allocator. + */ + explicit FixedStore(allocator_type const& a) : _a(a) {} + + ~FixedStore(); + + /// @return A span containing the valid elements. + MemSpan<T> span(); + }; + + using DynamicStore = vector_type; ///< Dynamic (heap) storage. + + /// Generic form for referencing stored objects. + using span = swoc::MemSpan<T>; + using const_span = swoc::MemSpan<T const>; + +public: + /// Default constructor, construct an empty container. + Vectray(); + /// Destructor - destructs all contained elements. + /// @internal The internal store takes care of the details. + ~Vectray() = default; + + /// Construct empty instance with allocator. + constexpr explicit Vectray(allocator_type const& a) : _store(std::in_place_type_t<FixedStore>{}, a) {} + + /** Construct with @a n default constructed elements. + * + * @param n Number of elements. + * @param alloc Allocator (optional - default constructed if not a parameter). + */ + explicit Vectray(size_type n, allocator_type const& alloc = allocator_type{}); + + template < size_t M > Vectray(Vectray<T, M, A> && that); + + /// Move constructor. + Vectray(self_type && that, allocator_type const& a); + + /// @return The number of elements in the container. + size_type size() const; + + /// Implicitly convert to a @c MemSpan. + operator span () { return this->items(); } + /// Implicitly convert to a @c MemSpan. + operator const_span () const { return this->items(); } + + /** Index operator. + * + * @param idx Index of element. + * @return A reference to the element. + */ + T& operator[](size_type idx); + + /** Index operator (const). + * + * @param idx Index of element. + * @return A @c const reference to the element. + */ + T const& operator[](size_type idx) const; + + /// @return A reference to the first element. + T const& front() const { + return (*this)[0]; + } + + /// @return A reference to the first element. + T & front() { + return (*this)[0]; + } + + /// @return A reference to the last element. + T const& back() const { + return (*this)[this->size()-1]; + } + + /// @return A reference to the last element. + T & back() { + return (*this)[this->size()-1]; + } + /** Append an element by copy. + * + * @param src Element to add. + * @return @a this. + */ + self_type& push_back(T const& t); + + /** Append an element by move. + * + * @param src Element to add. + * @return @a this. + */ + self_type& push_back(T && t); + + /** Append an element by direct construction. + * + * @tparam Args Constructor parameter types. + * @param args Constructor arguments. + * @return @a this + */ + template < typename ... Args> self_type& emplace_back(Args && ... args); + + /** Remove an element from the end of the current elements. + * + * @return @a this. + */ + self_type& pop_back(); + + /// Iterator for first element. + const_iterator begin() const; + + /// Iterator past last element. + const_iterator end() const; + + /// Iterator for last element. + iterator begin(); + + /// Iterator past last element. + iterator end(); + + /// Force at internal storage to hold at least @a n items. + void reserve(size_type n); + +protected: + /// Content storage. + /// @note This is constructed as fixed but can change to dynamic. It can never change back. + std::variant<FixedStore, DynamicStore> _store; + + static constexpr auto FIXED = 0; ///< Variant index for fixed storage. + static constexpr auto DYNAMIC = 1; ///< Variant index for dynamic storage. + + /// Get the span of the valid items. + span items(); + /// Get the span of the valid items. + const_span items() const; + + /// Default size to reserve in the vector when switching to dynamic. + static constexpr size_type BASE_DYNAMIC_SIZE = (7 * N) / 5; + + + /** Transfer from fixed storage to dynamic storage. + * + * @param rN Numer of elements of storage to reserve in the vector. + * + * @note Must be called at most once for any instance. + */ + void transfer(size_type rN = BASE_DYNAMIC_SIZE); +}; + +// --- Implementation --- + +template<typename T, size_t N, typename A> +Vectray<T,N,A>::Vectray() {} + +template<typename T, size_t N, class A> +Vectray<T, N, A>::Vectray(Vectray::size_type n, allocator_type const& alloc) : Vectray() { + this->reserve(n); + while (n-- > 0) { + this->emplace_back(); + } +} + +template <typename T, size_t N, class A> template <size_t M> Vectray<T, N, A>::Vectray(Vectray<T, M, A> &&that) { + // If @a that is already a vector, always move that here. + if (DYNAMIC == that._store.index()) { + _store = std::move(std::get<DYNAMIC>(that._store)); + } else { + auto span = std::get<FIXED>(that._store).span(); + if (span.size() > N) { + + } else { + for ( auto && item : span ) { + this->template emplace_back(std::move(item)); + } + } + } +} + +template <typename T, size_t N, class A> Vectray<T, N, A>::FixedStore::~FixedStore() { + for ( auto & item : this->span() ) { + std::destroy_at(std::addressof(item)); + } +} + +template<typename T, size_t N, class A> +MemSpan<T> Vectray<T, N, A>::FixedStore::span() { + return MemSpan(_raw).template rebind<T>(); +} + +template<typename T, size_t N, typename A> +T& Vectray<T,N,A>::operator[](size_type idx) { + return this->items()[idx]; +} + +template<typename T, size_t N, typename A> +T const& Vectray<T,N,A>::operator[](size_type idx) const { + return this->items[idx]; +} + +template<typename T, size_t N, typename A> +auto Vectray<T,N,A>::push_back(const T& t) -> self_type& { + std::visit(swoc::meta::vary{ + [&](FixedStore& fs) -> void { + if (fs._count < N) { + new(reinterpret_cast<T*>(fs._raw.data()) + fs._count++) T(t); // A::traits ? + } else { + this->transfer(); + std::get<DYNAMIC>(_store).push_back(t); + } + } + , [&](DynamicStore& ds) -> void { + ds.push_back(t); + } + }, _store); + return *this; +} + +template<typename T, size_t N, typename A> +auto Vectray<T,N,A>::push_back(T && t) -> self_type& { + std::visit(swoc::meta::vary{ + [&](FixedStore& fs) -> void { + if (fs._count < N) { + new(reinterpret_cast<T*>(fs._raw.data()) + fs._count++) T(std::move(t)); // A::traits ? + } else { + this->transfer(); + std::get<DYNAMIC>(_store).push_back(t); + } + } + , [&](DynamicStore& ds) -> void { + ds.push_back(std::move(t)); + } + }, _store); + return *this; +} + +template<typename T, size_t N, class A> +template<typename... Args> +auto Vectray<T, N, A>::emplace_back(Args && ... args) -> self_type& { + if (_store.index() == FIXED) { + auto& fs{std::get<FIXED>(_store)}; + if (fs._count < N) { + new(reinterpret_cast<T*>(fs._raw.data()) + fs._count++) T(std::forward<Args>(args)...); // A::traits ? + return *this; + } + this->transfer(); // transfer to dynamic and fall through to add item. + } + std::get<DYNAMIC>(_store).emplace_back(std::forward<Args>(args)...); + return *this; +} + +template<typename T, size_t N, class A> +auto Vectray<T, N, A>::pop_back() -> self_type & { + std::visit(swoc::meta::vary{ + [&](FixedStore& fs) -> void { std::destroy_at(fs.span()[--fs._count]); } + , [&](DynamicStore& ds) -> void { ds.pop_back(); } + }, _store); + return *this; +} + +template<typename T, size_t N, typename A> +auto Vectray<T,N,A>::size() const -> size_type { + return std::visit(swoc::meta::vary{ + [](FixedStore const& fs) { return fs._count; } + , [](DynamicStore const& ds) { return ds.size(); } + }, _store); +} + +// --- iterators +template<typename T, size_t N, typename A> +auto Vectray<T,N,A>::begin() const -> const_iterator { return this->items().begin(); } + +template<typename T, size_t N, typename A> +auto Vectray<T,N,A>::end() const -> const_iterator { return this->items().end(); } + +template<typename T, size_t N, typename A> +auto Vectray<T,N,A>::begin() -> iterator { return this->items().begin(); } + +template<typename T, size_t N, typename A> +auto Vectray<T,N,A>::end() -> iterator { return this->items().end(); } +// --- iterators + +template<typename T, size_t N, class A> +void Vectray<T, N, A>::transfer(size_type rN) { + DynamicStore tmp{std::get<FIXED>(_store)._a}; + tmp.reserve(rN); + + for (auto&& item : this->items()) { + tmp.emplace_back(std::move(item)); // move if supported, copy if not. + } + // Fixed elements destroyed here, by variant. + _store = std::move(tmp); +} + +template<typename T, size_t N, class A> +auto Vectray<T, N, A>::items() const -> const_span { + return std::visit(swoc::meta::vary{ + [](FixedStore const& fs) { fs.span(); } + , [](DynamicStore const& ds) { return const_span(ds.data(), ds.size()); } + }, _store); +} + +template<typename T, size_t N, class A> +auto Vectray<T, N, A>::items() -> span { + return std::visit(swoc::meta::vary{ + [](FixedStore & fs) { return fs.span(); } + , [](DynamicStore & ds) { return span(ds.data(), ds.size()); } + }, _store); +} + +template<typename T, size_t N, class A> +void Vectray<T, N, A>::reserve(Vectray::size_type n) { + if (DYNAMIC == _store.index()) { + std::get<DYNAMIC>(_store).reserve(n); + } else if (n > N) { + this->transfer(n); + } +} + +}} // namespace swoc + diff --git a/doc/code/Vectray.en.rst b/doc/code/Vectray.en.rst new file mode 100644 index 0000000..04f8f38 --- /dev/null +++ b/doc/code/Vectray.en.rst @@ -0,0 +1,64 @@ +.. SPDX-License-Identifier: Apache-2.0 + Copyright Apache Software Foundation 2019 + +.. include:: ../common-defs.rst +.. highlight:: cpp +.. default-domain:: cpp +.. |V| replace:: :code:`Vectray` + +.. _swoc-vectray: + +******** +Vectray +******** + +Synopsis +******** + +:code:`#include "swoc/Vectray.h"` + +.. class:: template < typename T, size_t N, class Allocator > Vectray + + :libswoc:`Reference documentation <Vectray>`. + +|V| is a class intended to replace :code:`std::vector` in situations where performance is critical. +An instance of |V| contains a static array of size :arg:`N` which is used in preference to allocating +memory. If the number of instances is generally less than :arg:`N` then no memory allocation / +deallocation is done and the performance is as fast as a :code:`std::array`. Unlike an array if +the required memory exceeds the static limits the internal storage is changed to a :code:`std::vector` +without data loss. + +Performance gain from using this class depends on + +* The static limit :arg:`N` being relatively small to minimize fixed costs. +* Required storage usually fits within the static limits. + +If allocation is commonly needed this will be slower than a :code:`std::vector` because of +the additional cost of copying from static to dynamic memory. + +The most common use case is for arrays that are usually only 1 or 2 elements (such as options +for a parameter) and only rarely longer. |V| can then significantly reduce memory churn at small +cost. + +The second common use case is for stack buffers of moderate size, such as logging buffers. Generally +these are selected to be large to enough to cover almost cases, except for the occasional truncation. +In this case |V| preserves the performance of the common case while providing an escape in the +rare circumstance of exceeding the buffer size. + +As always, performance tuning is an art, not a science. Do not simply assume |V| is a better choice. + +Usage +***** + +|V| acts as a combination of :code:`std::vector` and :code:`std::aray` and is declared like the latter. +For an instance that contains a single static element of type :code:`std::string` :: + + swoc::Vectray<std::string, 1> strings; + +The static elements are not default constructed, but are constructed as needed. + +Allocator +========= + +Generally this should be defaulted, but is provided so a polymorphic memory resource based +allocator can be used when needed. diff --git a/doc/index.rst b/doc/index.rst index d9036f7..72ef641 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -38,6 +38,7 @@ unrelated projects. Hence this library. I hope you find it as useful as I have. code/Lexicon.en code/Errata.en code/Scalar.en + code/Vectray.en code/IPSpace.en appendix.en diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 301262f..20c5cd6 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(test_libswoc test_TextView.cc test_Scalar.cc test_swoc_file.cc + test_Vectray.cc ex_bw_format.cc ex_IntrusiveDList.cc diff --git a/unit_tests/ex_UnitParser.cc b/unit_tests/ex_UnitParser.cc index 5b0a98f..865e3f0 100644 --- a/unit_tests/ex_UnitParser.cc +++ b/unit_tests/ex_UnitParser.cc @@ -77,31 +77,29 @@ auto UnitParser::operator()(swoc::TextView const& src) const noexcept -> Rv<valu TextView text = src; // Keep @a src around to report error offsets. while (text.ltrim_if(&isspace)) { - // Get a count first. - auto ptr = text.data(); // save for error reporting. - auto count = text.clip_prefix_of(&isdigit); - if (count.empty()) { - return Errata("Required count not found at offset {}", ptr - src.data()); + TextView parsed; + auto n = swoc::svtou(text, &parsed); + if (parsed.empty()) { + return Errata("Required count not found at offset {}", text.data() - src.data()); + } else if (n == std::numeric_limits<decltype(n)>::max()) { + return Errata("Count at offset {} was out of bounds", text.data() - src.data()); } - // Should always parse correctly as @a count is a non-empty sequence of digits. - auto n = svtou(count); - - // Next, the unit. - ptr = text.ltrim_if(&isspace).data(); // save for error reporting. + text.remove_prefix(parsed.size()); + auto ptr = text.ltrim_if(&isspace).data(); // save for error reporting. // Everything up to the next digit or whitespace. auto unit = text.clip_prefix_of([](char c) { return !(isspace(c) || isdigit(c)); } ); if (unit.empty()) { if (_unit_required_p) { return Errata("Required unit not found at offset {}", ptr - src.data()); } - zret += n; // no metric -> unit metric. } else { auto mult = _units[unit]; // What's the multiplier? if (mult == 0) { return Errata("Unknown unit \"{}\" at offset {}", unit, ptr - src.data()); } - zret += mult * n; + n *= mult; } + zret += n; } return zret; } @@ -110,43 +108,56 @@ auto UnitParser::operator()(swoc::TextView const& src) const noexcept -> Rv<valu TEST_CASE("UnitParser Bytes", "[Lexicon][UnitParser]") { UnitParser bytes{ - UnitParser::Units{ - { - {1, {"B", "bytes"}} - , {1024, {"K", "KB", "kilo", "kilobyte"}} - , {1048576, {"M", "MB", "mega", "megabyte"}} - , {1 << 30, {"G", "GB", "giga", "gigabytes"}} - }} + UnitParser::Units{ + { + {1, {"B", "bytes"}} + , {1024, {"K", "KB", "kilo", "kilobyte", "kilobytes"}} + , {1048576, {"M", "MB", "mega", "megabyte", "megabytes"}} + , {1 << 30, {"G", "GB", "giga", "gigabyte", "gigabytes"}} + } + } }; bytes.unit_required(false); REQUIRE(bytes("56 bytes") == 56); REQUIRE(bytes("3 kb") == 3 * (1 << 10)); REQUIRE(bytes("6k128bytes") == 6 * (1 << 10) + 128); + REQUIRE(bytes("6 k128bytes") == 6 * (1 << 10) + 128); + REQUIRE(bytes("6 K128 bytes") == 6 * (1 << 10) + 128); + REQUIRE(bytes("6 kilo 0x80 bytes") == 6 * (1 << 10) + 128); + REQUIRE(bytes("6kilo 0x8b bytes") == 6 * (1 << 10) + 0x8b); REQUIRE(bytes("111") == 111); - REQUIRE(bytes("4K") == 4 * (1 << 10)); + REQUIRE(bytes("4MB") == 4 * (uintmax_t(1) << 20)); + REQUIRE(bytes("4 giga") == 4 * (uintmax_t(1) << 30)); + REQUIRE(bytes("10M 256K 512") == 10 * (1<<20) + 256*(1<<10) + 512); + REQUIRE(bytes("512 256 kilobytes 10 megabytes") == 10 * (1<<20) + 256*(1<<10) + 512); + REQUIRE(bytes("0x100000000") == 0x100000000); auto result = bytes("56delain"); REQUIRE(result.is_ok() == false); REQUIRE(result.errata().front().text() == "Unknown unit \"delain\" at offset 2"); result = bytes("12K delain"); REQUIRE(result.is_ok() == false); REQUIRE(result.errata().front().text() == "Required count not found at offset 4"); + result = bytes("99999999999999999999"); + REQUIRE(result.is_ok() == false); + REQUIRE(result.errata().front().text() == "Count at offset 0 was out of bounds"); } TEST_CASE("UnitParser Time", "[Lexicon][UnitParser]") { using namespace std::chrono; UnitParser time { - UnitParser::Units{ - { - {nanoseconds{1}.count(), {"ns", "nanosec", "nanoseconds" }} - , {nanoseconds{microseconds{1}}.count(), {"us", "microsec", "microseconds"}} - , {nanoseconds{milliseconds{1}}.count(), {"ms", "millisec", "milliseconds"}} - , {nanoseconds{seconds{1}}.count(), {"s", "sec", "seconds"}} - , {nanoseconds{minutes{1}}.count(), {"m", "min", "minutes"}} - , {nanoseconds{hours{1}}.count(), {"h", "hours"}} - , {nanoseconds{hours{24}}.count(), {"d", "days"}} - , {nanoseconds{hours{168}}.count(), {"w", "weeks"}} - }} + UnitParser::Units{ + { + {nanoseconds{1}.count(), {"ns", "nanosec", "nanoseconds" }} + , {nanoseconds{microseconds{1}}.count(), {"us", "microsec", "microseconds"}} + , {nanoseconds{milliseconds{1}}.count(), {"ms", "millisec", "milliseconds"}} + , {nanoseconds{seconds{1}}.count(), {"s", "sec", "seconds"}} + , {nanoseconds{minutes{1}}.count(), {"m", "min", "minutes"}} + , {nanoseconds{hours{1}}.count(), {"h", "hour", "hours"}} + , {nanoseconds{hours{24}}.count(), {"d", "day", "days"}} + , {nanoseconds{hours{168}}.count(), {"w", "week", "weeks"}} + } + } }; REQUIRE(nanoseconds{time("2s")} == seconds{2}); diff --git a/unit_tests/test_TextView.cc b/unit_tests/test_TextView.cc index 8a77277..de9e6d2 100644 --- a/unit_tests/test_TextView.cc +++ b/unit_tests/test_TextView.cc @@ -359,7 +359,7 @@ TEST_CASE("TextView Affixes", "[libswoc][TextView]") s.remove_prefix_at('!'); REQUIRE(s == "file.cc.org"); - static constexpr TextView ctv {"http://delain.nl/albums/Lucidity.html"}; + static constexpr TextView ctv{"http://delain.nl/albums/Lucidity.html"}; static constexpr TextView ctv_scheme{ctv.prefix(4)}; static constexpr TextView ctv_stem{ctv.suffix(4)}; static constexpr TextView ctv_host{ctv.substr(7, 9)}; @@ -485,6 +485,34 @@ TEST_CASE("TextView Conversions", "[libswoc][TextView]") REQUIRE(25 == swoc::svto_radix<8>(x)); REQUIRE(x.size() == 0); + // Check overflow conditions + static constexpr auto MAX = std::numeric_limits<uintmax_t>::max(); + + // One less than max. + x.assign("18446744073709551614"); + REQUIRE(MAX-1 == swoc::svto_radix<10>(x)); + REQUIRE(x.size() == 0); + + // Exactly max. + x.assign("18446744073709551615"); + REQUIRE(MAX == swoc::svto_radix<10>(x)); + REQUIRE(x.size() == 0); + + // Should overflow and clamp. + x.assign("18446744073709551616"); + REQUIRE(MAX == swoc::svto_radix<10>(x)); + REQUIRE(x.size() == 0); + + // Even more digits. + x.assign("18446744073709551616123456789"); + REQUIRE(MAX == swoc::svto_radix<10>(x)); + REQUIRE(x.size() == 0); + + // This is a special value - where N*10 > N while also overflowing. The final "1" causes this. + // Be sure overflow is detected. + x.assign("27381885734412615681"); + REQUIRE(MAX == swoc::svto_radix<10>(x)); + // floating point is never exact, so "good enough" is all that is measureable. This checks the // value is within one epsilon (minimum change possible) of the compiler generated value. auto fcmp = [](double lhs, double rhs) { diff --git a/unit_tests/test_Vectray.cc b/unit_tests/test_Vectray.cc new file mode 100644 index 0000000..eea56c4 --- /dev/null +++ b/unit_tests/test_Vectray.cc @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +/** @file + + MemSpan unit tests. + +*/ + +#include <iostream> +#include "swoc/Vectray.h" +#include "catch.hpp" + +using swoc::Vectray; + +TEST_CASE("Vectray", "[libswoc][Vectray]") +{ + struct Thing { + unsigned n = 56; + Thing() = default; + Thing(Thing const& that) = default; + Thing(Thing && that) : n(that.n) { that.n = 0; } + Thing(unsigned u) : n(u) {} + }; + + Vectray<Thing, 1> unit_thing; + Thing PhysicalThing{0}; + + REQUIRE(unit_thing.size() == 0); + + unit_thing.push_back(PhysicalThing); // Copy construct + REQUIRE(unit_thing.size() == 1); + unit_thing.push_back(Thing{1}); + REQUIRE(unit_thing.size() == 2); + unit_thing.push_back(Thing{2}); + REQUIRE(unit_thing.size() == 3); + + // Check via indexed access. + for ( unsigned idx = 0 ; idx < unit_thing.size() ; ++idx ) { + REQUIRE(unit_thing[idx].n == idx); + } + + // Check via container access. + unsigned n = 0; + for ( auto const& thing : unit_thing ) { + REQUIRE(thing.n == n); + ++n; + } + REQUIRE(n == unit_thing.size()); + + Thing tmp{99}; + unit_thing.push_back(std::move(tmp)); + REQUIRE(unit_thing[3].n == 99); + REQUIRE(tmp.n == 0); + PhysicalThing.n = 101; + unit_thing.push_back(PhysicalThing); + REQUIRE(unit_thing.back().n == 101); + REQUIRE(PhysicalThing.n == 101); +} + +TEST_CASE("Vectray Destructor", "[libswoc][Vectray]") { + int count = 0; + struct Q { + int & count_; + Q(int & count) : count_(count) {} + ~Q() { ++count_; } + }; + + { + Vectray<Q, 1> v1; + v1.emplace_back(count); + } + REQUIRE(count == 1); + + count = 0; + { // force use of dynamic memory. + Vectray<Q, 1> v2; + v2.emplace_back(count); + v2.emplace_back(count); + v2.emplace_back(count); + } + // Hard to get an exact cound because of std::vector resizes. + // But first object should be at least double deleted because of transfer. + REQUIRE(count >= 4); + +}
