From a835a796b28c50a6ec4804bc7028a8dc595d6a2d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 30 Mar 2026 21:24:54 +0200 Subject: [PATCH] Library: investigate further ramifications and clean-up Itertools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The detailed analysis of corner cases indicated a lot of hidden dangers when stretching the `IterExplorer` design beyond its limits. By deliberate choice, this design was kept open so that advanced cohesive implementation structures can be built. It is even possible ''and tolerated'' to rely on ''references'' to some degree -- assuming however that the referred memory location remains stable. As it turned out, these basic assumptions can be broken by a combination of read-ahead processing (as is necessary for grouping and filtering), together with adaptor objects embedded as value, especially when the resulting pipeline object is then moved or copied during evaluation. While my general attitude towards such extremely clever implementation constructs is "don't shoot yourself into your foot unless you need to" -- it seems prudent to clean-up some crappy code from the first attempts towards an iterator pipeline -- notably some of the »Itertools«. --- src/lib/diff/gen-node.cpp | 6 +- src/lib/diff/gen-node.hpp | 18 +- src/lib/diff/record.hpp | 14 +- src/lib/diff/test-mutation-target.hpp | 10 +- .../diff/tree-mutator-collection-binding.hpp | 1 + src/lib/format-util.hpp | 2 +- src/lib/item-wrapper.hpp | 2 +- src/lib/iter-source.hpp | 26 +- src/lib/itertools.hpp | 1249 +++++--------- src/lib/meta/trait.hpp | 1 + src/steam/engine/job-planning.hpp | 10 +- src/steam/engine/job-ticket.hpp | 12 +- src/steam/mobject/session/placement-index.cpp | 1 - src/steam/mobject/session/placement-index.hpp | 5 +- src/test/event-log.cpp | 2 +- src/test/test-coll.hpp | 13 +- tests/12metaprogramming.tests | 34 +- .../diff-tree-application-simple-test.cpp | 7 +- tests/lib/format-helper-test.cpp | 4 +- tests/lib/iter-core-adapter-test.cpp | 12 +- tests/lib/iter-explorer-test.cpp | 97 +- tests/lib/iter-source-test.cpp | 2 +- tests/lib/itertools-test.cpp | 480 +++--- .../lib/meta/iterable-classification-test.cpp | 148 +- tests/stage/gen-node-location-query.hpp | 4 +- tests/stage/test/test-nexus.cpp | 6 +- tests/steam/engine/mock-support-test.cpp | 4 +- wiki/thinkPad.ichthyo.mm | 1489 ++++++++++++++++- 28 files changed, 2328 insertions(+), 1331 deletions(-) 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