/* 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; 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 = const char**; PStorage storage_; static size_t& size (PStorage& p) { REQUIRE (p); return reinterpret_cast (p[0]); } public: Extension() : storage_{nullptr} { } template explicit Extension (ELMS ...elms) : storage_{new const char* [1 + sizeof...(ELMS)]} { size(storage_) = sizeof...(ELMS); std::initializer_list lit{elms...}; std::copy (begin(lit),end(lit), storage_+1); } ~Extension() { if (storage_) delete[] storage_; } Extension (Extension const& r) : storage_{r.storage_? new const char* [1 + r.size()] : nullptr} { if (r.storage_) std::copy (r.storage_, r.storage_+(1+r.size()), this->storage_); } 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 (new const char* [1 + o.size()]); std::copy (o.storage_, o.storage_+(1+o.size()), cp.get()); } 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; } const char* operator[] (size_t idx) const { REQUIRE (storage_ and idx < size()); return storage_[1+idx]; } }; }//(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 Lit = 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 this holds only for empty paths } operator string() const; Literal operator[] (size_t idx) { Lit elm{0}; if (idx < chunk_size) elm = elms_[idx]; else if (idx-chunk_size < tail_.size()) elm = tail_[idx-chunk_size]; 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* src, Literal* pos) { ++pos; checkPoint (src,pos); } /** Implementation of Iteration-logic: detect iteration end. */ friend bool checkPoint (const PathArray* src, Literal* pos) { REQUIRE (src); if ((pos != nullptr) && (pos != src->storage_end())) return true; else { pos = nullptr; return false; } } public: using iterator = lib::IterAdapter; using const_iterator = iterator; iterator begin() const { UNIMPLEMENTED ("content iteration"); } iterator end() const { UNIMPLEMENTED ("content iteration"); } friend iterator begin(PathArray const& pa) { return pa.begin();} friend iterator end (PathArray const& pa) { return pa.end(); } private: /** find _effective end_ of data in the inline array, * i.e. the position _behind_ the last usable content */ Lit const* findInlineEnd() const { Lit const* lastPos = elms_.begin() + chunk_size-1; Lit const* beforeStart = elms_.begin() - 1; while (lastPos != beforeStart and not *lastPos) --lastPos; return ++lastPos; // at start if empty, else one behind the last } Literal* storage_end() const { UNIMPLEMENTED ("path implementation storage"); } void normalise() { UNIMPLEMENTED ("establish invariant"); } }; template inline PathArray::operator string() const { if (this->empty()) return ""; string buff; size_t expectedLen = this->size() * 10; buff.reserve (expectedLen); 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*/