You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2018/03/15 15:32:55 UTC

[trafficserver] branch master updated: Split MemSpan from TextView, cleanup MemSpan API, add documentation.

This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 7f89eb2  Split MemSpan from TextView, cleanup MemSpan API, add documentation.
7f89eb2 is described below

commit 7f89eb262731ecc101af7478d517f39889e13203
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Sun Feb 25 10:05:33 2018 -0600

    Split MemSpan from TextView, cleanup MemSpan API, add documentation.
---
 doc/developer-guide/api/types/SystemTypes.en.rst   |   8 +-
 .../internal-libraries/MemSpan.en.rst              |  98 ++++
 .../{memview.en.rst => TextView.en.rst}            |  76 +--
 .../internal-libraries/index.en.rst                |   3 +-
 lib/ts/Makefile.am                                 |   4 +-
 lib/ts/MemSpan.h                                   | 572 +++++++++++++++++++++
 lib/ts/TextView.h                                  | 476 -----------------
 lib/ts/unit-tests/test_MemSpan.cc                  |  56 ++
 8 files changed, 776 insertions(+), 517 deletions(-)

diff --git a/doc/developer-guide/api/types/SystemTypes.en.rst b/doc/developer-guide/api/types/SystemTypes.en.rst
index f086e24..aed6390 100644
--- a/doc/developer-guide/api/types/SystemTypes.en.rst
+++ b/doc/developer-guide/api/types/SystemTypes.en.rst
@@ -35,11 +35,15 @@ These types are provided by the compiler ("built-in") or from a required operati
 
 .. c:type:: off_t
 
-   `Reference <https://linux.die.net/include/unistd.h>`__.
+   `Reference <https://www.gnu.org/software/libc/manual/html_node/Important-Data-Types.html>`__.
 
 .. cpp:type:: off_t
 
-   `Reference <https://linux.die.net/include/unistd.h>`__.
+   `Reference <https://www.gnu.org/software/libc/manual/html_node/Important-Data-Types.html>`__.
+
+.. cpp:type:: ptrdiff_t
+
+   The difference between two pointers. `Reference <https://www.gnu.org/software/libc/manual/html_node/Important-Data-Types.html>`__.
 
 .. cpp:type:: uint64_t
 
