blob: 1f888a7536228427c72846486db9743dd6bddf90 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed 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
//
// https://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.
// View classes for arrays and bit arrays.
#ifndef EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_
#define EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include "public/emboss_arithmetic.h"
#include "public/emboss_array_view.h"
#include "public/emboss_text_util.h"
namespace emboss {
// Forward declarations for use by WriteShorthandArrayCommentToTextStream.
namespace prelude {
template <class Parameters, class BitViewType>
class UIntView;
template <class Parameters, class BitViewType>
class IntView;
} // namespace prelude
namespace support {
// Advance direction for ElementViewIterator.
enum class ElementViewIteratorDirection { kForward, kReverse };
// Iterator adapter for elements in a GenericArrayView.
template <class GenericArrayView, ElementViewIteratorDirection kDirection>
class ElementViewIterator {
public:
using iterator_category = ::std::random_access_iterator_tag;
using value_type = typename GenericArrayView::ViewType;
using difference_type = ::std::ptrdiff_t;
using pointer = typename ::std::add_pointer<value_type>::type;
using reference = typename ::std::add_lvalue_reference<value_type>::type;
explicit ElementViewIterator(const GenericArrayView *array_view,
::std::ptrdiff_t index)
: array_view_(array_view), view_((*array_view)[index]), index_(index) {}
ElementViewIterator() = default;
reference operator*() { return view_; }
pointer operator->() { return &view_; }
ElementViewIterator &operator+=(difference_type d) {
index_ += (kDirection == ElementViewIteratorDirection::kForward ? d : -d);
view_ = (*array_view_)[index_];
return *this;
}
ElementViewIterator &operator-=(difference_type d) { return *this += (-d); }
ElementViewIterator &operator++() {
*this += 1;
return *this;
}
ElementViewIterator &operator--() {
*this -= 1;
return *this;
}
ElementViewIterator operator++(int) {
auto copy = *this;
++(*this);
return copy;
}
ElementViewIterator operator--(int) {
auto copy = *this;
--(*this);
return copy;
}
ElementViewIterator operator+(difference_type d) const {
auto copy = *this;
copy += d;
return copy;
}
ElementViewIterator operator-(difference_type d) const {
return *this + (-d);
}
difference_type operator-(const ElementViewIterator &other) const {
return kDirection == ElementViewIteratorDirection::kForward
? index_ - other.index_
: other.index_ - index_;
}
bool operator==(const ElementViewIterator &other) const {
return array_view_ == other.array_view_ && index_ == other.index_;
}
bool operator!=(const ElementViewIterator &other) const {
return !(*this == other);
}
bool operator<(const ElementViewIterator &other) const {
return kDirection == ElementViewIteratorDirection::kForward
? index_ < other.index_
: other.index_ < index_;
}
bool operator<=(const ElementViewIterator &other) const {
return kDirection == ElementViewIteratorDirection::kForward
? index_ <= other.index_
: other.index_ <= index_;
}
bool operator>(const ElementViewIterator &other) const {
return !(*this <= other);
}
bool operator>=(const ElementViewIterator &other) const {
return !(*this < other);
}
private:
const GenericArrayView *array_view_;
typename GenericArrayView::ViewType view_;
::std::ptrdiff_t index_;
};
// View for an array in a structure.
//
// ElementView should be the view class for a single array element (e.g.,
// UIntView<...> or ArrayView<...>).
//
// BufferType is the storage type that will be passed into the array.
//
// kElementSize is the fixed size of a single element, in addressable units.
//
// kAddressableUnitSize is the size of a single addressable unit. It should be
// either 1 (one bit) or 8 (one byte).
//
// ElementViewParameterTypes is a list of the types of parameters which must be
// passed down to each element of the array. ElementViewParameterTypes can be
// empty.
template <class ElementView, class BufferType, ::std::size_t kElementSize,
::std::size_t kAddressableUnitSize,
typename... ElementViewParameterTypes>
class GenericArrayView final {
public:
using ViewType = ElementView;
using ForwardIterator =
ElementViewIterator<GenericArrayView,
ElementViewIteratorDirection::kForward>;
using ReverseIterator =
ElementViewIterator<GenericArrayView,
ElementViewIteratorDirection::kReverse>;
GenericArrayView() : buffer_() {}
explicit GenericArrayView(const ElementViewParameterTypes &... parameters,
BufferType buffer)
: parameters_{parameters...}, buffer_{buffer} {}
ElementView operator[](::std::size_t index) const {
return IndexOperatorHelper<sizeof...(ElementViewParameterTypes) ==
0>::ConstructElement(parameters_, buffer_,
index);
}
ForwardIterator begin() const { return ForwardIterator(this, 0); }
ForwardIterator end() const { return ForwardIterator(this, ElementCount()); }
ReverseIterator rbegin() const {
return ReverseIterator(this, ElementCount() - 1);
}
ReverseIterator rend() const { return ReverseIterator(this, -1); }
// In order to selectively enable SizeInBytes and SizeInBits, it is
// necessary to make them into templates. Further, it is necessary for
// ::std::enable_if to have a dependency on the template parameter, otherwise
// SFINAE won't kick in. Thus, these are templated on an int, and that int
// is (spuriously) used as the left argument to `,` in the enable_if
// condition. The explicit cast to void is needed to silence GCC's
// -Wunused-value.
template <int N = 0>
typename ::std::enable_if<((void)N, kAddressableUnitSize == 8),
::std::size_t>::type
SizeInBytes() const {
return buffer_.SizeInBytes();
}
template <int N = 0>
typename ::std::enable_if<((void)N, kAddressableUnitSize == 1),
::std::size_t>::type
SizeInBits() const {
return buffer_.SizeInBits();
}
::std::size_t ElementCount() const { return SizeOfBuffer() / kElementSize; }
bool Ok() const {
if (!buffer_.Ok()) return false;
if (SizeOfBuffer() % kElementSize != 0) return false;
for (::std::size_t i = 0; i < ElementCount(); ++i) {
if (!(*this)[i].Ok()) return false;
}
return true;
}
template <class OtherElementView, class OtherBufferType>
bool Equals(
const GenericArrayView<OtherElementView, OtherBufferType, kElementSize,
kAddressableUnitSize> &other) const {
if (ElementCount() != other.ElementCount()) return false;
for (::std::size_t i = 0; i < ElementCount(); ++i) {
if (!(*this)[i].Equals(other[i])) return false;
}
return true;
}
template <class OtherElementView, class OtherBufferType>
bool UncheckedEquals(
const GenericArrayView<OtherElementView, OtherBufferType, kElementSize,
kAddressableUnitSize> &other) const {
if (ElementCount() != other.ElementCount()) return false;
for (::std::size_t i = 0; i < ElementCount(); ++i) {
if (!(*this)[i].UncheckedEquals(other[i])) return false;
}
return true;
}
bool IsComplete() const { return buffer_.Ok(); }
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
return ReadArrayFromTextStream(this, stream);
}
template <class Stream>
void WriteToTextStream(Stream *stream,
const TextOutputOptions &options) const {
WriteArrayToTextStream(this, stream, options);
}
BufferType BackingStorage() const { return buffer_; }
private:
// This uses the same technique to select the correct definition of
// SizeOfBuffer() as in the SizeInBits()/SizeInBytes() selection above.
template <int N = 0>
typename ::std::enable_if<((void)N, kAddressableUnitSize == 8),
::std::size_t>::type
SizeOfBuffer() const {
return SizeInBytes();
}
template <int N = 0>
typename ::std::enable_if<((void)N, kAddressableUnitSize == 1),
::std::size_t>::type
SizeOfBuffer() const {
return SizeInBits();
}
// This mess is needed to expand the parameters_ tuple into individual
// arguments to the ElementView constructor. If parameters_ has M elements,
// then:
//
// IndexOperatorHelper<false>::ConstructElement() calls
// IndexOperatorHelper<false, 0>::ConstructElement(), which calls
// IndexOperatorHelper<false, 0, 1>::ConstructElement(), and so on, up to
// IndexOperatorHelper<false, 0, 1, ..., M-1>::ConstructElement(), which calls
// IndexOperatorHelper<true, 0, 1, ..., M>::ConstructElement()
//
// That last call will resolve to the second, specialized version of
// IndexOperatorHelper. That version's ConstructElement() uses
// `std::get<N>(parameters)...`, which will be expanded into
// `std::get<0>(parameters), std::get<1>(parameters), std::get<2>(parameters),
// ..., std::get<M>(parameters)`.
//
// If there are 0 parameters, then operator[]() will call
// IndexOperatorHelper<true>::ConstructElement(), which still works --
// `std::get<N>(parameters)...,` will be replaced by ``.
//
// In C++14, a lot of this can be replaced by std::index_sequence_of, and in
// C++17 it can be replaced with std::apply and a lambda.
//
// An alternate solution would be to force each parameterized view to have a
// constructor that accepts a tuple, instead of individual parameters, but
// that (further) complicates the matrix of constructors for view types.
template <bool, ::std::size_t... N>
struct IndexOperatorHelper {
static ElementView ConstructElement(
const ::std::tuple<ElementViewParameterTypes...> &parameters,
BufferType buffer, ::std::size_t index) {
return IndexOperatorHelper<
sizeof...(ElementViewParameterTypes) == 1 + sizeof...(N), N...,
sizeof...(N)>::ConstructElement(parameters, buffer, index);
}
};
template </**/ ::std::size_t... N>
struct IndexOperatorHelper<true, N...> {
static ElementView ConstructElement(
const ::std::tuple<ElementViewParameterTypes...> &parameters,
BufferType buffer, ::std::size_t index) {
return ElementView(::std::get<N>(parameters)...,
buffer.template GetOffsetStorage<kElementSize, 0>(
kElementSize * index, kElementSize));
}
};
::std::tuple<ElementViewParameterTypes...> parameters_;
BufferType buffer_;
};
// Optionally prints a shorthand representation of a BitArray in a comment.
template <class ElementView, class BufferType, ::std::size_t kElementSize,
::std::size_t kAddressableUnitSize, class Stream>
void WriteShorthandArrayCommentToTextStream(
const GenericArrayView<ElementView, BufferType, kElementSize,
kAddressableUnitSize> *array,
Stream *stream, const TextOutputOptions &options) {
// Intentionally empty. Overload for specific element types.
}
// Prints out the elements of an 8-bit Int or UInt array as characters.
template <class Array, class Stream>
void WriteShorthandAsciiArrayCommentToTextStream(
const Array *array, Stream *stream, const TextOutputOptions &options) {
if (!options.multiline()) return;
if (!options.comments()) return;
if (array->ElementCount() == 0) return;
static constexpr int kCharsPerBlock = 64;
static constexpr char kStandInForNonPrintableChar = '.';
auto start_new_line = [&]() {
stream->Write("\n");
stream->Write(options.current_indent());
stream->Write("# ");
};
for (int i = 0, n = array->ElementCount(); i < n; ++i) {
const int c = (*array)[i].Read();
const bool c_is_printable = (c >= 32 && c <= 126);
const bool starting_new_block = ((i % kCharsPerBlock) == 0);
if (starting_new_block) start_new_line();
stream->Write(c_is_printable ? static_cast<char>(c)
: kStandInForNonPrintableChar);
}
}
// Overload for arrays of UInt.
// Prints out the elements as ASCII characters for arrays of UInt:8.
template <class BufferType, class BitViewType, class Stream,
::std::size_t kElementSize, class Parameters,
class = typename ::std::enable_if<Parameters::kBits == 8>::type>
void WriteShorthandArrayCommentToTextStream(
const GenericArrayView<prelude::UIntView<Parameters, BitViewType>,
BufferType, kElementSize, 8> *array,
Stream *stream, const TextOutputOptions &options) {
WriteShorthandAsciiArrayCommentToTextStream(array, stream, options);
}
// Overload for arrays of UInt.
// Prints out the elements as ASCII characters for arrays of Int:8.
template <class BufferType, class BitViewType, class Stream,
::std::size_t kElementSize, class Parameters,
class = typename ::std::enable_if<Parameters::kBits == 8>::type>
void WriteShorthandArrayCommentToTextStream(
const GenericArrayView<prelude::IntView<Parameters, BitViewType>,
BufferType, kElementSize, 8> *array,
Stream *stream, const TextOutputOptions &options) {
WriteShorthandAsciiArrayCommentToTextStream(array, stream, options);
}
} // namespace support
} // namespace emboss
#endif // EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_