/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2015-2019 Danny Robson */ #pragma once #include "annotation.hpp" #include "cast.hpp" #include "debug/assert.hpp" #include "maths.hpp" #include "platform.hpp" #include "types/traits.hpp" #include #include #include #include #include #include #include #include #include namespace cruft { template struct view { public: using begin_type = BeginT; using end_type = EndT; using iterator = begin_type; //--------------------------------------------------------------------- using value_type = typename std::iterator_traits< remove_restrict_t >::value_type; using size_type = size_t; //--------------------------------------------------------------------- constexpr view (const BeginT &first, const EndT &last) noexcept: m_begin (first), m_end (last) { if constexpr (cruft::is_lteq_orderable_v) { CHECK (m_begin <= m_end); } } template < typename ContainerT, typename = std::void_t< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) > > view (ContainerT &rhs) noexcept ( noexcept (std::declval ().begin ()) && noexcept (std::declval ().end ()) ) : view (rhs.begin (), rhs.end ()) { ; } template < typename ContainerT, typename = std::void_t< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) > > view (const ContainerT &rhs): view (rhs.begin (), rhs.end ()) { ; } //--------------------------------------------------------------------- // Construction from pointer/size representations for ease of use with // legacy C code. template < typename CountT, typename = std::enable_if_t,void> > constexpr view ( const BeginT &_begin, CountT _size ) : view (_begin, _begin + _size) { ; } //--------------------------------------------------------------------- // implicit conversion from const pointer const views to const pointer views template < typename ValueT, typename = std::enable_if_t< std::is_same_v && std::is_same_v > > view (const view &rhs): view ( const_cast (rhs.begin ()), const_cast (rhs.end ()) ) { ; } //--------------------------------------------------------------------- // implicit conversion from pointer views to const pointer views template < typename ValueT, typename = std::enable_if_t< std::is_same_v && std::is_same_v > > view (const view &rhs): view (rhs.begin (), rhs.end ()) { ; } //--------------------------------------------------------------------- // explicitly cater for the char array case so that we don't // accidentally include the trailing null in the data. template view (const char (&value)[N]): view {std::begin (value), std::begin (value) + N - 1} { static_assert (N > 0); } //--------------------------------------------------------------------- view (const char *str): view { str, str + strlen (str) } { ; } //--------------------------------------------------------------------- view (char *str): view (str, str + strlen (str)) { ; } //--------------------------------------------------------------------- template view (char (&value)[N]): view {std::begin (value), std::begin (value) + N - 1} { static_assert (N > 0); } //--------------------------------------------------------------------- template view (const ValueT(&value)[N]): view {std::begin (value), std::end (value)} { ; } //--------------------------------------------------------------------- template view (ValueT(&value)[N]): view {std::begin (value), std::end (value)} { ; } //--------------------------------------------------------------------- constexpr view (const view &) noexcept ( std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_constructible_v ) = default; constexpr view (view &&) noexcept ( std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v ) = default; view& operator= (view const &rhs) noexcept ( std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_assignable_v ) = default; view& operator= (view &&rhs) noexcept ( std::is_nothrow_move_assignable_v && std::is_nothrow_move_assignable_v ) = default; //--------------------------------------------------------------------- // allow null construction of views where IteratorT is constructible // from nullptr_t // // ideally we would avoid exposing this as it promotes use of nulls but // it simplifies construction of views that are data members of classes // when we may not immediately know the values we should contain. constexpr view (std::nullptr_t) noexcept: view {nullptr,nullptr} { ; } //--------------------------------------------------------------------- template view (std::basic_string &val): view (std::data (val), std::size (val)) { ; } //--------------------------------------------------------------------- template view (const std::basic_string &val): view (std::data (val), std::size (val)) { ; } //--------------------------------------------------------------------- template view (const std::vector &rhs): view (std::data (rhs), std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (std::vector &rhs): view (std::data (rhs), std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (std::array &rhs): view (std::data (rhs), std::size (rhs)) { ; } //--------------------------------------------------------------------- template view (const std::array &rhs): view (std::data (rhs), std::size (rhs)) { ; } /////////////////////////////////////////////////////////////////////// constexpr BeginT begin (void) noexcept { return m_begin; } constexpr EndT end (void) noexcept { return m_end; } constexpr BeginT begin (void) const noexcept { return m_begin; } constexpr EndT end (void) const noexcept { return m_end; } //--------------------------------------------------------------------- constexpr BeginT cbegin (void) const noexcept { return m_begin; } constexpr EndT cend (void) const noexcept { return m_end; } //--------------------------------------------------------------------- auto data (void) noexcept { return begin (); } auto data (void) const noexcept { return begin (); } //--------------------------------------------------------------------- auto& front (void) noexcept { return *m_begin; } auto& front (void) const noexcept { return *m_begin; } //--------------------------------------------------------------------- auto& back (void) noexcept { return *(m_end - 1); } auto& back (void) const noexcept { return *(m_end - 1); } /////////////////////////////////////////////////////////////////////// /// Returns true if the size of the view is zero. constexpr bool empty (void) const noexcept { return m_begin == m_end; } ///-------------------------------------------------------------------- /// Returns true if the view is not empty; ie, there is data remaining. explicit constexpr operator bool () const noexcept { return not empty (); } ///-------------------------------------------------------------------- /// Returns the number of items in the view. constexpr auto size (void) const noexcept { return static_cast (std::distance (m_begin, m_end)); } ///-------------------------------------------------------------------- /// Returns a signed count of items in the view. constexpr ssize_t ssize (void) const noexcept { return std::distance (m_begin, m_end); } ///-------------------------------------------------------------------- /// Returns a subview of the first `count` elements of this view. [[nodiscard]] constexpr auto redim (size_type count) const { if constexpr (std::is_signed_v) assert (count >= 0); if (count > size ()) throw std::invalid_argument ("redim to higher size not allowed"); return view { m_begin, m_begin + count }; }; ///-------------------------------------------------------------------- /// Returns two subviews split at `pos`. /// /// The first view extends from `begin` to `pos`, and the second view /// extends from `pos` to `end`. [[nodiscard]] constexpr std::pair< view, view > split (BeginT pos) const { CHECK_GE (pos, m_begin); CHECK_LE (pos, m_end ); return { { m_begin, pos }, { pos, m_end } }; } ///-------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto split (IndexT idx) const { // It's ok if `idx` points to the end iterator; this just means the // second element of the returned pair is an empty view. static_assert ( std::numeric_limits::max () <= std::numeric_limits::max () ); CHECK_GE (idx, IndexT {0}); CHECK_LE (cruft::cast::lossless (idx), size ()); auto last = m_begin; std::advance (last, idx); return split (last); } //--------------------------------------------------------------------- // slices a view using python indexing semantics. ie, // "abc".slice(0, 3) == "abc" // "abc".slice(0, -1) == "abc" // "abc".slice(0, -2) == "ab" template [[nodiscard]] constexpr auto slice (IndexA a, IndexB b) const { CHECK_INCLUSIVE (cruft::abs (a), IndexA {0}, cruft::cast::lossless (size ())); CHECK_INCLUSIVE (cruft::abs (b), IndexB {0}, cruft::cast::lossless (size ())); auto first = m_begin; auto last = m_begin; std::advance (first, a < 0 ? size () + a + 1 : a); std::advance (last, b < 0 ? size () + b + 1 : b); return view { first, last }; } //--------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto head (IndexT idx) const { return std::get<0> (split (idx)); } //--------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto tail (IndexT idx) const { return std::get<1> (split (idx)); } //--------------------------------------------------------------------- template < typename IndexT, typename = std::enable_if_t> > [[nodiscard]] constexpr auto consume (IndexT count) const { auto [a,b] = split (count); (void)a; return b; } //--------------------------------------------------------------------- [[nodiscard]] constexpr view consume (view prefix) const { assert (prefix.begin () == begin ()); assert (prefix.end () < end ()); return { prefix.end (), end () }; } [[nodiscard]] constexpr view consume (const BeginT pos) const { return { pos, end () }; } /////////////////////////////////////////////////////////////////////// /// Explicitly cast to a view with different iterator types. /// /// The source and destination iterator types must be: /// * pointers /// * alignable /// /// It is undefined behaviour to cast with begin and/or end iterators /// that do not have natural alignment. Instrumented builds _may_ /// provide diagnostics or assertions in this case. /// /// \tparam ValueT The new iterator type /// \return A view that uses the new iterator type template < typename ValueT, typename = std::enable_if_t< // We can only convert views that use pointer iterators std::is_pointer_v && std::is_pointer_v && std::is_same_v && std::is_pointer_v > > view cast (void) const { // The values they point to must allow for alignment in one // direction or another. // // We prefer a static_assert over SFINAE because it reduces the // header burden for users (they do not need to include the // implementation of the pointer values to satisfy // iterator_traits), and it is quite unlikely we want to disable // this only if alignment is incompatible). static_assert ( sizeof (typename std::iterator_traits::value_type) % sizeof (typename std::iterator_traits::value_type) == 0 || sizeof (typename std::iterator_traits::value_type) % sizeof (typename std::iterator_traits::value_type) == 0 ); return { cast::alignment (m_begin), cast::alignment (m_end) }; } /////////////////////////////////////////////////////////////////////// constexpr decltype(auto) operator[] (size_t idx) noexcept { CHECK_GE (idx, 0u); CHECK_LT (idx, size ()); return *std::next (begin (), idx); } //--------------------------------------------------------------------- constexpr decltype(auto) operator[] (size_t idx) const noexcept { CHECK_GE (idx, 0u); CHECK_LT (idx, size ()); return *std::next (begin (), idx); } private: /////////////////////////////////////////////////////////////////////// BeginT m_begin; EndT m_end; }; //------------------------------------------------------------------------- template view (ValueT(&)[N]) -> view; //------------------------------------------------------------------------- view (const char*) -> view; view (char*) -> view; //------------------------------------------------------------------------- template < typename IteratorT, typename SizeT, typename = std::enable_if_t< std::is_integral_v > > view (IteratorT, SizeT) -> view; template view ( std::basic_string & ) -> view< typename std::allocator_traits::pointer >; template view ( const std::basic_string & ) -> view< typename std::allocator_traits::const_pointer >; template view ( std::vector& ) -> view::pointer>; template view ( const std::vector& ) -> view< typename std::allocator_traits::const_pointer >; template view (std::array&) -> view; template view (const std::array&) -> view; template view (ContainerT&) -> view< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) >; template view (const ContainerT&) -> view< decltype (std::declval ().begin ()), decltype (std::declval ().end ()) >; // base + count constructor template view (BeginT, std::uint64_t count) -> view; template view (BeginT, std::uint32_t count) -> view; template view (IteratorT, int) -> view; /////////////////////////////////////////////////////////////////////////// template auto make_view (const ValueT (&arr)[N]) { return view (arr + 0, arr + N); } //------------------------------------------------------------------------- template auto make_view (ContainerT &t) { return view { std::begin (t), std::end (t) }; } //------------------------------------------------------------------------- template auto make_view (const ContainerT &t) { return view { std::cbegin (t), std::cend (t) }; } //------------------------------------------------------------------------- // disable the possibility of creating a view to a temporary. note that // this only works if an lval version has already been defined otherwise // universal reference rules will capture both lval and rval here. template auto make_view (ContainerT&&) = delete; /////////////////////////////////////////////////////////////////////////// template auto make_cview (const ContainerT &t) { return make_view (t); //return view { std::cbegin (t), std::cend (t) }; } //------------------------------------------------------------------------- template auto make_view (BeginT first, EndT last) { return view {first, last}; } //------------------------------------------------------------------------- template auto make_cview (ValueT *first, ValueT *last) { return view {first, last}; } /////////////////////////////////////////////////////////////////////////// inline view make_view (const char *str) { return { str, str + strlen (str) }; } //------------------------------------------------------------------------- inline view make_view (char *str) { return { str, str + strlen (str) }; } //------------------------------------------------------------------------- template view make_view (const std::basic_string &str) { return { std::data (str), std::data (str) + std::size (str) }; } //------------------------------------------------------------------------- template view make_view (std::basic_string &str) { return { std::data (str), std::data (str) + std::size (str) }; } //------------------------------------------------------------------------- template view make_view (const std::basic_string&&) = delete; //------------------------------------------------------------------------- template view make_view (std::basic_string&&) = delete; /////////////////////////////////////////////////////////////////////////// /// Calculates a word oriented view over an arbitrary type /// /// Useful for passing in memory structures to file descriptors and the /// like. but the consequences of endian conversion is on the user... /// /// We have to be careful that rval-references and other temporaries aren't /// accepted in this signature. template < typename WordT = std::byte const, typename T > cruft::view make_byte_view (T &t) { static_assert (sizeof (T) % sizeof (WordT) == 0); static_assert (std::is_const_v ? std::is_const_v : true); return view { cast::alignment (&t), sizeof (T) / sizeof (WordT) }; } /////////////////////////////////////////////////////////////////////////// template cruft::view& operator-= ( cruft::view &val, typename std::iterator_traits::difference_type offset ) { return val = { val.begin () - offset, val.end () - offset }; } //------------------------------------------------------------------------- template cruft::view& operator+= ( cruft::view &val, typename std::iterator_traits::difference_type offset ) { return val = { val.begin () + offset, val.end () + offset }; } /////////////////////////////////////////////////////////////////////////// template view trim_if [[nodiscard]] ( view const buffer, FunctionT &&test ) { auto const first = std::find_if_not ( std::begin (buffer), std::end (buffer), test ); auto const last = std::find_if_not ( std::make_reverse_iterator (buffer.end ()), std::make_reverse_iterator (first), test ); return view (first, last.base ()); } /////////////////////////////////////////////////////////////////////////// template std::pair, view> split_on ( view const buffer, typename std::iterator_traits::value_type const value ) { auto const pos = std::find (std::begin (buffer), std::end (buffer), value); if (pos == std::end (buffer)) return { buffer, {std::end (buffer), std::end (buffer)} }; return { { buffer.begin (), pos, }, { pos + 1, buffer.end () } }; } /////////////////////////////////////////////////////////////////////////// /// Split a view at the first occurrence of the sub-string `value`. /// /// If no occurrence was found the returned pair will be the original view /// and an empty view. template std::pair, view> split_on ( view const buffer, typename std::iterator_traits::value_type (&value)[N] ) { auto const pos = std::search ( std::begin (buffer), std::end (buffer), std::begin (value), std::end (value) ); if (pos == std::end (buffer)) return { buffer, {std::end (buffer), std::end (buffer)} }; return { { buffer.begin (), pos, }, { pos + N, buffer.end () } }; } /////////////////////////////////////////////////////////////////////////// /// Returns a reference to a value of the designated type at the front of /// the word-view. if there is insufficient data for the extraction an /// exception will be thrown. /// /// There are no validity or other checks performed on the returned data /// this is deliberate, so that the function is safe to call on user /// supplied data during parsing routines. it is up to the user to ensure /// the object is valid. /// /// The buffer object is advanced in place so that it no longer covers /// the extract value /// /// It is assumed the user has taken care of alignment concerns template < typename ValueT, typename WordT > ValueT& extract (view &buffer) { // Only allow calls if the value is a multiple of the word size. // It's useful to allow non-unit words for areas like TCP/IP which // tend to include protocols that utilise u16 words. static_assert ( sizeof (ValueT) % sizeof (WordT) == 0, "The value type must be a multiple of the word size" ); static_assert ( !std::is_const_v or std::is_const_v, "buffer and output types must have matching constness" ); if (unlikely (sizeof (ValueT) > buffer.size () * sizeof (WordT))) throw std::runtime_error ("insufficient data for extraction"); auto ptr = cast::alignment (buffer.data ()); buffer = buffer.consume (sizeof (ValueT) / sizeof (WordT)); return *ptr; } ///------------------------------------------------------------------------ /// Returns a view of the specified type cast from the front of a /// byte-view. /// /// There may be runtime checks for alignment under debug builds. /// /// It is safe to use this on untrusted input. However absolutely no /// validation is performed on the result data. template < typename ValueT, typename WordT, typename = std::enable_if_t > cruft::view extract_array (view &src, std::size_t count) { static_assert ( std::is_const_v == std::is_const_v, "src and dst types must have matching constness" ); auto const src_size = sizeof (ValueT) / sizeof (WordT) * count; if (unlikely (src.size () < src_size)) throw std::runtime_error ("Insufficient data for extraction"); auto res = src.template cast ().redim (count); src = src.consume (src_size); return res; } /////////////////////////////////////////////////////////////////////////// /// extracts an object of a specified type from the front of a byte-view. /// /// in contrast to 'extract' this will always copy the bytes out from the /// view, making the operation alignment safe. /// /// It is safe to use this on untrusted input. template < typename ValueT, typename WordT, typename = std::enable_if_t > ValueT read (view &buffer) { // We're going to use memcpy which requires that the type is // trivially copyable. static_assert (std::is_trivially_copyable_v); if (unlikely (sizeof (ValueT) > buffer.size () * sizeof (WordT))) throw std::runtime_error ("insufficient data for extraction"); WordT* const head = buffer.begin (); buffer = buffer.consume (sizeof (ValueT) / sizeof (WordT)); // Don't use reinterpret_cast or other pointer schemes because it's quite easy to run into alignment issues. // No one should really be using this for bulk reads anyway, so it shouldn't be a performance issue. ValueT out; memcpy (&out, head, sizeof (ValueT)); return out; } /// Copy N elements of ValueT from a view over WordT into a supplied /// std::array. /// /// Throws if there is insufficient data in the src view. template < typename ValueT, typename WordT, std::size_t N, typename = std::enable_if_t > std::array& read_array (view &src, std::array &dst) { static_assert (std::is_trivially_copyable_v); if (unlikely (src.size () * sizeof (WordT) < sizeof (dst))) throw std::runtime_error ("insufficient data for extraction"); memcpy (dst.data (), src.data(), N * sizeof (ValueT)); src = src.consume (sizeof (ValueT) / sizeof (WordT) * N); return dst; } /////////////////////////////////////////////////////////////////////////// template < typename ValueT, typename WordT, typename = std::enable_if_t > view write [[nodiscard]] (view const &dst, ValueT const &src) { static_assert (std::is_trivially_copyable_v); if (unlikely (sizeof (ValueT) > dst.size () * sizeof (WordT))) throw std::runtime_error ("insufficient data for extraction"); memcpy (dst.data (), &src, sizeof (ValueT)); return { dst.begin () + sizeof (ValueT) / sizeof (WordT), dst.end (), }; } template < typename ValueT, typename WordT, typename = std::enable_if_t > view write [[nodiscard]] (view const &dst, cruft::view src) { static_assert (std::is_trivially_copyable_v); if (unlikely (src.size () * sizeof (ValueT) > dst.size () * sizeof (WordT))) throw std::runtime_error ("insufficient data for extraction"); memcpy ( dst.data (), src.data (), src.size () * sizeof (ValueT) ); return { dst.begin () + (sizeof (ValueT) / sizeof (WordT)) * src.size (), dst.end (), }; } /////////////////////////////////////////////////////////////////////////// /// Tests whether an iterator falls within a given view. template constexpr bool intersects (view a, IteratorT b) { return b >= a.begin () && b < a.end (); } ///------------------------------------------------------------------------ /// Tests whether view `a` inclusively contains view `b`. template constexpr bool covers ( view const &a, view const &b ) { return a.begin () <= b.begin () && a.end () >= b.end (); } /////////////////////////////////////////////////////////////////////////// template < typename IteratorA, typename IteratorB, typename ValueT > requires ( std::equality_comparable_with< typename std::iterator_traits::value_type, ValueT > ) bool contains (view container, ValueT &&value) { for (auto const &inner: container) if (inner == value) return true; return false; } /////////////////////////////////////////////////////////////////////////// template < typename BeginA, typename EndA, typename BeginB, typename EndB, typename ComparatorT > decltype(auto) equal ( view const &a, view const &b, ComparatorT &&cmp ) { return std::equal ( std::begin (a), std::end (a), std::begin (b), std::end (b), std::forward (cmp) ); } //------------------------------------------------------------------------- template < typename BeginA, typename EndA, typename BeginB, typename EndB > constexpr bool equal (const view &a, const view &b) { return ::cruft::equal (a, b, std::equal_to {}); } //------------------------------------------------------------------------- // defer equality to the view/view operator by way of make_view template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool equal (const view &a, const ValueT &b) { return equal (a, make_view (b)); } //------------------------------------------------------------------------- // reverse the arguments and forward to the above operator. we formumlate // equality this way to avoid implementing the operator twice for each // weird case. template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool equal (const ValueT &a, const view &b) { return equal (b, a); } /////////////////////////////////////////////////////////////////////////// template constexpr bool operator== (const view &a, const view &b) { return a.begin () == b.begin () && a.end () == b.end (); } //------------------------------------------------------------------------- template constexpr bool operator!= (const view &a, const view &b) { return !(a == b); } //------------------------------------------------------------------------- template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool operator!= (const view &a, const ValueT &b) { return !(a == b); } //------------------------------------------------------------------------- template < typename IteratorA, typename IteratorB, typename ValueT, typename = std::enable_if_t< !std::is_same_v>, void > > constexpr bool operator!= (const ValueT &a, const view &b) { return !(a == b); } /////////////////////////////////////////////////////////////////////////// template std::ostream& operator<< (std::ostream &os, view val) { std::copy ( std::cbegin (val), std::cend (val), std::ostream_iterator (os) ); return os; } /////////////////////////////////////////////////////////////////////////// /// a basic stringlike comparison operator that behaves as /// std::string::compare would. /// /// provided so that the common case of stringlike views can be used in a /// std::map and similar without a great deal of work. inline bool operator< (view a, view b) { const auto la = std::size (a); const auto lb = std::size (b); const auto res = strncmp ( std::data (a), std::data (b), min (la, lb) ); return res < 0 || (res == 0 && la < lb); } } #include "debug/validate.hpp" namespace cruft::debug { /////////////////////////////////////////////////////////////////////////// template struct validator< view, ArgsT&&... > { static bool is_valid (view data, ArgsT &&...args) { return std::all_of ( std::begin (data), std::end (data), [&] (auto const &i) { return ::cruft::debug::is_valid (i, args...); }); } }; };