diff --git a/doc/developer-guide/internal-libraries/MemSpan.en.rst b/doc/developer-guide/internal-libraries/MemSpan.en.rst
new file mode 100644
index 0000000..ce5f7df
--- /dev/null
+++ b/doc/developer-guide/internal-libraries/MemSpan.en.rst
@@ -0,0 +1,98 @@
+.. 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:: ../../common.defs
+
+.. default-domain:: cpp
+
+MemSpan
+*******
+
+Synopsis
+========
+
+:code:`#include <ts/MemSpan.h>`
+
+:class:`MemSpan` is a view on a contiguous section of writeable memory. A view does not own the memory
+and neither allocates nor de-allocates. The memory in the view is always owned by some other container
+and it is the responsibility of the code to make sure the lifetime of the view is no more than that
+of the owning container [#vector]_.
+
+
+Description
+===========
+
+A :class:`MemSpan` is generally constructed on either an array or an allocated buffer. This allows
+the buffer to be passed around with intrinsic length information. The buffer can also be treated as
+an array of varying types, which makes working with serialized data much easier.
+
+Reference
+=========
+
+.. class:: MemSpan
+
+   A span of writable memory. Because this is a chunk of memory, conceptually delimited by start and
+   end pointers, the sizing type is :code:`ptrdiff_t` so that all of the sizing is consistent with
+   differences between pointers. The memory is owned by some other object and that object must
+   maintain the memory as long as the span references it.
+
+   .. function:: MemSpan(void * ptr, ptrdiff_t size)
+
+      Construct a view starting at :arg:`ptr` for :arg:`size` bytes.
+
+   .. function:: void * data()  const
+
+      Return a pointer to the first byte of the span.
+
+   .. function:: ptrdiff_t size() const
+
+      Return the size of the span.
+
+   .. function:: bool operator == (MemSpan const& that) const
+
+      Check the equality of two spans, which are equal if they contain the same number of bytes of the same values.
+
+   .. function:: bool is_same(MemSpan const& that) const
+
+      Check if :arg:`that` is the same span as :arg:`this`, that is the spans contain the exact same bytes.
+
+   .. function:: template < typename V > V at(ptrdiff_t n) const
+
+      Return a value of type :arg:`V` as if the span were are array of type :arg:`V`.
+
+   .. function:: template < typename V > V * ptr(ptrdiff_t n) const
+
+      Return a pointer to a value of type :arg:`V` as if the span were are array of type :arg:`V`.
+
+   .. function:: MemSpan prefix(ptrdiff_t n) const
+
+      Return a new instance that contains the first :arg:`n` bytes of the current span. If :arg:`n`
+      is larger than the number of bytes in the span, only that many bytes are returned.
+
+   .. function:: MemSpan& remove_prefix(ptrdiff_t n)
+
+      Remove the first :arg:`n` bytes of the span. If :arg:`n` is more than the number of bytes in
+      the span the result is an empty span. A reference to the instance is returned.
+
+.. rubric:: Footnotes
+
+.. [#vector]
+
+   Strong caution must be used with containers such as :code:`std::vector` or :code:`std::string`
+   because the lifetime of the memory can be much less than the lifetime of the container. In
+   particular, adding or removing any element from a :code:`std::vector` can cause a re-allocation,
+   invalidating any view of the original memory. In general views should be treated like iterators.
diff --git a/doc/developer-guide/internal-libraries/memview.en.rst b/doc/developer-guide/internal-libraries/TextView.en.rst
similarity index 64%
rename from doc/developer-guide/internal-libraries/memview.en.rst
rename to doc/developer-guide/internal-libraries/TextView.en.rst
index 75f57f8..58641c0 100644
--- a/doc/developer-guide/internal-libraries/memview.en.rst
+++ b/doc/developer-guide/internal-libraries/TextView.en.rst
@@ -19,60 +19,62 @@
 
 .. default-domain:: cpp
 
-MemView
+TextView
 *************
 
 Synopsis
 ========
 
-:code:`#include <ts/MemView.h>`
-
-.. class:: MemView
-
-.. class:: StringView
+:code:`#include <ts/TextView.h>`
 
 .. class:: TextView
 
-These classes act as views in to already allocated memory. Internally in |TS| work must be done with
-string or memory entities that are embedded in larger pre-existing memory structures. These classes
-are designed to make that easier, more efficient, and less error prone.
+This class acts as a view in to memory allocated / owned elsewhere. It is in effect a pointer and
+should be treated as such (e.g. care must be taken to avoid dangling references by knowing where the
+memory really is). The purpose is to provide string manipulation that is fast, efficient, and
+non-modifying, particularly when temporary "copies" are needed.
+
 
 Description
 ===========
 
-The term "view" will be used to mean an instance of :class:`MemView` or :class:`StringView`.
-Fundamentally both classes do the same thing, maintain a read only view of a contiguous region of
-memory. They differ in the methods and return types due to the conflicting requirements of raw
-memory operations and string based operations.
+:class:`TextView` is a subclass of :class:`string_view` and has all of those methods. In addition it
+provides a number of ancillary methods of common string manipulation methods.
 
-A view is constructed by providing a contiguous region of memory, either based on a start pointer
-and a length or a pair of pointers in the usual STL half open range style where the view starts at
-the first pointer and ends one short of the second pointer. A view can be empty and refer to no
-memory (which what default construction yields). A view attempts to act like a normal pointer in
-most situations. A view is only somewhat more expensive than a raw pointer but in most cases a count
-is needed as well making a view not any more costly than existing code. Any code that already keeps
-a pointer and a count is a good candidate for using :class:`MemView` or :class:`StringView`.
+A :class:`TextView` should be treated as an enhanced character pointer that both a location and a
+size. This is when makes it possible to pass sub strings around without having to make copies or
+allocation additional memory. This comes at the cost of keeping track of the actual owner of the
+string memory and making sure the :class:`TextView` does not outlive the memory owner, just as with
+a normal pointer type. Internal for |TS| any place that passes a :code:`char *` and a size is an
+excellent candidate for using a :class:`TextView` as it is more convinient and no more risky than
+the existing arguments.
 
-:class:`MemView` and :class:`StringView` inter-convert because the difference between them is simply
-the API to access the underingly memory in the view, the actual class internal data is identical.
+In deciding between :class:`string_view` and :class:`TextView` remember that these easily and
+cheaply cross convert. In general if the string is treated as a block of data, :class:`string_view`
+is better. If the contents of the string are to be examined / parsed non-uniformly then
+:class:`TextView` is better. For example, if the string is used simply as a key or a hash source,
+use :class:`string_view`. Or, if the string may contain substrings of interests such as key / value
+pairs, then use a :class:`TextView`.
 
-:class:`StringView` provides a variety of methods for manipulating the view as a string. These are provided as families of overloads differentiated by how characters are compared. There are four flavors.
+:class:`TextView` provides a variety of methods for manipulating the view as a string. These are
+provided as families of overloads differentiated by how characters are compared. There are four
+flavors.
 
 * Direct, a pointer to the target character.
 * Comparison, an explicit character value to compare.
-* Set, a set of characters (described by a :class:`StringView`) which are compared, any one of which matches.
+* Set, a set of characters (described by a :class:`TextView`) which are compared, any one of which matches.
 * Predicate, a function that takes a single character argument and returns a bool to indicate a match.
 
 If the latter three are inadequate the first, the direct pointer, can be used after finding the
 appropriate character through some other mechanism.
 
-The increment operator for :class:`StringView` shrinks the view by one character from the front
+The increment operator for :class:`TextView` shrinks the view by one character from the front
 which allows stepping through the view in normal way, although the string view itself should be the
 loop condition, not a dereference of it.
 
 .. code-block:: cpp
 
-   StringView v;
+   TextView v;
    size_t hash = 0;
    for ( ; v ; ++v) hash = hash * 13 + *v;
 
@@ -80,21 +82,21 @@ Or, because the view acts as a container of characters, this can be done non-des
 
 .. code-block:: cpp
 
-   StringView v;
+   TextView v;
    size_t hash = 0;
    for (char c : v) hash = hash * 13 + c;
 
 Views are cheap to construct therefore making a copy to use destructively is very inexpensive.
 
-:class:`MemView` provides a :code:`find` method that searches for a matching value. The type of this
+:class:`MemSpan` provides a :code:`find` method that searches for a matching value. The type of this
 value can be anything that is fixed sized and supports the equality operator. The view is treated as
 an array of the type and searched sequentially for a matching value. The value type is treated as
 having no identity and cheap to copy, in the manner of a integral type.
 
-Parsing with StringView
+Parsing with TextView
 -----------------------
 
-A primary use of :class:`StringView` is to do field oriented parsing. It is easy and fast to split
+A primary use of :class:`TextView` is to do field oriented parsing. It is easy and fast to split
 strings in to fields without modifying the original data. For example, assume that :arg:`value`
 contains a null terminated string which is possibly several tokens separated by commas.
 
@@ -102,9 +104,9 @@ contains a null terminated string which is possibly several tokens separated by
 
    #include <ctype.h>
    parse_token(const char* value) {
-     StringView v(value); // construct assuming null terminated string.
+     TextView v(value); // construct assuming null terminated string.
      while (v) {
-       StringView token(v.extractPrefix(','));
+       TextView token(v.extractPrefix(','));
        token.trim(&isspace);
        if (token) {
          // process token
@@ -125,10 +127,10 @@ What if the tokens were key / value pairs, of the form `key=value`? This is can
 
    #include <ctype.h>
    parse_token(const char* source) {
-     StringView in(source); // construct assuming null terminated string.
+     TextView in(source); // construct assuming null terminated string.
      while (in) {
-       StringView value(in.extractPrefix(','));
-       StringView key(value.trim(&isspace).splitPrefix('=').rtrim(&isspace));
+       TextView value(in.extractPrefix(','));
+       TextView key(value.trim(&isspace).splitPrefix('=').rtrim(&isspace));
        if (key) {
          // it's a key=value token with key and value set appropriately.
          value.ltrim(&isspace); // clip potential space after '='.
@@ -159,9 +161,9 @@ proved to provide little of the functionality available in :code:`ts::ConstBuffe
 reworking was required in any case, it seemed better to start from scratch and build just what was
 useful in the |TS| context.
 
-The next step was the :code:`StringView` class which turned out reasonably well. It was then
+The next step was the :code:`TextView` class which turned out reasonably well. It was then
 suggested that more support for raw memory (as opposed to memory presumed to contain printable ASCII
 data) would be useful. An attempt was made to do this but the differences in arguments, subtle
-method differences, and return types made that infeasible. Instead :class:`MemView` was split off to
+method differences, and return types made that infeasible. Instead :class:`MemSpan` was split off to
 provide a :code:`void*` oriented view. String specific methods were stripped out and a few
 non-character based methods added.
diff --git a/doc/developer-guide/internal-libraries/index.en.rst b/doc/developer-guide/internal-libraries/index.en.rst
index 0542b07..80582fd 100644
--- a/doc/developer-guide/internal-libraries/index.en.rst
+++ b/doc/developer-guide/internal-libraries/index.en.rst
@@ -29,6 +29,7 @@ development team.
    :maxdepth: 1
 
    string_view.en
-   memview.en
+   TextView.en
+   MemSpan.en
    scalar.en
    buffer-writer.en
diff --git a/lib/ts/Makefile.am b/lib/ts/Makefile.am
index 7175ac8..47cf63a 100644
--- a/lib/ts/Makefile.am
+++ b/lib/ts/Makefile.am
@@ -170,6 +170,7 @@ libtsutil_la_SOURCES = \
   Map.h \
   MatcherUtils.cc \
   MatcherUtils.h \
+  MemSpan.h \
   MMH.cc \
   MMH.h \
   MT_hashtable.h \
@@ -267,7 +268,8 @@ test_tslib_SOURCES = \
 	unit-tests/test_layout.cc \
 	unit-tests/test_MT_hashtable.cc \
 	unit-tests/test_string_view.cc \
-	unit-tests/test_TextView.cc
+	unit-tests/test_TextView.cc \
+	unit-tests/test_MemSpan.cc
 
 CompileParseRules_SOURCES = CompileParseRules.cc
 
diff --git a/lib/ts/MemSpan.h b/lib/ts/MemSpan.h
new file mode 100644
index 0000000..8f4f873
--- /dev/null
+++ b/lib/ts/MemSpan.h
@@ -0,0 +1,572 @@
+/** @file
+
+   Spans of memory. This is similar but independently developed from @c std::span. The goal is
+   to provide convenient handling for chunks of memory. These chunks can be treated as arrays
+   of arbitrary types via template methods.
+
+
+   @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 <cstring>
+#include <iosfwd>
+
+/// Apache Traffic Server commons.
+namespace ts
+{
+/** A span of contiguous piece of memory.
+
+    A @c MemSpan does not own the memory to which it refers, it is simply a span of part of some
+    (presumably) larger memory object. The purpose is that frequently code needs to work on a specific
+    part of the memory. This can avoid copying or allocation by allocating all needed memory at once
+    and then working with it via instances of this class.
+ */
+class MemSpan
+{
+  using self_type = MemSpan; ///< Self reference type.
+
+protected:
+  void *_data     = nullptr; ///< Pointer to base of memory chunk.
+  ptrdiff_t _size = 0;       ///< Size of memory chunk.
+
+public:
+  /// Default constructor (empty buffer).
+  constexpr MemSpan();
+
+  /** Construct explicitly with a pointer and size.
+   */
+  constexpr MemSpan(void *ptr,  ///< Pointer to buffer.
+                    ptrdiff_t n ///< Size of buffer.
+                    );
+
+  /** Construct from a half open range of two pointers.
+      @note The instance at @start is in the span but the instance at @a end is not.
+  */
+  template <typename T>
+  constexpr MemSpan(T *start, ///< First byte in the span.
+                    T *end    ///< First byte not in the span.
+                    );
+
+  /** Construct from a half open range of two pointers.
+      @note The instance at @start is in the span but the instance at @a end is not.
+  */
+  MemSpan(void *start, ///< First byte in the span.
+          void *end    ///< First byte not in the span.
+          );
+
+  /** Construct to cover an array.
+   *
+   * @tparam T Array element type.
+   * @tparam N Number of elements in the array.
+   * @param a The array.
+   */
+  template <typename T, size_t N> MemSpan(T (&a)[N]);
+
+  /** Construct from nullptr.
+      This implicitly makes the length 0.
+  */
+  constexpr MemSpan(std::nullptr_t);
+
+  /** Equality.
+
+      Compare the span contents.
+
+      @return @c true if the contents of @a that are the same as the content of @a this,
+      @c false otherwise.
+   */
+  bool operator==(self_type const &that) const;
+
+  /** Identical.
+
+      Check if the spans refer to the same span of memory.
+      @return @c true if @a this and @a that refer to the same span, @c false if not.
+   */
+  bool is_same(self_type const &that) const;
+
+  /** Inequality.
+      @return @c true if @a that does not refer to the same span as @a this,
+      @c false otherwise.
+   */
+  bool operator!=(self_type const &that) const;
+
+  /// Assignment - the span is copied, not the content.
+  self_type &operator=(self_type const &that);
+
+  /** Shift the span to discard the first byte.
+      @return @a this.
+  */
+  self_type &operator++();
+
+  /** Shift the span to discard the leading @a n bytes.
+      @return @a this
+  */
+  self_type &operator+=(ptrdiff_t n);
+
+  /// Check for empty span.
+  /// @return @c true if the span is empty (no contents), @c false otherwise.
+  bool operator!() const;
+
+  /// Check for non-empty span.
+  /// @return @c true if the span contains bytes.
+  explicit operator bool() const;
+
+  /// Check for empty span (no content).
+  /// @see operator bool
+  bool empty() const;
+
+  /// @name Accessors.
+  //@{
+  /// Pointer to the first byte in the span.
+  void *begin() const;
+
+  /// Pointer to first byte not in the span.
+  void *end() const;
+
+  /// Number of bytes in the span.
+  constexpr ptrdiff_t size() const;
+
+  /// Memory pointer.
+  /// @note This is equivalent to @c begin currently but it's probably good to have separation.
+  constexpr void *data() const;
+
+  /// Memory pointer, one past the last element of the span.
+  void *data_end() const;
+
+  /// @return the @a V value at index @a n.
+  template <typename V> V at(ptrdiff_t n) const;
+
+  /// @return a pointer to the @a V value at index @a n.
+  template <typename V> V const *ptr(ptrdiff_t n) const;
+  //@}
+
+  /// Set the span.
+  /// This is faster but equivalent to constructing a new span with the same
+  /// arguments and assigning it.
+  /// @return @c this.
+  self_type &assign(void *ptr,      ///< Buffer address.
+                    ptrdiff_t n = 0 ///< Buffer size.
+                    );
+
+  /// Set the span.
+  /// This is faster but equivalent to constructing a new span with the same
+  /// arguments and assigning it.
+  /// @return @c this.
+  self_type &assign(void *start,    ///< First valid character.
+                    void const *end ///< First invalid character.
+                    );
+
+  /// Clear the span (become an empty span).
+  self_type &clear();
+
+  /// @return @c true if the byte at @a *p is in the span.
+  bool contains(const void *p) const;
+
+  /** Find a value.
+      The memory is searched as if it were an array of the value type @a V.
+
+      @return A pointer to the first occurrence of @a v in @a this
+      or @c nullptr if @a v is not found.
+  */
+  template <typename V> V *find(V v) const;
+
+  /** Find a value.
+      The memory is searched as if it were an array of type @a V.
+
+      @return A pointer to the first value for which @a pred is @c true otherwise
+      @c nullptr.
+  */
+  template <typename V, typename F> V *find_if(F const &pred);
+
+  /** Get the initial segment of the span before @a p.
+
+      The byte at @a p is not included. If @a p is not in the span an empty span
+      is returned.
+
+      @return A buffer that contains all data before @a p.
+  */
+  self_type prefix(const void *p) const;
+
+  /** Get the first @a n bytes of the span.
+
+      @return A span with the first @a n bytes of this span.
+  */
+  self_type prefix(ptrdiff_t n) const;
+
+  /** Shrink the span from the front.
+   *
+   * @param p The limit of the removed span.
+   * @return @c *this
+   *
+   * The byte at @a p is not removed.
+   */
+
+  self_type remove_prefix(void const *p);
+  /** Shringt the span from the front.
+   *
+   * @param n The number of bytes to remove.
+   * @return @c *this
+   */
+  self_type &remove_prefix(ptrdiff_t n);
+
+  /** Get the trailing segment of the span after @a p.
+
+      The byte at @a p is not included. If @a p is not in the span an empty span is returned.
+
+      @return A buffer that contains all data after @a p.
+  */
+  self_type suffix(const void *p) const;
+
+  /** Get the trailing @a n bytes.
+
+      @return A span with @a n bytes of the current span.
+  */
+  self_type suffix(ptrdiff_t p) const;
+
+  /** Shrink the span from the back.
+   *
+   * @param p The limit of the removed span.
+   * @return @c *this
+   *
+   * The byte at @a p is not removed.
+   */
+  self_type &remove_suffix(void const *p);
+
+  /** Shringt the span from the back.
+   *
+   * @param n The number of bytes to remove.
+   * @return @c *this
+   */
+  self_type &remove_suffix(ptrdiff_t n);
+
+  /// Internal utility for computing the difference of two void pointers.
+  /// @return the byte (char) difference between the pointers, @a lhs - @a rhs
+  static ptrdiff_t distance(void const *lhs, void const *rhs);
+};
+
+// -- Implementation --
+
+inline int
+memcmp(MemSpan const &lhs, MemSpan const &rhs)
+{
+  int zret    = 0;
+  ptrdiff_t n = lhs.size();
+
+  // Seems a bit ugly but size comparisons must be done anyway to get the memcmp args.
+  if (lhs.size() < rhs.size()) {
+    zret = 1;
+  } else if (lhs.size() > rhs.size()) {
+    zret = -1;
+    n    = rhs.size();
+  }
+  // else the sizes are equal therefore @a n and @a zret are already correct.
+
+  int r = std::memcmp(lhs.data(), rhs.data(), n);
+  if (0 != r) { // If we got a not-equal, override the size based result.
+    zret = r;
+  }
+
+  return zret;
+}
+// need to bring memcmp in so this is an overload, not an override.
+using std::memcmp;
+
+inline constexpr MemSpan::MemSpan()
+{
+}
+
+inline constexpr MemSpan::MemSpan(void *ptr, ptrdiff_t n) : _data(ptr), _size(n)
+{
+}
+
+template <typename T> constexpr MemSpan::MemSpan(T *start, T *end) : _data(start), _size((end - start) * sizeof(T))
+{
+}
+
+// <void*> is magic, handle that specially.
+// No constexpr because the spec specifically forbids casting from <void*> to a typed pointer.
+inline MemSpan::MemSpan(void *start, void *end) : _data(start), _size(static_cast<char *>(end) - static_cast<char *>(start))
+{
+}
+
+template <typename T, size_t N> MemSpan::MemSpan(T (&a)[N]) : _data(a), _size(N * sizeof(T))
+{
+}
+
+inline constexpr MemSpan::MemSpan(std::nullptr_t) : _data(nullptr), _size(0)
+{
+}
+
+inline ptrdiff_t
+MemSpan::distance(void const *lhs, void const *rhs)
+{
+  return static_cast<const char *>(lhs) - static_cast<const char *>(rhs);
+}
+
+inline MemSpan &
+MemSpan::assign(void *ptr, ptrdiff_t n)
+{
+  _data = ptr;
+  _size = n;
+  return *this;
+}
+
+inline MemSpan &
+MemSpan::assign(void *ptr, void const *limit)
+{
+  _data = ptr;
+  _size = static_cast<const char *>(limit) - static_cast<const char *>(ptr);
+  return *this;
+}
+
+inline MemSpan &
+MemSpan::clear()
+{
+  _data = 0;
+  _size = 0;
+  return *this;
+}
+
+inline bool
+MemSpan::is_same(self_type const &that) const
+{
+  return _data == that._data && _size == that._size;
+}
+
+inline bool
+MemSpan::operator==(self_type const &that) const
+{
+  return _size == that._size && (_data == that._data || 0 == memcmp(this->data(), that.data(), _size));
+}
+
+inline bool
+MemSpan::operator!=(self_type const &that) const
+{
+  return !(*this == that);
+}
+
+inline bool MemSpan::operator!() const
+{
+  return _size == 0;
+}
+
+inline MemSpan::operator bool() const
+{
+  return _size != 0;
+}
+
+inline bool
+MemSpan::empty() const
+{
+  return _size == 0;
+}
+
+inline MemSpan &MemSpan::operator++()
+{
+  _data = static_cast<char *>(_data) + 1;
+  --_size;
+  return *this;
+}
+
+inline MemSpan &
+MemSpan::operator+=(ptrdiff_t n)
+{
+  if (n > _size) {
+    this->clear();
+  } else {
+    _data = static_cast<char *>(_data) + n;
+    _size -= n;
+  }
+  return *this;
+}
+
+inline void *
+MemSpan::begin() const
+{
+  return _data;
+}
+
+inline constexpr void *
+MemSpan::data() const
+{
+  return _data;
+}
+
+inline void *
+MemSpan::end() const
+{
+  return static_cast<char *>(_data) + _size;
+}
+
+inline void *
+MemSpan::data_end() const
+{
+  return static_cast<char *>(_data) + _size;
+}
+
+inline constexpr ptrdiff_t
+MemSpan::size() const
+{
+  return _size;
+}
+
+inline MemSpan &
+MemSpan::operator=(MemSpan const &that)
+{
+  _data = that._data;
+  _size = that._size;
+  return *this;
+}
+
+inline bool
+MemSpan::contains(const void *p) const
+{
+  return !this->empty() && _data <= p && p < this->data_end();
+}
+
+inline MemSpan
+MemSpan::prefix(const void *p) const
+{
+  self_type zret;
+  if (_data <= p && p <= this->data_end())
+    zret.assign(_data, p);
+  return zret;
+}
+
+inline MemSpan
+MemSpan::prefix(ptrdiff_t n) const
+{
+  return {_data, std::min(n, _size)};
+}
+
+inline MemSpan &
+MemSpan::remove_prefix(ptrdiff_t n)
+{
+  if (n < 0) {
+  } else if (n <= _size) {
+    _size -= n;
+    _data = static_cast<char *>(_data) + n;
+  } else {
+    this->clear();
+  }
+  return *this;
+}
+
+inline MemSpan
+MemSpan::suffix(void const *p) const
+{
+  self_type zret;
+  if (_data <= p && p <= this->data_end()) {
+    zret.assign(const_cast<void *>(p), this->data_end());
+  }
+  return zret;
+}
+
+inline MemSpan
+MemSpan::suffix(ptrdiff_t n) const
+{
+  self_type zret;
+  if (n < 0) {
+    n = std::max(ptrdiff_t{0}, n + _size);
+  }
+  if (n <= _size) {
+    zret.assign(static_cast<char *>(_data) + n, _size - n);
+  }
+  return zret;
+}
+
+inline MemSpan &
+MemSpan::remove_suffix(void const *p)
+{
+  if (_data <= p && p <= this->data_end()) {
+    _size -= distance(this->data_end(), p);
+  }
+  return *this;
+}
+
+inline MemSpan &
+MemSpan::remove_suffix(ptrdiff_t n)
+{
+  if (n < 0) {
+    n = std::max(ptrdiff_t{0}, n + _size);
+  }
+  if (n <= _size) {
+    _size -= n;
+    _data = static_cast<char *>(_data) + n;
+  }
+  return *this;
+}
+
+template <typename V>
+inline V
+MemSpan::at(ptrdiff_t n) const
+{
+  return static_cast<V *>(_data)[n];
+}
+
+template <typename V>
+inline V const *
+MemSpan::ptr(ptrdiff_t n) const
+{
+  return static_cast<V const *>(_data) + n;
+}
+
+template <typename V>
+inline V *
+MemSpan::find(V v) const
+{
+  for (V *spot = static_cast<V *>(_data), *limit = spot + (_size / sizeof(V)); spot < limit; ++spot)
+    if (v == *spot)
+      return spot;
+  return nullptr;
+}
+
+// Specialize char for performance.
+template <>
+inline char *
+MemSpan::find(char v) const
+{
+  return static_cast<char *>(memchr(_data, v, _size));
+}
+
+template <typename V, typename F>
+inline V *
+MemSpan::find_if(F const &pred)
+{
+  for (V *p = static_cast<V *>(_data), *limit = p + (_size / sizeof(V)); p < limit; ++p)
+    if (pred(*p))
+      return p;
+  return nullptr;
+}
+
+} // namespace ts
+
+namespace std
+{
+ostream &
+operator<<(ostream &os, const ts::MemSpan &b)
+{
+  if (os.good()) {
+    ostringstream out;
+    out << b.size() << '@' << hex << b.data();
+    os << out.str();
+  }
+  return os;
+}
+} // std
diff --git a/lib/ts/TextView.h b/lib/ts/TextView.h
index dce8e51..5554dd9 100644
--- a/lib/ts/TextView.h
+++ b/lib/ts/TextView.h
@@ -1033,479 +1033,3 @@ namespace std
 {
 ostream &operator<<(ostream &os, const ts::TextView &b);
 }
-
-#if 0
-// Preserved for now, I may want this back later.
-/** A read only view of contiguous piece of memory.
-
-    A @c MemView does not own the memory to which it refers, it is simply a view of part of some
-    (presumably) larger memory object. The purpose is to allow working in a read only way a specific
-    part of the memory. This can avoid copying or allocation by allocating all needed memory at once
-    and then working with it via instances of this class.
-
-    MemView is based on an earlier class ConstBuffer and influenced by Boost.string_ref. Neither
-    of these were adequate for how use of @c ConstBuffer evolved and so @c MemView is @c
-    ConstBuffer with some additional stylistic changes based on Boost.string_ref.
-
-    This class is closely integrated with @c StringView. These classes have the same underlying
-    implementation and are differentiated only because of the return types and a few string oriented
-    methods.
- */
-class MemView
-{
-  typedef MemView self; ///< Self reference type.
-
-protected:
-  const void *_ptr = nullptr; ///< Pointer to base of memory chunk.
-  size_t _size     = 0;       ///< Size of memory chunk.
-
-public:
-  /// Default constructor (empty buffer).
-  constexpr MemView();
-
-  /** Construct explicitly with a pointer and size.
-   */
-  constexpr MemView(const void *ptr, ///< Pointer to buffer.
-                    size_t n         ///< Size of buffer.
-                    );
-
-  /** Construct from a half open range of two pointers.
-      @note The instance at @start is in the view but the instance at @a end is not.
-  */
-  template <typename T>
-  constexpr MemView(T const *start, ///< First byte in the view.
-                    T const *end    ///< First byte not in the view.
-                    );
-
-  /** Construct from a half open range of two pointers.
-      @note The instance at @start is in the view but the instance at @a end is not.
-  */
-  MemView(void const *start, ///< First byte in the view.
-          void const *end    ///< First byte not in the view.
-          );
-
-  /** Construct from nullptr.
-      This implicitly makes the length 0.
-  */
-  constexpr MemView(std::nullptr_t);
-
-  /// Convert from StringView.
-  constexpr MemView(StringView const &that);
-
-  /** Equality.
-
-      This is effectively a pointer comparison, buffer contents are not compared.
-
-      @return @c true if @a that refers to the same view as @a this,
-      @c false otherwise.
-   */
-  bool operator==(self const &that) const;
-
-  /** Inequality.
-      @return @c true if @a that does not refer to the same view as @a this,
-      @c false otherwise.
-   */
-  bool operator!=(self const &that) const;
-
-  /// Assignment - the view is copied, not the content.
-  self &operator=(self const &that);
-
-  /** Shift the view to discard the first byte.
-      @return @a this.
-  */
-  self &operator++();
-
-  /** Shift the view to discard the leading @a n bytes.
-      @return @a this
-  */
-  self &operator+=(size_t n);
-
-  /// Check for empty view.
-  /// @return @c true if the view has a zero pointer @b or size.
-  bool operator!() const;
-
-  /// Check for non-empty view.
-  /// @return @c true if the view refers to a non-empty range of bytes.
-  explicit operator bool() const;
-
-  /// Check for empty view (no content).
-  /// @see operator bool
-  bool isEmpty() const;
-
-  /// @name Accessors.
-  //@{
-  /// Pointer to the first byte in the view.
-  const void *begin() const;
-  /// Pointer to first byte not in the view.
-  const void *end() const;
-  /// Number of bytes in the view.
-  constexpr size_t size() const;
-  /// Memory pointer.
-  /// @note This is equivalent to @c begin currently but it's probably good to have separation.
-  constexpr const void *ptr() const;
-  /// @return the @a V value at index @a n.
-  template <typename V> V at(ssize_t n) const;
-  /// @return a pointer to the @a V value at index @a n.
-  template <typename V> V const *at_ptr(ssize_t n) const;
-  //@}
-
-  /// Set the view.
-  /// This is faster but equivalent to constructing a new view with the same
-  /// arguments and assigning it.
-  /// @return @c this.
-  self &setView(const void *ptr, ///< Buffer address.
-                size_t n = 0     ///< Buffer size.
-                );
-
-  /// Set the view.
-  /// This is faster but equivalent to constructing a new view with the same
-  /// arguments and assigning it.
-  /// @return @c this.
-  self &setView(const void *start, ///< First valid character.
-                const void *end    ///< First invalid character.
-                );
-
-  /// Clear the view (become an empty view).
-  self &clear();
-
-  /// @return @c true if the byte at @a *p is in the view.
-  bool contains(const void *p) const;
-
-  /** Find a value.
-      The memory is searched as if it were an array of the value type @a T.
-
-      @return A pointer to the first occurrence of @a v in @a this
-      or @c nullptr if @a v is not found.
-  */
-  template <typename V> const V *find(V v) const;
-
-  /** Find a value.
-      The memory is searched as if it were an array of the value type @a V.
-
-      @return A pointer to the first value for which @a pred is @c true otherwise
-      @c nullptr.
-  */
-  template <typename V> const V *find(std::function<bool(V)> const &pred);
-
-  /** Get the initial segment of the view before @a p.
-
-      The byte at @a p is not included. If @a p is not in the view an empty view
-      is returned.
-
-      @return A buffer that contains all data before @a p.
-  */
-  self prefix(const void *p) const;
-
-  /** Split the view at @a p.
-
-      The view is split in to two parts at @a p and the prefix is returned. The view is updated to
-     contain the bytes not returned in the prefix. The prefix will not contain @a p.
-
-      @note If @a *p refers to a byte that is not in @a this then @a this is not changed and an empty
-      buffer is returned. Therefore this method can be safely called with the return value of
-      calling @c find.
-
-      @return A buffer containing data up to but not including @a p.
-
-      @see extractPrefix
-  */
-  self splitPrefix(const void *p);
-
-  /** Extract a prefix delimited by @a p.
-
-      A prefix of @a this is removed from the view and returned. If @a p is not in the view then the
-      entire view is extracted and returned.
-
-      If @a p points at a byte in the view this is identical to @c splitPrefix.  If not then the
-      entire view in @a this will be returned and @a this will become an empty view.
-
-      @return The prefix bounded at @a p or the entire view if @a p is not a byte in the view.
-
-      @see splitPrefix
-  */
-  self extractPrefix(const void *p);
-
-  /** Get the trailing segment of the view after @a p.
-
-      The byte at @a p is not included. If @a p is not in the view an empty view is returned.
-
-      @return A buffer that contains all data after @a p.
-  */
-  self suffix(const void *p) const;
-
-  /** Split the view at @a p.
-
-      The view is split in to two parts and the suffix is returned. The view is updated to contain
-      the bytes not returned in the suffix. The suffix will not contain @a p.
-
-      @note If @a p does not refer to a byte in the view, an empty view is returned and @a this is
-      unchanged.
-
-      @return @a this.
-  */
-  self splitSuffix(const void *p);
-};
-
-inline constexpr MemView::MemView()
-{
-}
-inline constexpr MemView::MemView(void const *ptr, size_t n) : _ptr(ptr), _size(n)
-{
-}
-template <typename T> constexpr MemView::MemView(const T *start, const T *end) : _ptr(start), _size((end - start) * sizeof(T))
-{
-}
-// <void*> is magic, handle that specially.
-// No constexpr because the spec specifically forbids casting from <void*> to a typed pointer.
-inline MemView::MemView(void const *start, void const *end)
-  : _ptr(start), _size(static_cast<const char *>(end) - static_cast<char const *>(start))
-{
-}
-inline constexpr MemView::MemView(std::nullptr_t) : _ptr(nullptr), _size(0)
-{
-}
-inline constexpr MemView::MemView(StringView const &that) : _ptr(that.ptr()), _size(that.size())
-{
-}
-
-inline MemView &
-MemView::setView(const void *ptr, size_t n)
-{
-  _ptr  = ptr;
-  _size = n;
-  return *this;
-}
-
-inline MemView &
-MemView::setView(const void *ptr, const void *limit)
-{
-  _ptr  = ptr;
-  _size = static_cast<const char *>(limit) - static_cast<const char *>(ptr);
-  return *this;
-}
-
-inline MemView &
-MemView::clear()
-{
-  _ptr  = 0;
-  _size = 0;
-  return *this;
-}
-
-inline bool
-MemView::operator==(self const &that) const
-{
-  return _size == that._size && _ptr == that._ptr;
-}
-
-inline bool
-MemView::operator!=(self const &that) const
-{
-  return !(*this == that);
-}
-
-inline bool MemView::operator!() const
-{
-  return !(_ptr && _size);
-}
-
-inline MemView::operator bool() const
-{
-  return _ptr && _size;
-}
-
-inline bool
-MemView::isEmpty() const
-{
-  return !(_ptr && _size);
-}
-
-inline MemView &MemView::operator++()
-{
-  _ptr = static_cast<const char *>(_ptr) + 1;
-  --_size;
-  return *this;
-}
-
-inline MemView &
-MemView::operator+=(size_t n)
-{
-  if (n > _size) {
-    _ptr  = nullptr;
-    _size = 0;
-  } else {
-    _ptr = static_cast<const char *>(_ptr) + n;
-    _size -= n;
-  }
-  return *this;
-}
-
-inline const void *
-MemView::begin() const
-{
-  return _ptr;
-}
-inline constexpr const void *
-MemView::ptr() const
-{
-  return _ptr;
-}
-
-inline const void *
-MemView::end() const
-{
-  return static_cast<const char *>(_ptr) + _size;
-}
-
-inline constexpr size_t
-MemView::size() const
-{
-  return _size;
-}
-
-inline MemView &
-MemView::operator=(MemView const &that)
-{
-  _ptr  = that._ptr;
-  _size = that._size;
-  return *this;
-}
-
-inline bool
-MemView::contains(const void *p) const
-{
-  return _ptr <= this->begin() && p < this->end();
-}
-
-inline MemView
-MemView::prefix(const void *p) const
-{
-  self zret;
-  if (this->contains(p))
-    zret.setView(_ptr, p);
-  return zret;
-}
-
-inline MemView
-MemView::splitPrefix(const void *p)
-{
-  self zret; // default to empty return.
-  if (this->contains(p)) {
-    zret.setView(_ptr, p);
-    this->setView(p, this->end());
-  }
-  return zret;
-}
-
-inline MemView
-MemView::extractPrefix(const void *p)
-{
-  self zret{this->splitPrefix(p)};
-
-  // For extraction if zret is empty, use up all of @a this
-  if (!zret) {
-    zret = *this;
-    this->clear();
-  }
-
-  return zret;
-}
-
-inline MemView
-MemView::suffix(const void *p) const
-{
-  self zret;
-  if (this->contains(p))
-    zret.setView(p, this->end());
-  return zret;
-}
-
-inline MemView
-MemView::splitSuffix(const void *p)
-{
-  self zret;
-  if (this->contains(p)) {
-    zret.setView(p, this->end());
-    this->setView(_ptr, p);
-  }
-  return zret;
-}
-
-template <typename V>
-inline V
-MemView::at(ssize_t n) const
-{
-  return static_cast<V const *>(_ptr)[n];
-}
-
-template <typename V>
-inline V const *
-MemView::at_ptr(ssize_t n) const
-{
-  return static_cast<V const *>(_ptr) + n;
-}
-
-template <typename V>
-inline const V *
-MemView::find(V v) const
-{
-  for (const V *spot = static_cast<const V *>(_ptr), limit = spot + (_size / sizeof(V)); spot < limit; ++spot)
-    if (v == *spot)
-      return spot;
-  return nullptr;
-}
-
-// Specialize char for performance.
-template <>
-inline const char *
-MemView::find(char v) const
-{
-  return static_cast<const char *>(memchr(_ptr, v, _size));
-}
-
-template <typename V>
-inline const V *
-MemView::find(std::function<bool(V)> const &pred)
-{
-  for (const V *p = static_cast<const V *>(_ptr), *limit = p + (_size / sizeof(V)); p < limit; ++p)
-    if (pred(*p))
-      return p;
-  return nullptr;
-}
-
-inline int
-memcmp(MemView const &lhs, MemView const &rhs)
-{
-  int zret;
-  size_t n;
-
-  // Seems a bit ugly but size comparisons must be done anyway to get the memcmp args.
-  if (lhs.size() < rhs.size()) {
-    zret = 1, n = lhs.size();
-  } else {
-    n    = rhs.size();
-    zret = rhs.size() < lhs.size() ? -1 : 0;
-  }
-
-  int r = ::memcmp(lhs.ptr(), rhs.ptr(), n);
-  if (0 != r) { // If we got a not-equal, override the size based result.
-    zret = r;
-  }
-
-  return zret;
-}
-
-namespace std
-{
-ostream &
-operator<<(ostream &os, const ts::MemView &b)
-{
-  if (os.good()) {
-    ostringstream out;
-    out << b.size() << '@' << hex << b.ptr();
-    os << out.str();
-  }
-  return os;
-}
-}
-#endif
diff --git a/lib/ts/unit-tests/test_MemSpan.cc b/lib/ts/unit-tests/test_MemSpan.cc
new file mode 100644
index 0000000..d5949c1
--- /dev/null
+++ b/lib/ts/unit-tests/test_MemSpan.cc
@@ -0,0 +1,56 @@
+/** @file
+
+    TextView unit tests.
+
+    @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+#include <catch.hpp>
+#include <ts/MemSpan.h>
+#include <iostream>
+
+using ts::MemSpan;
+
+TEST_CASE("MemSpan", "[libts][MemSpan]")
+{
+  int idx[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+  char buff[1024];
+  MemSpan span(buff, sizeof(buff));
+  MemSpan left = span.prefix(512);
+  REQUIRE(left.size() == 512);
+  REQUIRE(span.size() == 1024);
+  span.remove_prefix(512);
+  REQUIRE(span.size() == 512);
+  REQUIRE(left.data_end() == span.data());
+
+  MemSpan idx_span(idx);
+  REQUIRE(idx_span.size() == sizeof(idx));
+  REQUIRE(idx_span.data() == idx);
+  REQUIRE(idx_span.find<int>(4) == idx + 4);
+  REQUIRE(idx_span.find<int>(8) == idx + 8);
+  MemSpan a = idx_span.suffix(idx_span.find<int>(7));
+  REQUIRE(a.at<int>(0) == 7);
+  MemSpan b = idx_span.suffix(-(4 * sizeof(int)));
+  REQUIRE(b.size() == 4 * sizeof(int));
+  REQUIRE(b.at<int>(0) == 7);
+  REQUIRE(a == b);
+  MemSpan c = idx_span.prefix(3 * sizeof(int));
+  REQUIRE(c.size() == 3 * sizeof(int));
+  REQUIRE(c.ptr<int>(2) == idx + 2);
+}

-- 
To stop receiving notification emails like this one, please contact
amc@apache.org.