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);
+
+}

Reply via email to