diff --git a/src/lib/diff/gen-node.cpp b/src/lib/diff/gen-node.cpp index 69cc70ca7..ed0b18c7a 100644 --- a/src/lib/diff/gen-node.cpp +++ b/src/lib/diff/gen-node.cpp @@ -44,7 +44,7 @@ using util::join; using boost::lexical_cast; using lib::time::TimeValue; -using lib::transformIterator; +using lib::transformIter; using util::almostEqual; using lib::hash::LuidH; @@ -297,9 +297,9 @@ namespace diff{ return (Rec::TYPE_NIL==rec.getType()? "" : rec.getType()) + "{" - + join (transformIterator (rec.attribs(), renderAttrib)) + + join (transformIter (rec.attribs(), renderAttrib)) + (isnil(rec.scope())? "" : "|") - + join (transformIterator (rec.scope() , renderChild)) + + join (transformIter (rec.scope() , renderChild)) + "}" ; } diff --git a/src/lib/diff/gen-node.hpp b/src/lib/diff/gen-node.hpp index bf89f3b67..9f21f9d15 100644 --- a/src/lib/diff/gen-node.hpp +++ b/src/lib/diff/gen-node.hpp @@ -375,8 +375,6 @@ namespace diff{ iterator end() const; - using ChildDataIter = TransformIter; - /** visit the _data_ of nested child elements * @return an iterator over the DataCap elements of all children, * in case this GenNode actually holds a Record. @@ -384,26 +382,26 @@ namespace diff{ * @note this iterator visits _only_ the children, which are * by definition unnamed. It does _not_ visit attributes. */ - friend ChildDataIter + friend auto childData (GenNode const& n) { - return ChildDataIter{ n.data.childIter() - , [](GenNode const& child) ->DataCap const& + return transformIter(n.data.childIter() + ,[](GenNode const& child) ->DataCap const& { return child.data; } - }; + ); } - friend ChildDataIter + friend auto childData (Rec::scopeIter&& scopeIter) { - return ChildDataIter{ std::forward(scopeIter) - , [](GenNode const& child) ->DataCap const& + return transformIter(std::forward(scopeIter) + ,[](GenNode const& child) ->DataCap const& { return child.data; } - }; + ); } diff --git a/src/lib/diff/record.hpp b/src/lib/diff/record.hpp index 89c9f7637..e5da73aba 100644 --- a/src/lib/diff/record.hpp +++ b/src/lib/diff/record.hpp @@ -308,18 +308,16 @@ namespace diff{ using iterator = IterAdapter; using scopeIter = iter_stl::_SeqT::Range; - using keyIter = TransformIter; - using valIter = TransformIter; /** default iteration exposes all data within this "object", starting with the attributes */ iterator begin () const { return iterator(this, attribs_.empty()? children_.begin() : attribs_.begin()); } iterator end () const { return iterator(); } - scopeIter attribs() const { return iter_stl::eachElm(attribs_); } - scopeIter scope() const { return iter_stl::eachElm(children_); } + scopeIter attribs() const { return iter_stl::eachElm (attribs_); } + scopeIter scope() const { return iter_stl::eachElm (children_); } - keyIter keys() const { return transformIterator(attribs(), extractKey); } - valIter vals() const { return transformIterator(attribs(), extractVal); } + auto keys() const { return transformIter (attribs(), extractKey); } + auto vals() const { return transformIter (attribs(), extractVal); } protected: /* ==== API for the IterAdapter ==== */ @@ -792,11 +790,11 @@ namespace diff{ Record::operator std::string() const { using util::join; - using lib::transformIterator; + using lib::transformIter; return "Rec(" + (TYPE_NIL==type_? "" : type_) - + (isnil(this->attribs())? "" : "| "+join (transformIterator (this->attribs(), renderAttribute))+" ") + + (isnil(this->attribs())? "" : "| "+join (transformIter (this->attribs(), renderAttribute))+" ") + (isnil(this->scope())? "" : "|{"+join (this->scope())+"}") + ")" ; diff --git a/src/lib/diff/test-mutation-target.hpp b/src/lib/diff/test-mutation-target.hpp index 13ee8583e..7a308ed73 100644 --- a/src/lib/diff/test-mutation-target.hpp +++ b/src/lib/diff/test-mutation-target.hpp @@ -110,12 +110,12 @@ namespace diff{ renderRecord (Rec const& record) { using util::join; - using lib::transformIterator; + using lib::transformIter; return "Rec(" + (Rec::TYPE_NIL==record.getType()? "" : record.getType()) - + (isnil(record.attribs())? "" : "| "+join (transformIterator (record.attribs(), renderAttribute))+" ") - + (isnil(record.scope())? "" : "|{"+join (transformIterator (record.scope(), renderChild))+"}") + + (isnil(record.attribs())? "" : "| "+join (transformIter (record.attribs(), renderAttribute))+" ") + + (isnil(record.scope())? "" : "|{"+join (transformIter (record.scope(), renderChild))+"}") + ")" ; } @@ -289,14 +289,14 @@ namespace diff{ string showContent () const { - return join (transformIterator (begin(), renderNode)); + return join (transformIter (begin(), renderNode)); } /** render elements waiting in source buffer to be accepted */ string showSrcBuffer () const { - return join (transformIterator (eachElm(prev_content_), renderNode)); + return join (transformIter (eachElm(prev_content_), renderNode)); } EventMatch diff --git a/src/lib/diff/tree-mutator-collection-binding.hpp b/src/lib/diff/tree-mutator-collection-binding.hpp index ef237a583..fc36284d4 100644 --- a/src/lib/diff/tree-mutator-collection-binding.hpp +++ b/src/lib/diff/tree-mutator-collection-binding.hpp @@ -76,6 +76,7 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" #include "lib/meta/trait.hpp" +#include "lib/meta/function.hpp" #include "lib/diff/gen-node.hpp" #include "lib/diff/tree-mutator.hpp" #include "lib/iter-adapter-stl.hpp" diff --git a/src/lib/format-util.hpp b/src/lib/format-util.hpp index 01e7c8616..9a1e65ae2 100644 --- a/src/lib/format-util.hpp +++ b/src/lib/format-util.hpp @@ -135,7 +135,7 @@ namespace util { { using Val = lib::meta::ValueTypeBinding::value_type; - return lib::transformIterator(forward(src), util::toString); + return lib::transformIter (forward(src), util::toString); } diff --git a/src/lib/item-wrapper.hpp b/src/lib/item-wrapper.hpp index bd45621ed..aca3fbc78 100644 --- a/src/lib/item-wrapper.hpp +++ b/src/lib/item-wrapper.hpp @@ -31,7 +31,7 @@ ** this to the pattern of _type erasure_, where we discard specific ** knowledge after construction; for _such_ cases, opaque-holder.hpp ** might be a better fit. - ** @see lib::TransformIter + ** @see lib::iter::Transform ** @see lib::explore ** */ diff --git a/src/lib/iter-source.hpp b/src/lib/iter-source.hpp index 8a157a336..352a88f66 100644 --- a/src/lib/iter-source.hpp +++ b/src/lib/iter-source.hpp @@ -44,6 +44,7 @@ #include "lib/meta/util.hpp" +#include "lib/meta/function.hpp" #include "lib/iter-adapter.hpp" #include "lib/itertools.hpp" #include "lib/nocopy.hpp" @@ -310,7 +311,7 @@ namespace lib { { using Src = std::remove_reference::type; using ResVal = lib::meta::_Fun::Ret; - using TransIter = TransformIter; + using TransIter = decltype(lib::transformIter (std::declval(), std::declval())); using Iter = IterSource::iterator; }; @@ -326,26 +327,23 @@ namespace lib { // we can strip the const added by the STL map types using KeyType = std::remove_const::type; - using KeyIter = TransformIter; - using ValIter = TransformIter; - static KeyType takeFirst (PairType const& pair) { return pair.first; } static ValType takeSecond(PairType const& pair) { return pair.second;} }; template - _PairIterT::KeyIter + auto takePairFirst (IT&& source) { - return transformIterator(forward(source), _PairIterT::takeFirst ); + return transformIter (forward(source), _PairIterT::takeFirst ); } template - _PairIterT::ValIter + auto takePairSecond (IT&& source) { - return transformIterator(forward(source), _PairIterT::takeSecond ); + return transformIter (forward(source), _PairIterT::takeSecond ); } } //(END) type helpers... @@ -375,10 +373,10 @@ namespace lib { auto singleVal (VAL&& something) { - using Src = decltype(singleValIterator (forward(something))); + using Src = decltype(singletonIter (forward(something))); using Val = _IterT::Val; - return IterSource::build (new WrappedLumieraIter{singleValIterator (forward(something))}); + return IterSource::build (new WrappedLumieraIter{singletonIter (forward(something))}); } @@ -400,7 +398,7 @@ namespace lib { return IterSource::build ( new WrappedLumieraIter ( - transformIterator (forward(source), processingFunc))); + transformIter (forward(source), processingFunc))); } @@ -408,7 +406,7 @@ namespace lib { * all the keys of the given Map or Hashtable */ template - _MapT::KeyIter + auto eachMapKey (MAP& map) { using Range = RangeIter; @@ -438,13 +436,13 @@ namespace lib { * the distinct keys */ template - _MapT::KeyIter + auto eachDistinctKey (MAP& map) { using Range = RangeIter; Range contents (map.begin(), map.end()); - return wrapIter (filterRepetitions (takePairFirst(contents))); + return wrapIter (filterRepeat (takePairFirst(contents))); } diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index 6c952bb94..2fd3ca90a 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -2,7 +2,7 @@ ITERTOOLS.hpp - collection of tools for building and combining iterators Copyright (C) - 2009, Hermann Vosseler + 2009,2026 Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the @@ -12,55 +12,50 @@ */ /** @file itertools.hpp - ** Helpers for working with iterators based on the pipeline model. - ** Iterators abstract from the underlying data container and provide - ** the contained data as a source to pull values from. Based on this - ** model, we can build pipelines, with filters, valves, junction points - ** and transforming facilities. The templates in this header enable such, - ** based on the Lumiera Forward Iterator concept. They build on generic - ** programming techniques, thus are intended to be combined at compile time - ** using definitive type information. Contrast this to an iterator model - ** as in Java's Collections, where Iterator is an Interface based on - ** virtual functions. Thus, the basic problem to overcome is the lack - ** of a single common interface, which could serve as foundation for - ** type inference. As a solution, we use a "onion" approach, where a - ** generic base gets configured with an active core, implementing - ** the filtering or processing functionality, while the base class - ** (IterTool) exposes the operations necessary to comply to the - ** Forward Iterator Concept. + ** Collection of tools for adapting and combining Lumiera iterators. + ** These abstract from the underlying data container and provide the + ** exposed data as a source to pull values from. Based on this model + ** of on-demand processing, pipelines can be built with data filtering + ** and transforming defined by functions. The templates and builders in + ** his header support this approach, based on the **Lumiera Forward Iterator** + ** concept. The implementation relies techniques of _generic programming_, + ** so that any kind of _invocable_ can be used as processing function and + ** actual type information is used at compile time to generate an optimised + ** calculation scheme. The tools provided in this header are fairly basic + ** however, and will typically be used to apply simple adaptations to be + ** used inline. + ** @remark A similar technique for building much more elaborate processing, + ** search and evaluation schemes is provided by iter-explorer.hpp ** - ** \par filtering Iterator - ** The FilterIter template can be used to build a filter into a pipeline, + ** # filtering Iterator + ** The iter::Filter template can be used to build a filter into a pipeline, ** as it forwards only those elements from its source iterator, which pass - ** the predicate evaluation. Anything acceptable as ctor parameter for a - ** std::function object can be passed in as predicate, but of course the - ** signature must be sensible. Please note, that -- depending on the - ** predicate -- already the ctor or even a simple `bool` test might - ** pull and exhaust the source iterator completely, in an attempt - ** to find the first element passing the predicate test. + ** the predicate evaluation. Anything invocable that yields a bool convertible + ** result can be passed in as predicate. Please note, that — depending on the + ** predicate — already the ctor or even a simple `bool` test might pull and + ** exhaust the source iterator completely, in an attempt to find the + ** first element to pass the predicate test. ** - ** \par extensible Filter - ** Based on the FilterIter, this facility allows to elaborate the filter - ** function while in the middle of iteration. The new augmented filter - ** will be in effect starting with the current element, which might even - ** be filtered away now due to a more restrictive condition. However, - ** since this is still an iterator, any "past" elements are already - ** extracted and gone and can thus not be subject to changed filtering. - ** The ExtensibleFilterIter template provides several _builder functions_ - ** to elaborate the initial filter condition, like adding conjunctive or - ** disjunctive clauses, flip the filter's meaning or even replace it - ** altogether by a completely different filter function. - ** - ** \par processing Iterator - ** the TransformIter template can be used as processing (or transforming) + ** # processing Iterator + ** The iter::Trans template can be used as processing (or transforming) ** step within the pipeline. It is created with a functor, which, when ** pulling elements, is invoked for each element pulled from the - ** source iterator. The signature of the functor must match the - ** desired value (output) type. + ** source iterator. The functor need to accept the result type from + ** the underlying source iterator and its result value determines + ** the result (output) type of the resulting pipeline. + ** + ** # convenience utilities + ** In addition, this header provides an assortment of convenience helpers + ** and utilities, that were created over time to simplify the formulation + ** of common tasks, while the fundamental building blocks for definition and + ** combination of Lumiera iterators can be found in \ref iter-adapter.hpp. + ** @warning since this header is widely used in the Lumiera codebase, + ** excessive complexity and rarely used includes should be avoided. ** - ** @see iter-adapter.hpp ** @see itertools-test.cpp - ** @see event-log.hpp + ** @see iter-explorer.hpp + ** @see iter-adapter.hpp + ** @see gen-node.hpp */ @@ -70,741 +65,333 @@ #include "lib/iter-adapter.hpp" #include "lib/item-wrapper.hpp" -#include "lib/meta/value-type-binding.hpp" -#include "lib/meta/function.hpp" #include "lib/meta/trait.hpp" #include "lib/util.hpp" #include +#include #include - namespace lib { + using std::move; using std::forward; - using std::function; using util::unConst; - using lib::meta::ValueTypeBinding; - - - - /** - * A neutral \em identity-function core, - * also serving as point-of reference how any - * core is intended to work. Any core is intended - * to serve as inner part of an iterator tool template. - * - it provides the trait typedefs - * - it abstracts the "source" - * - it abstracts the local operation to be performed - * - the ctor of the core sets up the configuration. - * @note cores should be copyable without much overhead - */ - template - struct IdentityCore - { - IT source_; - - IdentityCore (IT&& orig) - : source_{forward(orig)} - { } - IdentityCore (IT const& orig) - : source_{orig} - { } - - IT& - pipe () + namespace iter { // Implementation building blocks + using lib::meta::ValueTypeBinding; + + /** + * Predicate to be applied to an iterator or container element. + * Requires a non-strict function (side-effects acceptable) + * that operates on the value yielded by the iterator. + */ + template::reference> + concept iter_predicate = requires (FUN fun, REF ref) + { + requires std::invocable; + { fun(ref) } -> std::convertible_to; + }; + + template::reference> + concept iter_transformer = std::invocable; + + + + /** + * Pseudo-Iterator to yield just a single value. + * When incremented, the value is destroyed and + * the Iterator transitions to _exhausted state_. + * @remark can be useful to integrate special cases + * into an established interface based on iterators. + * @tparam VAL anything, value or reference to store + * @note relies on ItemWrapper, which includes a flag. + */ + template + class Single + { + using Item = wrapper::ItemWrapper; + + Item theValue_; + + void + __throw_if_empty() const + { + if (not isValid()) + _throwIterExhausted(); + } + + public: + using value_type = meta::RefTraits::value_type; + using reference = meta::RefTraits::reference; + using pointer = meta::RefTraits::pointer; + + + template> + Single (X&& val) + : theValue_{forward(val)} + { } + + Single() =default; + // standard copy operations + + + /* === Lumiera Forward Iterator concept === */ + + operator bool() const + { + return isValid(); + } + + reference + operator*() const + { + __throw_if_empty(); + return *theValue_; + } + + pointer + operator->() const + { + return & operator*(); + } + + Single& + operator++() + { + __throw_if_empty(); + theValue_.reset(); + return *this; + } + + bool + isValid() const + { + return theValue_.isValid(); + } + + bool + empty() const + { + return not isValid(); + } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (Single); + + friend bool + operator== (Single const& s1, Single const& s2) { - return source_; + return s1.theValue_ == s2.theValue_; } - - IT const& - pipe () const + }; + + + + /** + * Iterator adapter that applies filtering to a source iterator. + * Any invocable that can be used on the iterator's result to + * yield something bool-convertable can be employed here, and + * will be embedded as-is (which might be by reference). + * The source iterator will be pulled immediately on construction, + * until the first result is found that satisfies the filter predicate. + * @note the filter is thus invoked _at most once_ per value yielded. + * @warning this eager pull can be insidious when the predicate happens + * to store a reference to the filtered value, and the adapter object + * is then moved to another location after construction. + */ + template + class Filter + { + SRC srcIter_; + PRED predicate_; + + void + __throw_if_empty() const + { + if (not isValid()) + _throwIterExhausted(); + } + + /** @note establishes the **invariant**: + * whatever the source yields as current element, + * has already been approved by our predicate */ + void + pullFilter() + { + while (srcIter_ and not predicate_(*srcIter_)) + ++srcIter_; + } + + public: + using value_type = ValueTypeBinding::value_type; + using reference = ValueTypeBinding::reference; + using pointer = ValueTypeBinding::pointer; + + + template FUN> + Filter (IT&& src, FUN&& filter) + : srcIter_{forward (src)} + , predicate_{forward (filter)} + { + pullFilter(); // initially pull to establish the invariant + } + + Filter() =default; + // standard copy operations + + + /* === Lumiera Forward Iterator concept === */ + + operator bool() const + { + return isValid(); + } + + reference + operator*() const + { + __throw_if_empty(); + return *srcIter_; + } + + pointer + operator->() const + { + return & operator*(); + } + + Filter& + operator++() + { + __throw_if_empty(); + ++srcIter_; + pullFilter(); + return *this; + } + + bool + isValid() const + { + return bool(srcIter_); + } + + bool + empty() const + { + return not isValid(); + } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (Filter); + + friend bool + operator== (Filter const& s1, Filter const& s2) { - return source_; + return s1.srcIter_ == s2.srcIter_; } - - void - advance () + }; + + + /** + * Iterator adapter to map a function onto each result + * of the given source iterator. The result of this function + * invocation is captured into an ItemWrapper storage buffer. + * Consecutive value access will thus use this cached result. + */ + template + class Trans + { + using Res = std::invoke_result_t>; + using Item = wrapper::ItemWrapper; + + SRC srcIter_; + PROC transformer_; + Item transformed_; + + void + __throw_if_empty() const + { + if (not isValid()) + _throwIterExhausted(); + } + + decltype(auto) + process() + { + if (not transformed_) // invoke transform function once per src item + transformed_ = transformer_(*srcIter_); + return *transformed_; + } + + public: + using value_type = ValueTypeBinding::value_type; + using reference = ValueTypeBinding::reference; + using pointer = ValueTypeBinding::pointer; + + + template FUN> + Trans (IT&& src, FUN&& processor) + : srcIter_{forward (src)} + , transformer_{forward (processor)} + , transformed_{/* initially empty */} + { } + + Trans() = default; + // standard copy operations + + + /* === Lumiera Forward Iterator concept === */ + + operator bool() const + { + return isValid(); + } + + reference + operator*() const + { + __throw_if_empty(); + return unConst(this)->process(); + } + + pointer + operator->() const + { + return & operator*(); + } + + Trans& + operator++() + { + __throw_if_empty(); + ++srcIter_; + transformed_.reset(); + return *this; + } + + bool + isValid() const + { + return bool(srcIter_); + } + + bool + empty() const + { + return not isValid(); + } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (Trans); + + friend bool + operator== (Trans const& s1, Trans const& s2) { - ++source_; - } - - bool - evaluate () const - { - return bool(source_); - } - - using pointer = IT::pointer; - using reference = IT::reference; - using value_type = IT::value_type; - }; - - - /** - * Standard functionality to build up any iterator tool. - * IterTool exposes the frontend functions necessary to - * comply to the Lumiera Forward Iterator concept. - * The protected part provides the _iteration control_ - * building blocks to drive the processing/filter logic, - * which is implemented in the specific core for each tool. - */ - template - class IterTool - { - - protected: /* == iteration control == */ - CORE core_; - - bool - hasData() const - { - return core_.evaluate() - or unConst(this)->iterate(); - } // to skip irrelevant results doesn't count as "mutation" - - bool - iterate () - { - if (not core_.pipe()) return false; - - do core_.advance(); - while (core_.pipe() and not core_.evaluate()); - - return bool{core_.pipe()}; - } - - void - _maybe_throw() const - { - if (!isValid()) - _throwIterExhausted(); - } - - - - public: - using pointer = CORE::pointer; - using reference = CORE::reference; - using value_type = CORE::value_type; - - - IterTool (CORE&& setup) - : core_{std::move(setup)} - { - hasData(); - } - - explicit - operator bool() const - { - return isValid(); - } - - - - /* === lumiera forward iterator concept === */ - - reference - operator*() const - { - _maybe_throw(); - return *core_.pipe(); - } - - pointer - operator->() const - { - _maybe_throw(); - return & *core_.pipe(); - } - - IterTool& - operator++() - { - _maybe_throw(); - iterate(); - return *this; - } - - bool - isValid () const - { - return hasData(); - } - - bool - empty () const - { - return not isValid(); - } - - }; - - - template - inline bool - operator== (IterTool const& it1, IterTool const& it2) - { - return (!it1 and !it2 ) - or ( it1 and it2 and (*it1) == (*it2) ) - ; - } - - template - inline bool - operator!= (IterTool const& ito1, IterTool const& ito2) - { - return not (ito1 == ito2); - } - - - - - - - - - /** - * Implementation of the filter logic. - * This core stores a function object instance, - * passing each pulled source element to this - * predicate function for evaluation. - * @note predicate is evaluated at most once - * for each value yielded by the source - */ - template - struct FilterCore - : IdentityCore - { - using Raw = IdentityCore; - using Val = iter::Yield; - - - function predicate_; - - bool - evaluate () const - { - return Raw::pipe() - and currVal_isOK(); - } - - - mutable bool cached_; - mutable bool isOK_; - - bool - currVal_isOK () const ///< @return (maybe cached) result of filter predicate - { - return (cached_ and isOK_) - or (cached_ = true - and (isOK_ = predicate_(*Raw::pipe()))); - } - - void - advance () - { - cached_ = false; - Raw::advance(); - } - - - template - FilterCore (IT&& source, PRED prediDef) - : Raw{forward(source)} - , predicate_(prediDef) // induces a signature check - , cached_(false) // not yet cached - , isOK_(false) // not yet relevant - { } - - template - FilterCore (IT const& source, PRED prediDef) - : Raw{source} - , predicate_(prediDef) - , cached_(false) - , isOK_(false) - { } - }; - - - /** - * Iterator tool filtering pulled data according to a predicate - */ - template - class FilterIter - : public IterTool> - { - typedef FilterCore _Filter; - typedef IterTool<_Filter> _Impl; - - public: - static bool acceptAll(_Filter::Val) { return true; } - - - FilterIter () - : _Impl{FilterCore(IT(), acceptAll)} - { } - - template - FilterIter (IT const& src, PRED filterPredicate) - : _Impl{_Filter(src, filterPredicate)} - { } - - template - FilterIter (IT&& src, PRED filterPredicate) - : _Impl{_Filter(forward(src), filterPredicate)} - { } - - ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (FilterIter) - }; - - - /** Build a FilterIter: convenience free function shortcut, - * picking up the involved types automatically. - * @param filterPredicate to be invoked for each source element - * @return Iterator filtering contents of the source - */ - template - inline auto - filterIterator (IT const& src, PRED filterPredicate) - { - return FilterIter{src, filterPredicate}; - } - - template - inline auto - filterIterator (IT&& src, PRED filterPredicate) - { - using SrcIT = std::remove_reference::type; - return FilterIter{forward(src), filterPredicate}; - } - - - /** - * Additional capabilities for FilterIter, - * allowing to extend the filter condition underway. - * This wrapper enables remoulding of the filer functor - * while in the middle of iteration. When the filter is - * modified, current head of iteration gets re-evaluated - * and possible fast-forwarded to the next element - * satisfying the now extended filter condition. - * @note changing the condition modifies a given iterator in place. - * Superficially this might look as if the storage remains - * the same, but in fact we're adding a lambda closure, - * which the runtime usually allocates on the heap, - * holding the previous functor and a second functor - * for the added clause. - * @warning the addition of disjunctive and negated clauses might - * actually weaken the filter condition. Yet still there is - * _no reset of the source iterator,_ i.e. we don't - * re-evaluate from start, but just from current head. - * Which means we might miss elements in the already consumed - * part of the source sequence, which theoretically would - * pass the now altered filter condition. - * @see IterTools_test::verify_filterExtension - */ - template - class ExtensibleFilterIter - : public FilterIter - { - using _Filter = FilterCore; - using Val = _Filter::Val; - - void - reEvaluate() - { - this->core_.cached_ = false; - this->hasData(); // re-evaluate head element - } - - public: - ExtensibleFilterIter() { } - - template - ExtensibleFilterIter (IT&& src, PRED initialFilterPredicate) - : FilterIter{forward(src), initialFilterPredicate} - { } - template - ExtensibleFilterIter (IT const& src, PRED initialFilterPredicate) - : FilterIter{src, initialFilterPredicate} - { } - - ExtensibleFilterIter (IT&& src) - : ExtensibleFilterIter{forward(src), FilterIter::acceptAll} - { } - - // standard copy operations acceptable - - - /** access the unfiltered source iterator - * in current state */ - IT& - underlying() - { - return this->core_.source_; - } - - - template - ExtensibleFilterIter& - andFilter (COND conjunctiveClause) - { - function& filter = this->core_.predicate_; - - filter = [=](Val val) - { - return filter(val) - and conjunctiveClause(val); - }; - reEvaluate(); - return *this; - } - - template - ExtensibleFilterIter& - andNotFilter (COND conjunctiveClause) - { - function& filter = this->core_.predicate_; - - filter = [=](Val val) - { - return filter(val) - and not conjunctiveClause(val); - }; - reEvaluate(); - return *this; - } - - template - ExtensibleFilterIter& - orFilter (COND disjunctiveClause) - { - function& filter = this->core_.predicate_; - - filter = [=](Val val) - { - return filter(val) - or disjunctiveClause(val); - }; - reEvaluate(); - return *this; - } - - template - ExtensibleFilterIter& - orNotFilter (COND disjunctiveClause) - { - function& filter = this->core_.predicate_; - - filter = [=](Val val) - { - return filter(val) - or not disjunctiveClause(val); - }; - reEvaluate(); - return *this; - } - - - template - ExtensibleFilterIter& - setNewFilter (COND entirelyDifferentPredicate) - { - this->core_.predicate_ = entirelyDifferentPredicate; - reEvaluate(); - return *this; - } - - ExtensibleFilterIter& - flipFilter () - { - function& filter = this->core_.predicate_; - - filter = [=](Val val) - { - return not filter(val); - }; - reEvaluate(); - return *this; - } - }; - - - - /** - * Helper: predicate returning `true` - * whenever the argument value changes - * during a sequence of invocations. - */ - template - class SkipRepetition - { - using Item = wrapper::ItemWrapper; - - Item prev_; - - public: - bool - operator() (VAL const& elm) - { - if (prev_ and - (*prev_ == elm)) - return false; - - // element differs from predecessor - prev_ = elm; - return true; - } - - using result_type = bool; - }; - - - - - - - - - - - /** - * Implementation of a _singleton value_ holder, - * which discards the contained value once "iterated" - */ - template - class SingleValCore - { - using Item = wrapper::ItemWrapper; - - Item theValue_; - - public: - SingleValCore() { } ///< passive and empty - - SingleValCore (VAL&& something) - : theValue_{forward (something)} - { } - - Item const& - pipe () const - { - return theValue_; - } - - void - advance () - { - theValue_.reset(); - } - - bool - evaluate () const - { - return theValue_.isValid(); - } - - using pointer = std::remove_reference_t *; - using reference = std::remove_reference_t &; - using value_type = std::remove_reference_t ; - }; - - - /** - * Pseudo-Iterator to yield just a single value. - * When incremented, the value is destroyed and - * the Iterator transitions to _exhausted state_. - * @remark as such might look nonsensical, but proves - * useful when a function yields an iterator, while - * producing an explicit value in some special case. - * @tparam VAL anything, value or reference to store - */ - template - class SingleValIter - : public IterTool> - { - using _ValHolder = SingleValCore; - using _IteratorImpl = IterTool<_ValHolder> ; - - public: - SingleValIter () - : _IteratorImpl{_ValHolder{}} - { } - - SingleValIter (VAL&& something) - : _IteratorImpl{_ValHolder{forward(something)}} - { } - - ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (SingleValIter) - }; - - - - /** Build a SingleValIter: convenience free function shortcut, - * to pick up just any value and wrap it as Lumiera Forward Iterator. - * @return Iterator to yield the value once - * @warning be sure to understand that we literally pick up and wrap anything - * provided as argument. If you pass a reference, we wrap a reference. - * If you want to wrap a copy, you have to do the copy yourself inline - */ - template - inline auto - singleValIterator (VAL&& something) - { - return SingleValIter{forward(something)}; - } - - - /** not-anything-at-all iterator */ - template - inline auto - nilIterator() - { - return SingleValIter(); - } - - - - - - /** - * Implementation of custom processing logic. - * This core stores a function object instance - * to treat each source element pulled. - */ - template - class TransformingCore - { - using InType = iter::Yield; - using Item = wrapper::ItemWrapper; - - function trafo_; - - IT source_; - Item treated_; - - void - processItem () - { - if (source_) - treated_ = trafo_(*source_); - else - treated_.reset(); - } - - - - public: - TransformingCore () ///< deactivated core - : trafo_() - , source_() - , treated_() - { } - - template - TransformingCore (IT&& orig, FUN processor) - : trafo_(processor) // induces a signature check - , source_(forward (orig)) - { - processItem(); - } - - template - TransformingCore (IT const& orig, FUN processor) - : trafo_(processor) // induces a signature check - , source_(orig) - { - processItem(); - } - - Item const& - pipe () const - { - return treated_; - } - - void - advance () - { - ++source_; - processItem(); - } - - bool - evaluate () const - { - return bool(source_); - } - - using pointer = meta::RefTraits::pointer; - using reference = meta::RefTraits::reference; - using value_type = meta::RefTraits::value_type; - }; - - - /** - * Iterator tool treating pulled data by a custom transformation (function) - * @tparam IT source iterator - * @tparam VAL result (output) type - */ - template - class TransformIter - : public IterTool> - { - using _Trafo = TransformingCore; - using _IteratorImpl = IterTool<_Trafo> ; - - public: - TransformIter () - : _IteratorImpl(_Trafo()) - { } - - template - TransformIter (IT&& src, FUN trafoFunc) - : _IteratorImpl{_Trafo(forward(src), trafoFunc)} - { } - template - TransformIter (IT const& src, FUN trafoFunc) - : _IteratorImpl{_Trafo(src, trafoFunc)} - { } - - ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (TransformIter) - }; - - - - - - /** Build a TransformIter: convenience free function shortcut, - * picking up the involved types automatically. - * @tparam processingFunc to be invoked for each source element - * @return Iterator processing the source feed - */ - template - inline auto - transformIterator (IT const& src, FUN processingFunc) - { - using OutVal = lib::meta::_Fun::Ret; - return TransformIter{src,processingFunc}; - } - - template - inline auto - transformIterator (IT&& src, FUN processingFunc) - { - using SrcIT = std::remove_reference::type; - using OutVal = lib::meta::_Fun::Ret; - return TransformIter{forward(src), processingFunc}; - } - - - - - - namespace iter { + return s1.srcIter_ == s2.srcIter_ + and (not s1 or not s2 + or *s1 == *s2); + } // even same type might hold different function + }; + /** * Iterator adapter to yield a value, @@ -818,7 +405,7 @@ namespace lib { class ValueAdapter : public SRC { - static_assert(can_IterForEach::value, "Lumiera Iterator required as source."); + static_assert (can_IterForEach::value, "Lumiera Iterator required as source."); public: SRC::value_type @@ -835,12 +422,118 @@ namespace lib { using SRC::SRC; }; - } // namespace iter + + + /** + * Helper: predicate returning `true` + * whenever the argument value changes + * during a sequence of invocations. + */ + template + class ChangeDetector + { + using Item = wrapper::ItemWrapper; + + Item prev_; + + public: + bool + operator() (VAL const& elm) + { + if (prev_ and + (*prev_ == elm)) + return false; + + // element differs from predecessor + prev_ = elm; + return true; + } + }; + + }// namespace iter (building blocks) + + + + + + /** + * Build a filtering iterator that wraps a source iterator. + * Only elements classified as `true` will appear as results. + * @param filterPredicate to be invoked for each source element + * @note Both the source iterator and the predicate are _embodied_ + * into the generated filter-adapter object, yet both can be + * initialised by RValue. Furthermore this implies that both + * types should be default constructible and assignable, to + * support the typical usage pattern of an iterator. + */ + template + inline auto + filterIter (IT&& src, FUN&& filterPredicate) + { + using SrcIT = std::decay_t; + using Predi = std::decay_t; + return iter::Filter{forward (src) + ,forward(filterPredicate)}; + } + + + /** + * Build filtering adapter that removes + * consecutive identical values emitted by source iterator + */ + template + inline auto + filterRepeat (IT&& source) + { + using Val = meta::ValueTypeBinding::value_type; + return filterIter (forward (source) + ,iter::ChangeDetector{} ); + } + + + /** + * A »singleton« iterator that yields a single value and is empty after iteration. + * @warning Be sure to understand that we literally pick up and wrap anything + * provided as argument. If you pass a reference, we wrap a reference. + * If you want to wrap a copy, you have to create a copy value argument. + */ + template + inline auto + singletonIter (VAL&& something) + { + return iter::Single{forward (something)}; + } + + /** not-anything-at-all iterator */ + template + inline auto + nilIter() + { + return iter::Single(); + } + + + /** + * Build a transforming iterator that maps a function to the source. + * @tparam processingFun to be invoked for each source element + * @note both the source iterator and the transforming functor + * are embodied, together with a result cache buffer. + */ + template + inline auto + transformIter (IT&& src, FUN&& processingFun) + { + using SrcIT = std::decay_t; + using ProcF = std::decay_t; + return iter::Trans{forward (src) + ,forward(processingFun)}; + } + /** * Build an adapter that wraps the given iterator - * and returns by-value, taking a copy from the result + * and returns by-value, taking a copy from each result. */ template auto @@ -882,12 +575,11 @@ namespace lib { container.push_back (*iter); } - template - inline IT::value_type + inline meta::ValueTypeBinding::value_type pull_last (IT iter) { - using Val = IT::value_type; + using Val = meta::ValueTypeBinding::value_type; using Item = wrapper::ItemWrapper; Item lastElm; @@ -907,28 +599,5 @@ namespace lib { } - - /** filters away repeated values - * emitted by source iterator */ - template - inline auto - filterRepetitions (IT const& source) - { - using Val = meta::ValueTypeBinding::value_type; - return filterIterator (source, SkipRepetition()); - } - - template - inline auto - filterRepetitions (IT&& source) - { - using Val = meta::ValueTypeBinding::value_type; - return filterIterator (forward(source), SkipRepetition() ); - } - - - - - } // namespace lib #endif diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index fd1bb1066..930767930 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -494,6 +494,7 @@ namespace meta { * @note deliberately a Lumiera iterator does _not fulfil_ * the concept std::input_iterator. And vice versa: * a STL iterator lacks the `bool` check for iteration end. + * @see IterableClassification_test */ template class can_IterForEach diff --git a/src/steam/engine/job-planning.hpp b/src/steam/engine/job-planning.hpp index 25e043b92..d9e5528fb 100644 --- a/src/steam/engine/job-planning.hpp +++ b/src/steam/engine/job-planning.hpp @@ -176,11 +176,11 @@ namespace engine { auto buildDependencyPlanning() { - return lib::transformIterator (jobTicket_.getPrerequisites() - ,[this](JobTicket& prereqTicket) - { - return JobPlanning{*this, prereqTicket}; - }); + return lib::transformIter (jobTicket_.getPrerequisites() + ,[this](JobTicket& prereqTicket) + { + return JobPlanning{*this, prereqTicket}; + }); } private: diff --git a/src/steam/engine/job-ticket.hpp b/src/steam/engine/job-ticket.hpp index 661a6f89a..63ca5c04d 100644 --- a/src/steam/engine/job-ticket.hpp +++ b/src/steam/engine/job-ticket.hpp @@ -154,12 +154,12 @@ using lib::LUID; auto getPrerequisites () { - return lib::transformIterator (this->empty()? Prerequisites::iterator() - : provision_.prerequisites.begin() - ,[](Prerequisite& prq) -> JobTicket& - { - return prq.prereqTicket; - }); + return lib::transformIter (this->empty()? Prerequisites::iterator() + : provision_.prerequisites.begin() + ,[](Prerequisite& prq) -> JobTicket& + { + return prq.prereqTicket; + }); } /** diff --git a/src/steam/mobject/session/placement-index.cpp b/src/steam/mobject/session/placement-index.cpp index b473d3afc..f4198c8a1 100644 --- a/src/steam/mobject/session/placement-index.cpp +++ b/src/steam/mobject/session/placement-index.cpp @@ -395,7 +395,6 @@ namespace session { } - using ElementResolver = function const&)>; mutable ElementResolver elementResolver_; ElementResolver diff --git a/src/steam/mobject/session/placement-index.hpp b/src/steam/mobject/session/placement-index.hpp index 282cb4c51..9891b3f6d 100644 --- a/src/steam/mobject/session/placement-index.hpp +++ b/src/steam/mobject/session/placement-index.hpp @@ -106,6 +106,7 @@ #include "lib/util.hpp" #include +#include #include #include @@ -181,7 +182,9 @@ namespace session { using _PID = PlacementMO::ID; using ScopeIter = std::unordered_multimap<_PID,_PID>::const_iterator; using ScopeRangeIter = lib::RangeIter; - using _ID_TableIterator = lib::TransformIter; + using ElementResolver = std::function const&)>; + using _ID_TableIterator = decltype(lib::transformIter(std::declval() + ,std::declval())); diff --git a/src/test/event-log.cpp b/src/test/event-log.cpp index 95f5e54f2..826de4330 100644 --- a/src/test/event-log.cpp +++ b/src/test/event-log.cpp @@ -123,7 +123,7 @@ namespace test{ * @note the match also fails, when the given log entry * has more or less arguments, than the number of * given match expressions in `argSeq` - * @see ExtensibleFilterIter::andFilter() + * @see lib::iter_explorer::MutableFilter::andFilter() */ inline auto matchArguments (ArgSeq&& argSeq) diff --git a/src/test/test-coll.hpp b/src/test/test-coll.hpp index 4e737bb58..1973633b6 100644 --- a/src/test/test-coll.hpp +++ b/src/test/test-coll.hpp @@ -21,8 +21,9 @@ #define TESTSUPPORT_TEST_COLL_H +#include "lib/format-cout.hpp" + #include -#include #include #include @@ -30,19 +31,19 @@ namespace test { - typedef std::vector VecI; - template inline VEC getTestSeq_int (const uint NUM_ELMS) { VEC vec; for (uint i=0; i; using MapII = std::map; using MMapII = std::multimap; @@ -81,9 +82,9 @@ namespace test { pullOut (ITER const& i) { for (ITER ii(i); ii ; ++ii ) - std::cout << "::" << *ii; + cout << "::" << *ii; } - + }// namespace test diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests index 0a977d29d..3832abc4d 100644 --- a/tests/12metaprogramming.tests +++ b/tests/12metaprogramming.tests @@ -190,19 +190,7 @@ return: 0 END -TEST "Lumiera Iterator Tools" IterTools_test 20 < : Yes -out: can_STL_ForEach : Yes -out: can_STL_ForEach : Yes -out: can_STL_ForEach : Yes -out: can_STL_ForEach : Yes -out: can_STL_ForEach : Yes -out: can_STL_ForEach : No -out: can_STL_ForEach : No -out: can_STL_ForEach : No -out: can_STL_ForEach : No -out: can_IterForEach : No -out: can_IterForEach : No -out: can_IterForEach : No -out: can_IterForEach : No -out: can_IterForEach : No -out: can_IterForEach : No -out: can_IterForEach : Yes -out: can_IterForEach : Yes -out: can_IterForEach : Yes -out: can_IterForEach : Yes return: 0 END diff --git a/tests/lib/diff/diff-tree-application-simple-test.cpp b/tests/lib/diff/diff-tree-application-simple-test.cpp index 8a5d9974a..efc8cef7f 100644 --- a/tests/lib/diff/diff-tree-application-simple-test.cpp +++ b/tests/lib/diff/diff-tree-application-simple-test.cpp @@ -22,6 +22,7 @@ #include "lib/diff/mutation-message.hpp" #include "lib/diff/tree-diff-application.hpp" #include "lib/format-util.hpp" +#include "lib/itertools.hpp" #include "lib/util.hpp" #include @@ -47,9 +48,9 @@ namespace test{ string contents (Rec const& object) { - return util::join (transformIterator (object.scope() - ,[](GenNode const& n) { return n.data.get(); } - )); + return util::join (transformIter (object.scope() + ,[](GenNode const& n) { return n.data.get(); } + )); } string diff --git a/tests/lib/format-helper-test.cpp b/tests/lib/format-helper-test.cpp index 2ffe762c9..f1bde0ea7 100644 --- a/tests/lib/format-helper-test.cpp +++ b/tests/lib/format-helper-test.cpp @@ -28,9 +28,9 @@ using lumiera::error::Fatal; -using lib::transformIterator; using lib::iter_stl::snapshot; using lib::iter_stl::eachElm; +using lib::transformIter; using lib::eachNum; using util::_Fmt; @@ -215,7 +215,7 @@ namespace test { "7.7--+--" "8.8--+--" "9.9"_expect); - CHECK (join (transformIterator(eachElm(dubious), justCount)) + CHECK (join (transformIter (eachElm(dubious), justCount)) == "Nr.01(0.0), " "Nr.02(2.2), " "Nr.03(4.4), " diff --git a/tests/lib/iter-core-adapter-test.cpp b/tests/lib/iter-core-adapter-test.cpp index 520734628..06f505e4e 100644 --- a/tests/lib/iter-core-adapter-test.cpp +++ b/tests/lib/iter-core-adapter-test.cpp @@ -38,7 +38,7 @@ namespace test{ namespace { // Test fixture /** - * A test »*State Core*« which steps down a number to zero. + * A test »**State Core**« which steps down a number to zero. * @note this is a minimal description of a state progression towards a goal * - default constructed is equivalent to _goal was reached_ * - can be copied, manipulated and compared @@ -49,24 +49,24 @@ namespace test{ { uint n; - StepDown(uint start =0) - : n(start) + StepDown (uint start =0) + : n{start} { } bool - checkPoint () const + checkPoint() const { return n != 0; } uint& - yield () const + yield() const { return util::unConst(n); } void - iterNext () + iterNext() { --n; } diff --git a/tests/lib/iter-explorer-test.cpp b/tests/lib/iter-explorer-test.cpp index 836d554db..9a362aafe 100644 --- a/tests/lib/iter-explorer-test.cpp +++ b/tests/lib/iter-explorer-test.cpp @@ -13,29 +13,37 @@ /** @file iter-explorer-test.cpp ** The \ref IterExplorer_test covers and demonstrates a generic mechanism - ** to expand and evaluate tree like structures. It was created in response to - ** a recurring need for configurable tree expanding and backtracking evaluations. - ** Due to the nature of Lumiera's design, we repeatedly encounter this kind of - ** algorithms, when it comes to matching configuration and parametrisation against - ** a likewise hierarchical and rules based model. To keep the code base maintainable, - ** we deem it crucial to reduce the inherent complexity in such algorithms by clearly - ** separate the _mechanics of evaluation_ from the actual logic of the target domain. + ** to expand and evaluate tree like structures. This was created to address + ** the recurring need for **tree-expanding and backtracking evaluations**. + ** Due to the nature of Lumiera's design, we frequently encounter this type + ** of evaluation pattern when it comes to matching a given configuration with + ** a hierarchical and rule-based model. To keep the codebase maintainable, it + ** is considered crucial to reduce the inherent complexity of such algorithms + ** by clearly separating the _evaluation mechanisms_ from the actual logic + ** of the target domain. ** - ** This test relies on a demonstration setup featuring a custom encapsulated state type: - ** we rely on a counter with start and end value, embedded into an iterator as »state core«. - ** This running counter, when iterated, generates a descending sequence of numbers start ... end. - ** So -- conceptually -- this counting iterator can be conceived as _representing_ this sequence - ** of numbers, while not actually representing all these numbers as data in memory. And this is - ** the whole point of the exercise: _not to represent_ this sequence in runtime state at once, - ** rather to __pull and expand it on demand._ + ** This test relies on a demonstration setup that features a **State Core** — + ** which is custom type with encapsulated evaluation state: here we rely on a + ** counter with start and end value, which however is not accessed directly from + ** the outside, but rather through a set of functions expected to be present on + ** every »state core« (effectively this is a Concept). So this running counter, + ** when iterated, generates a descending sequence of numbers ∈ [start ... end[. + ** So — conceptually — this counting iterator can be conceived as _representing_ + ** this sequence of numbers, while not actually representing all these numbers as + ** data in memory. And this is the whole point of the exercise: _not to materialise_ + ** this sequence in runtime state at once, rather to _pull and expand it on demand_. ** - ** All these tests work by first defining these _functional structures_, which just - ** yields an iterator entity. We get the whole structure it conceptually defines - ** only if we »pull« and »materialise« this iterator until exhaustion — which essentially - ** is what the test does to verify proper operation. In contrast, _Real World Code_ of course - ** would not proceed in this way, like pulling everything from such an iterator. Since often - ** the very reason we're using such a setup is the ability to represent infinite structures. - ** Like e.g. the evaluation graph of video passed through a complex processing pipeline. + ** All these tests work by first defining these _functional structures_, in the form + ** of a pipeline, which, as a language construct, is embodied into an iterator entity. + ** This is a copyable value object that implements the common function expected to be + ** present on each »Lumiera Forward Iterator«. We then retrieve the whole structure it + ** conceptually defines only if we »pull« and »materialise« this iterator until it is + ** exhausted — which essentially is what the test does to verify proper operation. + ** + ** In contrast, _Real World Code_ of course would not proceed in this way, like pulling + ** everything from such an iterator. Since often the very reason we're using such a setup + ** is the ability to represent infinite structures. An example would be the evaluation + ** graph of video passed through a complex processing pipeline. ** */ @@ -474,20 +482,20 @@ namespace test{ ++ii; CHECK (3 == *ii); CHECK (0 == ii.depth()); - CHECK (materialise(ii) == "3-2-1"); + CHECK (materialise(ii) == "3-2-1"_expect); ii.expandChildren(); CHECK (1 == ii.depth()); - CHECK (materialise(ii) == "2-1-2-1"); + CHECK (materialise(ii) == "2-1-2-1"_expect); ++++ii; CHECK (0 == ii.depth()); - CHECK (materialise(ii) == "2-1"); + CHECK (materialise(ii) == "2-1"_expect); ii.expandChildren(); CHECK (1 == ii.depth()); - CHECK (materialise(ii) == "1-1"); + CHECK (materialise(ii) == "1-1"_expect); ++ii; CHECK (0 == ii.depth()); CHECK (1 == *ii); - CHECK (materialise(ii) == "1"); + CHECK (materialise(ii) == "1"_expect); ii.expandChildren(); CHECK (isnil (ii)); VERIFY_ERROR (ITER_EXHAUST, *ii ); @@ -586,18 +594,27 @@ namespace test{ // demonstrate chaining of several transformation layers vector numz{1,-2,3,-5,8,-13}; - CHECK ("≺1≻-≺-2≻-≺3≻-≺-5≻-≺8≻-≺-13≻" == materialise (explore(numz) - .transform(formatify)) ); + CHECK (materialise ( + explore(numz) + .transform(formatify) + ) + == "≺1≻-≺-2≻-≺3≻-≺-5≻-≺8≻-≺-13≻"_expect); - CHECK ("≺2≻-≺-4≻-≺6≻-≺-10≻-≺16≻-≺-26≻" == materialise (explore(numz) - .transform(multiply) - .transform(formatify)) ); + CHECK (materialise ( + explore(numz) + .transform(multiply) + .transform(formatify) + ) + == "≺2≻-≺-4≻-≺6≻-≺-10≻-≺16≻-≺-26≻"_expect); - CHECK ("≺≺4≻≻-≺≺-8≻≻-≺≺12≻≻-≺≺-20≻≻-≺≺32≻≻-≺≺-52≻≻" == materialise (explore(numz) - .transform(multiply) - .transform(multiply) - .transform(formatify) - .transform(formatify)) ); + CHECK (materialise ( + explore(numz) + .transform(multiply) + .transform(multiply) + .transform(formatify) + .transform(formatify) + ) + == "≺≺4≻≻-≺≺-8≻≻-≺≺12≻≻-≺≺-20≻≻-≺≺32≻≻-≺≺-52≻≻"_expect); // demonstrate the functor is evaluated only once per step @@ -869,8 +886,8 @@ namespace test{ CHECK (materialise ( explore(CountDown{10}) .transform(util::toString) - .groupedBy([](auto& it) { return std::ilogbf (it.p); }) // note trickery: takes not the value, rather the iterator and - ) // accesses internals of CountDown, bypassing the transform layer above + .groupedBy([](auto& it) { return std::ilogbf (it.p); }) // note trickery: accept not the value, rather the iterator and + ) // access internals of CountDown, bypassing the transform layer above == "1098-7654-32-1"_expect); // `+` does string concatenation @@ -1577,8 +1594,8 @@ namespace test{ { uint val = get<0>(tup); uint sum = get<1>(tup); - return val? singleValIterator (Tu2{val-1, sum+val}) - : SingleValIter(); + return val? singletonIter(Tu2{val-1, sum+val}) + : nilIter(); }; CHECK (materialise( diff --git a/tests/lib/iter-source-test.cpp b/tests/lib/iter-source-test.cpp index 48ab5ac82..c82491232 100644 --- a/tests/lib/iter-source-test.cpp +++ b/tests/lib/iter-source-test.cpp @@ -310,7 +310,7 @@ namespace test{ template void - verify_MultimapIters() ///< @see IterTools_test#verify_filterRepetitions + verify_MultimapIters() ///< @see IterTools_test#verify_filterRepeat { MAP testMap; for (uint i=0; i + 2009,2026 Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the @@ -16,52 +16,40 @@ */ - #include "test/run.hpp" +#include "lib/itertools.hpp" +#include "test/test-coll.hpp" #include "test/test-helper.hpp" -#include "lib/util-foreach.hpp" -#include "lib/format-cout.hpp" +#include "lib/iter-adapter-stl.hpp" +#include "lib/format-util.hpp" #include "lib/util.hpp" -#include "lib/itertools.hpp" - -#include -#include +using test::Test; +using util::isnil; +using util::join; +using std::vector; +using test::VecI; +using test::getTestSeq_int; +using test::showType; namespace lib { namespace test{ - using ::Test; - using boost::lexical_cast; - using util::for_each; - using util::isnil; - using std::vector; - using LERR_(ITER_EXHAUST); - - namespace { // Test data + namespace { // a test iterator - struct TestSource + const uint NUM_ELMS{10}; + const auto TEST_NUMZ = getTestSeq_int (NUM_ELMS); + + inline auto + iterNum() { - vector data_; - - TestSource(uint num) - { - while (num) - data_.push_back(num--); - } - - typedef vector::iterator sourceIter; - typedef RangeIter iterator; - - iterator begin() { return iterator(data_.begin(),data_.end()); } - iterator end() { return iterator(); } - - }; + return RangeIter{TEST_NUMZ.begin(), TEST_NUMZ.end()}; + } } // (END) Test data @@ -72,266 +60,55 @@ namespace test{ /***************************************************************************//** - * @test build combined and filtering iterators with the help of lib::IterTool. + * @test build simple filter and transforming iterators using the »itertools«. * Check correct behaviour of the resulting iterators and - * verify they fulfil the Lumiera Forward Iterator concept - * - * @todo implement more iterator tools.... see Ticket #347 + * verify they behave like a **Lumiera Forward Iterator**. */ class IterTools_test : public Test { - typedef TestSource::iterator Iter; - - uint NUM_ELMS{0}; + using Iter = decltype(iterNum()); virtual void - run (Arg arg) + run (Arg) { - NUM_ELMS = firstVal (arg, 10); - - TestSource source(NUM_ELMS); - - pullOut (source.begin()); - verifyComparisons (source.begin()); - - - buildFilterIterator (source.begin()); - Iter ii (source.begin()); - ++++++ii; - buildFilterIterator (ii); - verify_filterExtension(); - verify_filterRepetitions(); - - buildWrappedSingleElement(); - - buildTransformingIterator (source.begin()); - - verifyPullLast(source.begin()); + verify_testSetup(); + + verify_singleIter(); + verify_filterIter(); + verify_filterRepeat(); + verify_transformIter(); + verify_pullLast(); } - - template + /** @test fundamental iterator behaviour. */ void - pullOut (IT const& ii) + verify_testSetup() { - for (IT iter(ii) ; iter; ++iter ) - cout << "::" << *iter; - cout << endl; + Iter ii = iterNum(); + + CHECK (ii); + CHECK (not isnil(ii)); + CHECK (0 == *ii); + ++++ii; + CHECK (2 == *ii); + + verifyComparisons(ii); + CHECK (join (ii) == "2, 3, 4, 5, 6, 7, 8, 9"_expect ); + CHECK (join (ii, "-") == "2-3-4-5-6-7-8-9"_expect ); + CHECK (ii); + CHECK (2 == *ii); + + while (ii) + ++ii; + CHECK (not ii); + VERIFY_ERROR (ITER_EXHAUST, *ii ); + VERIFY_ERROR (ITER_EXHAUST, ++ii); } - - static bool takeAll (int) { return true; } - static bool takeOdd (int i) { return 0 != i % 2; } - static bool takeEve (int i) { return 0 == i % 2; } - static bool takeTrd (int i) { return 0 == i % 3; } - - void - buildFilterIterator (Iter const& ii) - { - pullOut (filterIterator (ii, takeAll)); // note: using the convenient builder function - pullOut (filterIterator (ii, takeEve)); - pullOut (filterIterator (ii, takeOdd)); - - FilterIter all (ii, takeAll); - FilterIter odd (ii, takeOdd); - verifyComparisons (all); - verifyComparisons (odd); - - while (++all && ++odd) - CHECK (all != odd); - - while (++all) { } - CHECK (isnil (odd)); - CHECK (all == odd); - } - - - /** @test verify the ability to extend a filter condition - * while in the middle of an ongoing iteration. - * Typically this means sharpening the filter condition - * and thus making the filter more restrictive, filtering - * away more elements of the source stream. But through - * the ability to add disjunctive and negated clauses, - * it is also possible to weaken the filter condition - * @note in case of a weakened filter condition, there is - * \em no reset of the source iterator, i.e. we don't - * re-evaluate from start, but just from current head. - */ - void - verify_filterExtension () - { - typedef vector Src; - typedef Src::iterator SrcIter; - typedef RangeIter SeqIter; - typedef ExtensibleFilterIter FilteredSeq; - - Src src; - for (uint i=0; i < 3*NUM_ELMS; ++i) - src.push_back(i); - - SeqIter completeSequence (src.begin(), src.end()); - FilteredSeq filterIter (completeSequence, takeAll); - - CHECK (!isnil (filterIter)); - CHECK (0 == *filterIter); - ++filterIter; - CHECK (1 == *filterIter); - - filterIter.andFilter(takeEve); - CHECK (!isnil (filterIter)); - CHECK (2 == *filterIter); - ++filterIter; - CHECK (4 == *filterIter); - - // sharpen the condition... - filterIter.andFilter(takeTrd); - CHECK (!isnil (filterIter)); - CHECK (6 == *filterIter); // divisible by two and by three - ++filterIter; - CHECK (12 == *filterIter); - - verifyComparisons (filterIter); - pullOut (filterIter); - - // adding a disjunctive clause actually weakens the filter... - filterIter = {completeSequence, takeTrd}; - CHECK (!isnil (filterIter)); - CHECK (0 == *filterIter); - ++filterIter; - CHECK (3 == *filterIter); - - filterIter.orFilter(takeEve); - CHECK (3 == *filterIter); - ++filterIter; - CHECK (4 == *filterIter); - ++filterIter; - CHECK (6 == *filterIter); - verifyComparisons (filterIter); - - // flip filter logic - filterIter.flipFilter(); - CHECK (7 == *filterIter); // not even and not divisible by three - ++filterIter; - CHECK (11 == *filterIter); - ++filterIter; - CHECK (13 == *filterIter); - - verifyComparisons (filterIter); - pullOut (filterIter); - } - - - /** @test verify the helper to filter duplicate elements - * emitted by an source iterator. This test creates - * a sequence of numbers with random repetitions. - */ - void - verify_filterRepetitions () - { - vector numberz; - for (uint i=0; i::iterator SrcIter; - typedef RangeIter SeqIter; - typedef FilterIter FilteredSeq; - - SeqIter completeSequence (numberz.begin(), numberz.end()); - FilteredSeq filtered = filterRepetitions (completeSequence); - - uint num=0; - for (; num{}; - CHECK (isnil(i1)); - } - - - - static ulong addTwo (int i) { return i+2; } - static int negate (int i) { return -i; } - static int idFunc (int i) { return i; } - - void - buildTransformingIterator (Iter const& ii) - { - pullOut (transformIterator(ii, idFunc)); - pullOut (transformIterator(ii, negate)); - pullOut (transformIterator(ii, addTwo)); // note: changing output type to unsigned - - TransformIter idi (ii, idFunc); - TransformIter neg (ii, negate); - verifyComparisons (idi); - verifyComparisons (neg); - - CHECK (idi); - CHECK (neg); - for ( ;idi&&neg; - ++idi,++neg) - CHECK (idi != neg); - - CHECK (!idi and !neg); - CHECK (idi == neg); - } - - - - - /** @test verify equality handling and NIL detection * for the given iterator/wrapper handed in */ template @@ -370,11 +147,159 @@ namespace test{ } + /** @test wrap an arbitrary single element as pseudo-iterator */ void - verifyPullLast(Iter const& ii) + verify_singleIter() { + uint i{12}; + + auto i1 = singletonIter (12); + auto i2 = singletonIter ( i); + auto i3 = singletonIter (&i); + + CHECK (not isnil(i1)); + CHECK (not isnil(i2)); + CHECK (not isnil(i3)); + CHECK (12 == *i1); + CHECK (12 == *i2); + CHECK (12 == **i3); + + i = 23; + CHECK (12 == *i1); + CHECK (23 == *i2); + CHECK (23 == **i3); + + ++i1; + ++i2; + ++i3; + CHECK (isnil(i1)); + CHECK (isnil(i2)); + CHECK (isnil(i3)); + VERIFY_ERROR (ITER_EXHAUST, *i1 ); + VERIFY_ERROR (ITER_EXHAUST, *i2 ); + VERIFY_ERROR (ITER_EXHAUST, *i3 ); + + // assignable as any iterator... + auto i0 = nilIter(); + i0 = singletonIter (13); + CHECK (13 == *i0); + CHECK (isnil (i1)); + std::swap (i0,i1); + CHECK (isnil (i0)); + CHECK (13 == *i1); + + i1 = iter::Single{}; + CHECK (isnil(i1)); + } + + + static bool takeAll (int) { return true; } + static bool takeOdd (int i) { return 0 != i % 2; } + static bool takeEve (int i) { return 0 == i % 2; } + static bool takeTrd (int i) { return 0 == i % 3; } + + /** @test build a filter pipeline based on a predicate */ + void + verify_filterIter() + { + Iter ii = iterNum(); + + CHECK (join (filterIter (ii, takeAll)) == "0, 1, 2, 3, 4, 5, 6, 7, 8, 9"_expect ); + CHECK (join (filterIter (ii, takeEve)) == "0, 2, 4, 6, 8"_expect ); + CHECK (join (filterIter (ii, takeOdd)) == "1, 3, 5, 7, 9"_expect ); + + auto all = filterIter (ii, takeAll); + auto odd = filterIter (ii, takeOdd); + verifyComparisons (all); + verifyComparisons (odd); + + while (++all and ++odd) + CHECK (all != odd); + + while (++all) { } + CHECK (isnil (odd)); + CHECK (all == odd); + } + + + /** @test verify the helper to filter duplicate elements + * emitted by an source iterator. This test creates + * a sequence of numbers with random repetitions. + */ + void + verify_filterRepeat() + { + vector numberz; + for (uint i=0; i() == "uint"_expect); + + auto idi = transformIter (ii, idFunc); + auto neg = transformIter (ii, negate); + verifyComparisons (idi); + verifyComparisons (neg); + + CHECK (idi == neg); // Note twist: same type, different functor, yet same result (≡0) + CHECK (++idi); + CHECK (++neg); + for ( ; idi and neg; + ++idi, + ++neg) + { + CHECK ( idi != neg); + CHECK (*idi != *neg); + } + + CHECK (!idi and !neg); + CHECK (idi == neg); + } + + + + /** @test helper to pull an iterator until its last element */ + void + verify_pullLast() + { + Iter ii = iterNum(); Iter::value_type lastElm = pull_last (ii); - CHECK (1 == lastElm); // TestSource holds a decreasing sequence of numbers ending with 1 + CHECK (NUM_ELMS-1 == lastElm); Iter emptyIterator; CHECK (isnil (emptyIterator)); @@ -387,4 +312,3 @@ namespace test{ }} // namespace lib::test - diff --git a/tests/lib/meta/iterable-classification-test.cpp b/tests/lib/meta/iterable-classification-test.cpp index 1f3af4c20..3cc500262 100644 --- a/tests/lib/meta/iterable-classification-test.cpp +++ b/tests/lib/meta/iterable-classification-test.cpp @@ -13,16 +13,17 @@ /** @file iterable-classification-test.cpp ** unit test \ref IterableClassification_test + ** @see iter-adapter.hpp + ** @see lib/meta/trait.hpp + ** @see lib/meta/duck-detector.hpp */ #include "test/run.hpp" -#include "steam/mobject/session/scope-query.hpp" -#include "steam/mobject/session/effect.hpp" -#include "lib/meta/duck-detector.hpp" +#include "lib/meta/trait.hpp" +#include "lib/diff/record.hpp" #include "lib/time/timevalue.hpp" -#include "lib/util-foreach.hpp" #include "lib/itertools.hpp" #include @@ -33,51 +34,60 @@ #include +using std::string; +using std::vector; +using std::declval; +using lib::time::TimeVar; + namespace lib { namespace meta { namespace test { - using steam::mobject::session::Effect; - using steam::mobject::session::ScopeQuery; - typedef lib::time::TimeVar Time; - - using std::cout; - using std::endl; - - namespace { // a custom test container.... - + /// @note: a bare type definition is sufficient here.... struct TestSource { - vector data_; - TestSource(uint num); - typedef vector::iterator sourceIter; - typedef RangeIter iterator; + using RawIter = vector::iterator; + using iterator = RangeIter; iterator begin() ; iterator end() ; - // note: a bare type definition is sufficient here.... }; + inline auto transformedSource(TestSource& src){ return lib::transformIter (src.begin(), [](int){ return long(0); }); } + inline auto filteredSource (TestSource& src){ return lib::filterIter (src.begin(), [](int){ return true; }); } + /// @note Dummy type that exposes a »*State Core*« interface. + struct DummyCore + { + bool checkPoint() const; + uint& yield() const; + void iterNext(); + friend bool operator== (DummyCore const&, DummyCore const&); + }; + }//(End) test containers -#define SHOW_CHECK(_EXPR_) cout << STRINGIFY(_EXPR_) << "\t : " << (_EXPR_::value? "Yes":"No") << endl; - /*******************************************************************************//** * @test verify the (static) classification/detection of iterables. - * Currently (1/10) we're able to detect the following - * - a STL like container with \c begin() and \c end() - * - a Lumiera Forward Iterator - * This test just retrieves the results of a compile time execution - * of the type detection; thus we just define types and then access + * Using some SFINAE trickery, it is possible to detect the following + * - a _STL-like container_ with `begin()` and `end()` + * - a _Lumiera Forward Iterator_ + * - a _State Core_ + * This test ensures this compile time detection works as intended; + * it suffices thus to define some minimal types in order to access * the generated meta function value. + * @see iter-adapter.hpp explanation of _Lumiera Forward Iterator_ and _State Core_ + * @todo 2026 many years later and this type detection has become a cornerstone + * of Lumiera's iterator pipeline framework. Since the language level has + * been raised recently to C++23, it should be considered to replace + * the SFINAE-based traits with Concept definitions. */ class IterableClassification_test : public Test { @@ -86,45 +96,71 @@ namespace test { run (Arg) { // define a bunch of STL containers - typedef std::vector LongVector; - typedef std::multiset