/* PATH-ARRAY.hpp - sequence of path-like component-IDs in fixed storage Copyright (C) Lumiera.org 2017, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file path-array.hpp ** Foundation abstraction to implement path-like component sequences. ** This library class can be used to build a path abstraction for data structure access ** or some similar topological coordinate system, like e.g. [UI coordinates](\ref ui-coord.hpp) ** A PathArray is an iterable sequence of literal component IDs, implemented as tuple of `Literal*` ** held in fixed inline storage with possible heap allocated (and thus unlimited) extension storage. ** It offers range checks, standard iteration and array-like indexed component access; as a whole ** it is copyable, while actual components are immutable after construction. ** ** @remark the choice of implementation layout is arbitrary and not based on evidence. ** A recursive structure with fixed inline storage looked like an interesting programming challenge. ** Using just a heap based array storage would have been viable likewise. ** @todo when UICoord is in widespread use, collect performance statistics and revisit this design. ** @todo WIP 9/2017 first draft ////////////////////////////////////////////////////////////////////////////TICKET #1106 generic UI coordinate system ** ** @see PathArray_test ** @see UICoord_test ** @see gui::interact::UICoord ** @see view-spec-dsl.hpp */ #ifndef LIB_PATH_ARRAY_H #define LIB_PATH_ARRAY_H #include "lib/error.hpp" #include "lib/symbol.hpp" #include "lib/iter-adapter.hpp" #include "lib/meta/variadic-helper.hpp" #include "lib/format-obj.hpp" #include "lib/util.hpp" //#include #include #include #include #include #include namespace lib { namespace error = lumiera::error; // using std::unique_ptr; using std::forward; using std::string; using lib::Literal; using util::unConst; using util::isnil; namespace con { // Implementation helper: flexible heap based extension storage.... /** * Heap-allocated extension storage for an immutable sequence of literal strings. * The size of the allocation is determined and fixed once, at construction time, * derived from the number of initialisers. The first slot within the allocation * stores this length. Extension can be _empty_ (default constructed), * in which case no heap allocation is performed. */ class Extension { using PStorage = Literal*; PStorage storage_; static size_t& size (PStorage& p) { REQUIRE (p); return reinterpret_cast (p[0]); } /** allocate a copy. * @note caller has to manage the allocated memory * @warning call to Literal's ctor deliberately elided */ PStorage newCopy() const { size_t siz = size (unConst(this)->storage_); const char** alloc = new const char*[siz]; std::copy (storage_, storage_+siz, alloc); return reinterpret_cast (alloc); } public: ~Extension() { if (storage_) delete[] storage_; } Extension() : storage_{nullptr} { } template explicit Extension (ELMS&& ...elms) : storage_{new Literal[1 + sizeof...(ELMS)]} { size(storage_) = sizeof...(ELMS); new(storage_+1) Literal[sizeof...(ELMS)] {forward(elms)...}; } Extension (Extension const& r) : storage_{r.storage_? r.newCopy() : nullptr} { } Extension (Extension&& rr) : storage_{nullptr} { if (rr.storage_) std::swap (storage_, rr.storage_); } Extension& operator= (Extension const& o) { if (this != &o) { std::unique_ptr cp; if (o.storage_) cp.reset (o.newCopy()); if (storage_) delete[] storage_; storage_ = cp.release(); } return *this; } Extension& operator= (Extension&& rr) { if (this != &rr) { std::swap (storage_, rr.storage_); } return *this; } operator bool() const { return not empty(); } bool empty() const { return not storage_;} size_t size() const { return storage_? size(unConst(this)->storage_) : 0; } Literal const& operator[] (size_t idx) const { REQUIRE (storage_ and idx < size()); return storage_[1+idx]; } bool isValid (Literal const* pos) const { return storage_ and storage_ < pos and pos < storage_ + (1 + size (unConst(this)->storage_)); } void trimTo (size_t cnt) { REQUIRE (cnt <= size()); if (cnt == 0 and storage_) { delete[] storage_; storage_ = nullptr; } else size(storage_) = cnt; // excess elements now unreachable // note: delete[] knows original size } }; }//(End)Implementation helper using meta::pickArg; using meta::pickInit; using meta::IndexSeq; /** * Abstraction for path-like topological coordinates. * A sequence of Literal strings, with array-like access and * standard iteration. Implemented as fixed size inline tuple * with heap allocated unlimited extension space. */ template class PathArray { static_assert (0 < chunk_size, "PathArray chunk_size must be nonempty"); using CcP = const char*; using LiteralArray = std::array; LiteralArray elms_; con::Extension tail_; /** * @internal delegate ctor to place the initialiser arguments appropriately * @remarks the two index sequences passed by pattern match determine which * arguments go to the inline array, and which go to heap allocated extension. * The inline array has fixed size an is thus filled with trailing `NULL` ptrs, * which is achieved with the help of meta::pickInit(). The con::Extension * is an embedded smart-ptr, which, when receiving additional tail arguments, * will place and manage them within a heap allocated array. */ template PathArray (IndexSeq ,IndexSeq ,ARGS&& ...args) : elms_{pickInit (forward(args)...) ...} , tail_{pickArg (forward(args)...) ...} { this->normalise(); } /** * @internal rebinding helper for building sequences of index numbers, * to route the initialiser arguments into the corresponding storage * - the leading (`chunk_size`) arguments go into the LiteralArray inline * - all the remaining arguments go into heap allocated extension storage */ template struct Split { using Prefix = typename meta::BuildIndexSeq::Ascending; using Rest = typename meta::BuildIdxIter::template After; }; public: template explicit PathArray (ARGS&& ...args) : PathArray(typename Split::Prefix() ,typename Split::Rest() ,forward (args)...) { } PathArray(PathArray&&) = default; PathArray(PathArray const&) = default; PathArray(PathArray& o) : PathArray((PathArray const&)o) { } PathArray& operator= (PathArray const&) = default; PathArray& operator= (PathArray &&) = default; ////////////////////////TICKET #963 Forwarding shadows copy operations size_t size() const { return tail_? chunk_size + tail_.size() : findInlineEnd() - elms_.begin(); } bool empty() const { return not elms_[0]; // normalise() ensures nonnull unless completely empty } operator string() const; Literal const& operator[] (size_t idx) const { Literal* elm = unConst(this)->getPosition (idx); if (not elm) throw error::Invalid ("Accessing index "+util::toString(idx) +" on PathArray of size "+ util::toString(size()) ,error::LUMIERA_ERROR_INDEX_BOUNDS); return *elm; } protected: /* ==== Iteration control API for IterAdapter ==== */ /** Implementation of Iteration-logic: pull next element. */ friend void iterNext (const PathArray*, const Literal*& pos) { ++pos; } /** Implementation of Iteration-logic: detect iteration end. */ friend bool checkPoint (const PathArray* src, const Literal*& pos) { REQUIRE (src); if (pos >= src->elms_.end() and src->tail_) pos = &src->tail_[0]; else if (not src->isValid (pos)) { pos = nullptr; return false; } ENSURE ( (src->elms_.begin() <= pos and pos < src->elms_.end()) or src->tail_.isValid(pos)); return true; } public: using const_iterator = lib::IterAdapter; using iterator = const_iterator; iterator begin() const { return iterator{this, elms_.begin()}; } iterator end() const { return iterator{}; } friend iterator begin(PathArray const& pa) { return pa.begin();} friend iterator end (PathArray const& pa) { return pa.end(); } private: /* ===== implementation details ===== */ bool isValid (Literal const* pos) const { return pos and (tail_.isValid(pos) or (pos < elms_.end() and *pos)); } /** find _effective end_ of data in the inline array, * i.e. the position _behind_ the last usable content */ Literal const* findInlineEnd() const { Literal const* lastPos = elms_.begin() + chunk_size-1; Literal const* beforeStart = elms_.begin() - 1; while (lastPos != beforeStart and not *lastPos) --lastPos; return ++lastPos; // at start if empty, else one behind the last } /** * @internal access content element by index * @return pointer to storage, `null` if out of bounds */ Literal* getPosition (size_t idx) { Literal const* elm =nullptr; if (idx < chunk_size) elm = &elms_[idx]; else if (idx-chunk_size < tail_.size()) elm = &tail_[idx-chunk_size]; return const_cast (elm); } /** @internal force new content into the given entry */ void setContent (Literal* pos, const char* val) { REQUIRE (pos); *reinterpret_cast (pos) = val; } /** * establish the _contract_ of PathArray * - no null pointer within the valid storage range * - storage is precisely trimmed at the end * - missing leading elements are filled with Symbol::EMPTY * - missing inner elements are interpolated as "*" */ void normalise() { static Symbol ANY("*"); const char* fill = Symbol::EMPTY; Literal* end = elms_.end(); Literal* seg = elms_.begin(); for (Literal* pos = elms_.begin(); pos!=end; ++pos) if (isnil (*pos)) setContent (pos, fill); else if (fill==Symbol::EMPTY) fill = ANY; if (not tail_) return; seg = const_cast (&tail_[0]); end = seg + tail_.size(); for (Literal* pos = seg; pos!=end; ++pos) if (isnil (*pos)) setContent (pos, fill); else if (fill==Symbol::EMPTY) fill = ANY; size_t idx = size(); while (idx and ANY == (*this)[--idx]) setContent (getPosition(idx), nullptr); if (idx >= chunk_size-1) tail_.trimTo (idx - chunk_size); } }; template inline PathArray::operator string() const { if (this->empty()) return ""; string buff; size_t expectedLen = this->size() * 10; buff.reserve (expectedLen); // heuristic for (Literal elm : *this) buff += elm + "/"; // chop off last delimiter size_t len = buff.length(); ASSERT (len >= 1); buff.resize(len-1); return buff; } }// namespace lib #endif /*LIB_PATH_ARRAY_H*/