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 <wka...@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 ( wka...@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>'].