trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a..@apache.org
Subject [trafficserver] branch master updated: Add 'BufferWriter' class for constructing strings/sequences of characters.
Date Tue, 29 Aug 2017 13:46:57 GMT
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 fa7190b  Add 'BufferWriter' class for constructing strings/sequences of characters.
fa7190b is described below

commit fa7190b09b017a52a9286893747e5f632fd4e7ab
Author: Walt Karas <wkaras@yahoo-inc.com>
AuthorDate: Mon Jul 24 18:08:07 2017 +0000

    Add 'BufferWriter' class for constructing strings/sequences of characters.
---
 lib/ts/BufferWriter.h              | 326 +++++++++++++++++++++++++++++++++++++
 lib/ts/Makefile.am                 |   4 +-
 lib/ts/unit-tests/BufferWriter.cpp | 311 +++++++++++++++++++++++++++++++++++
 3 files changed, 640 insertions(+), 1 deletion(-)

diff --git a/lib/ts/BufferWriter.h b/lib/ts/BufferWriter.h
new file mode 100644
index 0000000..4d604fc
--- /dev/null
+++ b/lib/ts/BufferWriter.h
@@ -0,0 +1,326 @@
+#if !defined TS_BUFFERWRITER_H_
+#define TS_BUFFERWRITER_H_
+
+/** @file
+
+    Utilities for generating character sequences.
+
+    @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 <utility>
+#include <cstring>
+
+#include <ts/string_view.h>
+#include <ts/ink_assert.h>
+
+namespace ts
+{
+// Abstract class.
+//
+class BufferWriter
+{
+public:
+  // The write() functions "add" characters at the end.  If these functions discard any characters,
this must put the instance
+  // in an error state (indicated by the override of error() ).  Derived classes must not
assume the write() functions will
+  // not be called when the instance is in an error state.
+
+  virtual BufferWriter &write(char c) = 0;
+
+  virtual BufferWriter &
+  write(const void *data, size_t length)
+  {
+    const char *d = static_cast<const char *>(data);
+
+    while (length--) {
+      write(*(d++));
+    }
+    return *this;
+  }
+
+  BufferWriter &
+  write(const string_view &sV)
+  {
+    return write(sV.data(), sV.size());
+  }
+
+  // Returns true if the instance is in an error state.
+  //
+  virtual bool error() const = 0;
+
+  // Returns pointer to an auxiliary buffer (or nullptr if none is available).  Succeeding
calls to non-const member functions,
+  // other than auxBuffer(), must be presumed to invalidate the current auxiliary buffer
(contents and address).  Results
+  // are UNDEFINED if character locations at or beyond auxBuffer()[remaining()] are written.
+  //
+  virtual char *
+  auxBuffer()
+  {
+    return nullptr;
+  }
+
+  // Write the first n characters that have been placed in the auxiliary buffer.  This call
invalidates the auxiliary buffer.
+  // This function should not be called if no auxiliary buffer is available.
+  //
+  virtual BufferWriter &
+  write(size_t n)
+  {
+    return *this;
+  }
+
+  // Returns number of total characters that can be written without causing an error condidtion.
+  //
+  virtual size_t capacity() const = 0;
+
+  // Total number of characters that have been written, including those discarded due to
an error condition.
+  //
+  virtual size_t extent() const = 0;
+
+  // Total number of characters that are in the buffer (successfully written and not discarded).
+  //
+  size_t
+  size() const
+  {
+    size_t e = extent(), c = capacity();
+
+    return e < c ? e : c;
+  }
+
+  // Returns number of additional characters that can be written without causing an error
condidtion.
+  //
+  size_t
+  remaining() const
+  {
+    return capacity() - size();
+  }
+
+  // Reduce the capacity by n characters, potentially creating an error condition.
+  //
+  virtual BufferWriter &clip(size_t n) = 0;
+
+  // If there is an error condition, this function clears it and sets the extent to the size.
 It then increases the
+  // capacity by n characters.
+  //
+  virtual BufferWriter &extend(size_t n) = 0;
+
+  // Make destructor virtual.
+  //
+  virtual ~BufferWriter() {}
+};
+
+// A buffer writer that writes to an array of char that is external to the writer instance.
+//
+class FixedBufferWriter : public BufferWriter
+{
+protected:
+  FixedBufferWriter(char *buf, size_t capacity, size_t attempted) : _buf(buf), _capacity(capacity),
_attempted(attempted) {}
+
+public:
+  // 'buf' is a pointer to the external array of char to write to.  'capacity' is the number
of bytes in the array.
+  //
+  // If you create a instance of this class with capacity == 0 (and a nullptr buffer), you
can use it to measure the number of
+  // characters a series of writes would result it (from the extent() value) without actually
writing.
+  //
+  FixedBufferWriter(char *buf, size_t capacity) : FixedBufferWriter(buf, capacity, 0) {}
+
+  FixedBufferWriter &
+  write(char c) override
+  {
+    if (_attempted < _capacity) {
+      _buf[_attempted] = c;
+    }
+    ++_attempted;
+
+    return *this;
+  }
+
+  FixedBufferWriter &
+  write(const void *data, size_t length) override
+  {
+    size_t newSize = _attempted + length;
+
+    if (newSize <= _capacity) {
+      std::memcpy(_buf + _attempted, data, length);
+
+    } else if (_attempted < _capacity) {
+      std::memcpy(_buf + _attempted, data, _capacity - _attempted);
+    }
+    _attempted = newSize;
+
+    return *this;
+  }
+
+  // It's not clear to my why g++ needs this using declaration in order to consider the inherited
versions of 'write' when
+  // resolving calls to a 'write' member ( wkaras@oath.com ).
+  //
+  using BufferWriter::write;
+
+  bool
+  error() const override
+  {
+    return _attempted > _capacity;
+  }
+
+  char *
+  auxBuffer() override
+  {
+    return error() ? nullptr : _buf + _attempted;
+  }
+
+  FixedBufferWriter &
+  write(size_t n) override
+  {
+    _attempted += n;
+
+    return *this;
+  }
+
+  size_t
+  capacity() const override
+  {
+    return _capacity;
+  }
+
+  size_t
+  extent() const override
+  {
+    return _attempted;
+  }
+
+  FixedBufferWriter &
+  clip(size_t n) override
+  {
+    ink_assert(n <= _capacity);
+
+    _capacity -= n;
+
+    return *this;
+  }
+
+  FixedBufferWriter &
+  extend(size_t n) override
+  {
+    if (error()) {
+      _attempted = _capacity;
+    }
+
+    _capacity += n;
+
+    return *this;
+  }
+
+  // Reduce extent.  If extent is less than capacity, error condition is cleared.
+  //
+  void
+  reduce(size_t smallerExtent)
+  {
+    ink_assert(smallerExtent <= _attempted);
+
+    _attempted = smallerExtent;
+  }
+
+  // Provide a string_view of all successfully written characters.
+  //
+  string_view
+  view() const
+  {
+    return string_view(_buf, size());
+  }
+
+  operator string_view() const { return view(); }
+
+  // No copying
+  //
+  FixedBufferWriter(const FixedBufferWriter &) = delete;
+  FixedBufferWriter &operator=(const FixedBufferWriter &) = delete;
+
+  // Moving is OK.
+  //
+  FixedBufferWriter(FixedBufferWriter &&) = default;
+  FixedBufferWriter &operator=(FixedBufferWriter &&) = default;
+
+protected:
+  char *const _buf;
+
+  size_t _capacity;
+
+  size_t _attempted; // Number of characters written, including those discarded due error
condition.
+};
+
+// A buffer writer that writes to an array of char (of fixed dimension N) that is internal
to the writer instance.
+// It's called 'local' because instances are typically declared as stack-allocated, local
function variables.
+//
+template <size_t N> class LocalBufferWriter : public FixedBufferWriter
+{
+public:
+  LocalBufferWriter() : FixedBufferWriter(_arr, N) {}
+
+  LocalBufferWriter(const LocalBufferWriter &that) : FixedBufferWriter(_arr, that._capacity,
that._attempted)
+  {
+    std::memcpy(_arr, that._arr, size());
+  }
+
+  LocalBufferWriter &
+  operator=(const LocalBufferWriter &that)
+  {
+    if (this != &that) {
+      _capacity = that._capacity;
+
+      _attempted = that._attempted;
+
+      std::memcpy(_buf, that._buf, size());
+    }
+
+    return *this;
+  }
+
+  LocalBufferWriter &
+  extend(size_t n) override
+  {
+    if (error()) {
+      _attempted = _capacity;
+    }
+
+    _capacity += n;
+
+    ink_assert(_capacity < N);
+
+    return *this;
+  }
+
+  // Move construction/assignment intentionally defaulted to copying.
+
+protected:
+  char _arr[N];
+};
+
+BufferWriter &
+operator<<(BufferWriter &b, char c)
+{
+  return b.write(c);
+}
+
+BufferWriter &
+operator<<(BufferWriter &b, const string_view &sv)
+{
+  return b.write(sv);
+}
+
+} // end namespace ts
+
+#endif // include once
diff --git a/lib/ts/Makefile.am b/lib/ts/Makefile.am
index 734c6ba..552e01e 100644
--- a/lib/ts/Makefile.am
+++ b/lib/ts/Makefile.am
@@ -183,6 +183,7 @@ libtsutil_la_SOURCES = \
   SourceLocation.cc \
   SourceLocation.h \
   string_view.h \
+  BufferWriter.h \
   TestBox.h \
   TextBuffer.cc \
   TextBuffer.h \
@@ -253,7 +254,8 @@ test_tslib_LDADD = libtsutil.la
 test_tslib_SOURCES = \
 	unit-tests/main.cpp \
 	unit-tests/test_IpMap.cc \
-	unit-tests/test_layout.cpp
+	unit-tests/test_layout.cpp \
+	unit-tests/BufferWriter.cpp
 
 CompileParseRules_SOURCES = CompileParseRules.cc
 
diff --git a/lib/ts/unit-tests/BufferWriter.cpp b/lib/ts/unit-tests/BufferWriter.cpp
new file mode 100644
index 0000000..1f8b855
--- /dev/null
+++ b/lib/ts/unit-tests/BufferWriter.cpp
@@ -0,0 +1,311 @@
+/** @file
+
+    Unit tests for BufferWriter.h.
+
+    @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 "BufferWriter.h"
+
+#include "catch.hpp"
+
+#include "string_view.h"
+
+#include <cstring>
+
+namespace
+{
+
+ts::string_view three[] = {"a", "", "bcd"};
+
+}
+
+TEST_CASE("BufferWriter::write(StringView)", "[BWWSV]")
+{
+  class X : public ts::BufferWriter
+  {
+    size_t i, j;
+
+  public:
+    bool good;
+
+    X() : i(0), j(0), good(true) {}
+
+    X &
+    write(char c) override
+    {
+      while (j == three[i].size()) {
+        ++i;
+        j = 0;
+      }
+
+      if ((i >= 3) or (c != three[i][j])) {
+        good = false;
+      }
+
+      ++j;
+
+      return *this;
+    }
+
+    bool
+    error() const override
+    {
+      return false;
+    }
+
+    // Dummies.
+    size_t capacity() const override { return 0; }
+    size_t extent() const override { return 0; }
+    X & clip(size_t) override { return *this; }
+    X & extend(size_t) override { return *this; }
+  };
+
+  X x;
+
+  static_cast<ts::BufferWriter &>(x).write(three[0]).write(three[1]).write(three[2]);
+
+  REQUIRE(x.good);
+}
+
+namespace
+{
+template <size_t N> using LBW = ts::LocalBufferWriter<N>;
+}
+
+TEST_CASE("Minimal Local Buffer Writer", "[BWLM]")
+{
+  LBW<1> bw;
+
+  REQUIRE(!((bw.capacity() != 1) or (bw.size() != 0) or bw.error() or (bw.remaining() !=
1)));
+
+  bw.write('#');
+
+  REQUIRE(!((bw.capacity() != 1) or (bw.size() != 1) or bw.error() or (bw.remaining() !=
0)));
+
+  REQUIRE(bw.view() == "#");
+
+  bw.write('#');
+
+  REQUIRE(bw.error());
+
+  bw.reduce(1);
+
+  REQUIRE(!((bw.capacity() != 1) or (bw.size() != 1) or bw.error() or (bw.remaining() !=
0)));
+
+  REQUIRE(bw.view() == "#");
+}
+
+namespace
+{
+
+template <class BWType>
+bool twice(BWType &bw)
+{
+  if ((bw.capacity() != 20) or (bw.size() != 0) or bw.error() or (bw.remaining() != 20))
{
+    return false;
+  }
+
+  bw.write('T');
+
+  if ((bw.capacity() != 20) or (bw.size() != 1) or bw.error() or (bw.remaining() != 19))
{
+    return false;
+  }
+
+  if (bw.view() != "T") {
+    return false;
+  }
+
+  bw.write("he").write(' ').write("quick").write(' ').write("brown");
+
+  if ((bw.capacity() != 20) or bw.error() or (bw.remaining() != (21 - sizeof("The quick brown"))))
{
+    return false;
+  }
+
+  if (bw.view() != "The quick brown") {
+    return false;
+  }
+
+  bw.reduce(0);
+
+  bw << "The" << ' ' << "quick" << ' ' << "brown";
+
+  if ((bw.capacity() != 20) or bw.error() or (bw.remaining() != (21 - sizeof("The quick brown"))))
{
+    return false;
+  }
+
+  if (bw.view() != "The quick brown") {
+    return false;
+  }
+
+  bw.reduce(0);
+
+  bw.write("The", 3).write(' ').write("quick", 5).write(' ').write(ts::string_view("brown",
5));
+
+  if ((bw.capacity() != 20) or bw.error() or (bw.remaining() != (21 - sizeof("The quick brown"))))
{
+    return false;
+  }
+
+  if (bw.view() != "The quick brown") {
+    return false;
+  }
+
+  std::strcpy(bw.auxBuffer(), " fox");
+  bw.write(sizeof(" fox") - 1);
+
+  if (bw.error()) {
+    return false;
+  }
+
+  if (bw.view() != "The quick brown fox") {
+    return false;
+  }
+
+  bw.write('x');
+
+  if (bw.error()) {
+    return false;
+  }
+
+  bw.write('x');
+
+  if (!bw.error()) {
+    return false;
+  }
+
+  bw.write('x');
+
+  if (!bw.error()) {
+    return false;
+  }
+
+  bw.reduce(sizeof("The quick brown fox") - 1);
+
+  if (bw.error()) {
+    return false;
+  }
+
+  if (bw.view() != "The quick brown fox") {
+    return false;
+  }
+
+  bw.reduce(sizeof("The quick brown") - 1);
+  bw.clip(bw.capacity() + 2 - (sizeof("The quick brown fox") - 1)).write(" fox");
+
+  if (bw.view() != "The quick brown f") {
+    return false;
+  }
+
+  if (!bw.error()) {
+    return false;
+  }
+
+  bw.extend(2).write("ox");
+
+  if (bw.error()) {
+    return false;
+  }
+
+  if (bw.view() != "The quick brown fox") {
+    return false;
+  }
+
+  return true;
+}
+
+} // end anonymous namespace
+
+TEST_CASE("Concrete Buffer Writers 2", "[BWC2]")
+{
+  LBW<20> bw;
+
+  REQUIRE(twice(bw));
+
+  char space[21];
+
+  space[20] = '!';
+
+  ts::FixedBufferWriter fbw(space, 20);
+
+  REQUIRE(twice(fbw));
+
+  REQUIRE(space[20] == '!');
+
+  LBW<20> bw2(bw), bw3;
+
+  REQUIRE(bw2.view() == "The quick brown fox");
+
+  bw3 = bw2;
+
+  REQUIRE(bw3.view() == "The quick brown fox");
+}
+
+TEST_CASE("Discard Buffer Writer", "[BWD]")
+{
+  char scratch[1] = { '!' };
+  ts::FixedBufferWriter bw(scratch, 0);
+
+  REQUIRE(bw.size() == 0);
+  REQUIRE (bw.extent() == 0);
+
+  bw.write('T');
+
+  REQUIRE(bw.size() == 0);
+  REQUIRE(bw.extent() == 1);
+
+  bw.write("he").write(' ').write("quick").write(' ').write("brown");
+
+  REQUIRE(bw.size() == 0);
+  REQUIRE(bw.extent() == (sizeof("The quick brown") - 1));
+
+  bw.reduce(0);
+
+  bw.write("The", 3).write(' ').write("quick", 5).write(' ').write(ts::string_view("brown",
5));
+
+  REQUIRE(bw.size() == 0);
+  REQUIRE(bw.extent() == (sizeof("The quick brown") - 1));
+
+  bw.write(sizeof(" fox") - 1);
+
+  REQUIRE(bw.size() == 0);
+  REQUIRE(bw.extent() == (sizeof("The quick brown fox") - 1));
+
+  bw.reduce(sizeof("The quick brown fox") - 1);
+
+  REQUIRE(bw.size() == 0);
+  REQUIRE(bw.extent() == (sizeof("The quick brown fox") - 1));
+
+  bw.reduce(sizeof("The quick brown") - 1);
+
+  REQUIRE(bw.size() == 0);
+  REQUIRE(bw.extent() == (sizeof("The quick brown") - 1));
+
+  // Make sure no actual writing.
+  //
+  REQUIRE(scratch[0] == '!');
+}
+
+TEST_CASE("Buffer Writer << operator", "[BW<<]")
+{
+  ts::LocalBufferWriter<50> bw;
+
+  bw << "The" << ' ' << "quick" << ' ' << "brown fox";
+
+  REQUIRE(bw.view() == "The quick brown fox");
+}

-- 
To stop receiving notification emails like this one, please contact
['"commits@trafficserver.apache.org" <commits@trafficserver.apache.org>'].

Mime
View raw message