Library: investigate further ramifications and clean-up Itertools

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«.
This commit is contained in:
Fischlurch 2026-03-30 21:24:54 +02:00
parent 40d3167d1a
commit a835a796b2
28 changed files with 2328 additions and 1331 deletions

View file

@ -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))
+ "}"
;
}

View file

@ -375,8 +375,6 @@ namespace diff{
iterator end() const;
using ChildDataIter = TransformIter<Rec::scopeIter, DataCap const&>;
/** 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<Rec::scopeIter>(scopeIter)
, [](GenNode const& child) ->DataCap const&
return transformIter(std::forward<Rec::scopeIter>(scopeIter)
,[](GenNode const& child) ->DataCap const&
{
return child.data;
}
};
);
}

View file

@ -308,18 +308,16 @@ namespace diff{
using iterator = IterAdapter<ElmIter, const Record*>;
using scopeIter = iter_stl::_SeqT<const Storage>::Range;
using keyIter = TransformIter<scopeIter, string>;
using valIter = TransformIter<scopeIter, Access>;
/** 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<VAL>::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())+"}")
+ ")"
;

View file

@ -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

View file

@ -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"

View file

@ -135,7 +135,7 @@ namespace util {
{
using Val = lib::meta::ValueTypeBinding<IT>::value_type;
return lib::transformIterator(forward<IT>(src), util::toString<Val>);
return lib::transformIter (forward<IT>(src), util::toString<Val>);
}

View file

@ -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
**
*/

View file

@ -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<IT>::type;
using ResVal = lib::meta::_Fun<FUN>::Ret;
using TransIter = TransformIter<Src, ResVal>;
using TransIter = decltype(lib::transformIter (std::declval<IT>(), std::declval<FUN>()));
using Iter = IterSource<ResVal>::iterator;
};
@ -326,26 +327,23 @@ namespace lib {
// we can strip the const added by the STL map types
using KeyType = std::remove_const<ConstKeyType>::type;
using KeyIter = TransformIter<Src, KeyType>;
using ValIter = TransformIter<Src, ValType>;
static KeyType takeFirst (PairType const& pair) { return pair.first; }
static ValType takeSecond(PairType const& pair) { return pair.second;}
};
template<class IT>
_PairIterT<IT>::KeyIter
auto
takePairFirst (IT&& source)
{
return transformIterator(forward<IT>(source), _PairIterT<IT>::takeFirst );
return transformIter (forward<IT>(source), _PairIterT<IT>::takeFirst );
}
template<class IT>
_PairIterT<IT>::ValIter
auto
takePairSecond (IT&& source)
{
return transformIterator(forward<IT>(source), _PairIterT<IT>::takeSecond );
return transformIter (forward<IT>(source), _PairIterT<IT>::takeSecond );
}
} //(END) type helpers...
@ -375,10 +373,10 @@ namespace lib {
auto
singleVal (VAL&& something)
{
using Src = decltype(singleValIterator (forward<VAL>(something)));
using Src = decltype(singletonIter (forward<VAL>(something)));
using Val = _IterT<Src>::Val;
return IterSource<Val>::build (new WrappedLumieraIter<Src>{singleValIterator (forward<VAL>(something))});
return IterSource<Val>::build (new WrappedLumieraIter<Src>{singletonIter (forward<VAL>(something))});
}
@ -400,7 +398,7 @@ namespace lib {
return IterSource<ValType>::build (
new WrappedLumieraIter<TransIT> (
transformIterator (forward<IT>(source), processingFunc)));
transformIter (forward<IT>(source), processingFunc)));
}
@ -408,7 +406,7 @@ namespace lib {
* all the keys of the given Map or Hashtable
*/
template<class MAP>
_MapT<MAP>::KeyIter
auto
eachMapKey (MAP& map)
{
using Range = RangeIter<typename MAP::iterator>;
@ -438,13 +436,13 @@ namespace lib {
* the distinct keys
*/
template<class MAP>
_MapT<MAP>::KeyIter
auto
eachDistinctKey (MAP& map)
{
using Range = RangeIter<typename MAP::iterator>;
Range contents (map.begin(), map.end());
return wrapIter (filterRepetitions (takePairFirst(contents)));
return wrapIter (filterRepeat (takePairFirst(contents)));
}

File diff suppressed because it is too large Load diff

View file

@ -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<typename T>
class can_IterForEach

View file

@ -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:

View file

@ -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;
});
}
/**

View file

@ -395,7 +395,6 @@ namespace session {
}
using ElementResolver = function<PlacementMO& (pair<PID,PID> const&)>;
mutable ElementResolver elementResolver_;
ElementResolver

View file

@ -106,6 +106,7 @@
#include "lib/util.hpp"
#include <unordered_map>
#include <functional>
#include <memory>
#include <vector>
@ -181,7 +182,9 @@ namespace session {
using _PID = PlacementMO::ID;
using ScopeIter = std::unordered_multimap<_PID,_PID>::const_iterator;
using ScopeRangeIter = lib::RangeIter<ScopeIter>;
using _ID_TableIterator = lib::TransformIter<ScopeRangeIter, PlacementMO&>;
using ElementResolver = std::function<PlacementMO& (std::pair<_PID,_PID> const&)>;
using _ID_TableIterator = decltype(lib::transformIter(std::declval<ScopeRangeIter>()
,std::declval<ElementResolver>()));

View file

@ -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)

View file

@ -21,8 +21,9 @@
#define TESTSUPPORT_TEST_COLL_H
#include "lib/format-cout.hpp"
#include <unordered_map>
#include <iostream>
#include <vector>
#include <map>
@ -30,19 +31,19 @@
namespace test {
typedef std::vector<int> VecI;
template<class VEC>
inline VEC
getTestSeq_int (const uint NUM_ELMS)
{
VEC vec;
for (uint i=0; i<NUM_ELMS; ++i)
vec.push_back (i);
vec.emplace_back (i);
return vec;
}
using VecI = std::vector<int>;
using MapII = std::map<int,int>;
using MMapII = std::multimap<int,int>;
@ -81,9 +82,9 @@ namespace test {
pullOut (ITER const& i)
{
for (ITER ii(i); ii ; ++ii )
std::cout << "::" << *ii;
cout << "::" << *ii;
}
}// namespace test

View file

@ -190,19 +190,7 @@ return: 0
END
TEST "Lumiera Iterator Tools" IterTools_test 20 <<END
out-lit: ::20::19::18::17::16::15::14::13::12::11::10::9::8::7::6::5::4::3::2::1
out-lit: ::20::19::18::17::16::15::14::13::12::11::10::9::8::7::6::5::4::3::2::1
out-lit: ::20::18::16::14::12::10::8::6::4::2
out-lit: ::19::17::15::13::11::9::7::5::3::1
out-lit: ::17::16::15::14::13::12::11::10::9::8::7::6::5::4::3::2::1
out-lit: ::16::14::12::10::8::6::4::2
out-lit: ::17::15::13::11::9::7::5::3::1
out-lit: ::12::18::24::30::36::42::48::54
out-lit: ::13::17::19::23::25::29::31::35::37::41::43::47::49::53::55::59
out-lit: ::20::19::18::17::16::15::14::13::12::11::10::9::8::7::6::5::4::3::2::1
out-lit: ::-20::-19::-18::-17::-16::-15::-14::-13::-12::-11::-10::-9::-8::-7::-6::-5::-4::-3::-2::-1
out-lit: ::22::21::20::19::18::17::16::15::14::13::12::11::10::9::8::7::6::5::4::3
TEST "Lumiera Iterator Tools" IterTools_test <<END
return: 0
END
@ -262,26 +250,6 @@ END
TEST "Iterable type detection" IterableClassification_test <<END
out: can_STL_ForEach<LongVector> : Yes
out: can_STL_ForEach<TimeSet> : Yes
out: can_STL_ForEach<CharMap> : Yes
out: can_STL_ForEach<BoolList> : Yes
out: can_STL_ForEach<ShortDeque> : Yes
out: can_STL_ForEach<CustomCont> : Yes
out: can_STL_ForEach<ForwardRangeIter> : No
out: can_STL_ForEach<TransformedForwardIter> : No
out: can_STL_ForEach<FilteredForwardIter> : No
out: can_STL_ForEach<CustomForwardIter> : No
out: can_IterForEach<LongVector> : No
out: can_IterForEach<TimeSet> : No
out: can_IterForEach<CharMap> : No
out: can_IterForEach<BoolList> : No
out: can_IterForEach<ShortDeque> : No
out: can_IterForEach<CustomCont> : No
out: can_IterForEach<ForwardRangeIter> : Yes
out: can_IterForEach<TransformedForwardIter> : Yes
out: can_IterForEach<FilteredForwardIter> : Yes
out: can_IterForEach<CustomForwardIter> : Yes
return: 0
END

View file

@ -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 <string>
@ -47,9 +48,9 @@ namespace test{
string
contents (Rec const& object)
{
return util::join (transformIterator (object.scope()
,[](GenNode const& n) { return n.data.get<string>(); }
));
return util::join (transformIter (object.scope()
,[](GenNode const& n) { return n.data.get<string>(); }
));
}
string

View file

@ -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), "

View file

@ -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;
}

View file

@ -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<int64_t> 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<uint>)
.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<Tu2>();
return val? singletonIter(Tu2{val-1, sum+val})
: nilIter<Tu2>();
};
CHECK (materialise(

View file

@ -310,7 +310,7 @@ namespace test{
template<class MAP>
void
verify_MultimapIters() ///< @see IterTools_test#verify_filterRepetitions
verify_MultimapIters() ///< @see IterTools_test#verify_filterRepeat
{
MAP testMap;
for (uint i=0; i<NUM_ELMS; ++i)

View file

@ -2,7 +2,7 @@
IterTools(Test) - building combined and filtering iterators based on the Iterator tools
Copyright (C)
2009, Hermann Vosseler <Ichthyostega@web.de>
2009,2026 Hermann Vosseler <Ichthyostega@web.de>
  **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 <boost/lexical_cast.hpp>
#include <vector>
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<VecI> (NUM_ELMS);
inline auto
iterNum()
{
vector<int> data_;
TestSource(uint num)
{
while (num)
data_.push_back(num--);
}
typedef vector<int>::iterator sourceIter;
typedef RangeIter<sourceIter> 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<class IT>
/** @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<Iter> all (ii, takeAll);
FilterIter<Iter> 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<uint64_t> Src;
typedef Src::iterator SrcIter;
typedef RangeIter<SrcIter> SeqIter;
typedef ExtensibleFilterIter<SeqIter> 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<uint> numberz;
for (uint i=0; i<NUM_ELMS; ++i)
{
uint n = 1 + rani (100);
do numberz.push_back(i);
while (--n);
}
CHECK (NUM_ELMS < numberz.size(), "no repetition in test data??");
typedef vector<uint>::iterator SrcIter;
typedef RangeIter<SrcIter> SeqIter;
typedef FilterIter<SeqIter> FilteredSeq;
SeqIter completeSequence (numberz.begin(), numberz.end());
FilteredSeq filtered = filterRepetitions (completeSequence);
uint num=0;
for (; num<NUM_ELMS and not isnil(filtered);
++num,
++filtered
)
CHECK (num == *filtered);
CHECK (num == NUM_ELMS and isnil(filtered));
}
/** @test wrap an arbitrary single element as pseudo-iterator */
void
buildWrappedSingleElement()
{
uint i{12};
auto i1 = singleValIterator(12);
auto i2 = singleValIterator( i);
auto i3 = singleValIterator(&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..
i1 = singleValIterator(13);
CHECK (13 == *i1);
i1 = SingleValIter<int>{};
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<Iter, int> idi (ii, idFunc);
TransformIter<Iter, int> 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<class IT>
@ -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<int>();
i0 = singletonIter (13);
CHECK (13 == *i0);
CHECK (isnil (i1));
std::swap (i0,i1);
CHECK (isnil (i0));
CHECK (13 == *i1);
i1 = iter::Single<int>{};
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<uint> numberz;
for (uint i=0; i<NUM_ELMS; ++i)
{
uint n = 1 + rani (100);
do numberz.push_back(i);
while (--n);
}
CHECK (NUM_ELMS < numberz.size(), "no repetition in test data??");
auto completeSequence = iter_stl::eachElm (numberz);
auto filtered = filterRepeat (completeSequence);
uint num=0;
for (; num < NUM_ELMS and not isnil(filtered);
++num,
++filtered
)
CHECK (num == *filtered);
CHECK (num == NUM_ELMS and isnil(filtered));
}
static uint addTwo (int i) { return i+2; }
static int negate (int i) { return -i; }
static int idFunc (int i) { return i; }
/** @test build a pipeline that maps a function onto the iterator results */
void
verify_transformIter()
{
Iter ii = iterNum();
CHECK (join (transformIter (ii, idFunc)) == "0, 1, 2, 3, 4, 5, 6, 7, 8, 9"_expect );
CHECK (join (transformIter (ii, negate)) == "0, -1, -2, -3, -4, -5, -6, -7, -8, -9"_expect );
CHECK (join (transformIter (ii, addTwo)) == "2, 3, 4, 5, 6, 7, 8, 9, 10, 11"_expect );
// note: output type changed to unsigned
using TransIT = decltype(transformIter (ii, addTwo));
CHECK (showType<TransIT::value_type>() == "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

View file

@ -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 <iostream>
@ -33,51 +34,60 @@
#include <set>
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<int> data_;
TestSource(uint num);
typedef vector<int>::iterator sourceIter;
typedef RangeIter<sourceIter> iterator;
using RawIter = vector<int>::iterator;
using iterator = RangeIter<RawIter>;
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<long> LongVector;
typedef std::multiset<Time> TimeSet;
typedef std::map<int,char> CharMap;
typedef std::list<bool> BoolList;
typedef std::deque<ushort> ShortDeque;
typedef TestSource CustomCont;
using TimeVector = std::vector<TimeVar>;
using LongSet = std::multiset<long> ;
using CharMap = std::map<int,char> ;
using BoolList = std::list<bool> ;
using ShortDeque = std::deque<ushort> ;
using CustomCont = TestSource ;
// some types in compliance to the "Lumiera Forward Iterator" concept
typedef TestSource::iterator ForwardRangeIter;
typedef TransformIter<ForwardRangeIter, long> TransformedForwardIter;
typedef FilterIter<TransformedForwardIter> FilteredForwardIter;
typedef ScopeQuery<Effect>::iterator CustomForwardIter;
// some types that comply with the "Lumiera Forward Iterator" concept
using LumieraRangeIter = TestSource::iterator;
using LumieraFilteredIter = decltype( filteredSource (declval<TestSource&>()) );
using LumieraTransformedIter = decltype( transformedSource (declval<TestSource&>()) );
using CustomLumieraIter = diff::Record<string>::iterator;
// some types that comply with the "State Core" concept
using CustomStateCore = DummyCore;
using LumieraCoreIter = IterableDecorator<DummyCore>;
// detect STL iteration
SHOW_CHECK( can_STL_ForEach<LongVector> );
SHOW_CHECK( can_STL_ForEach<TimeSet> );
SHOW_CHECK( can_STL_ForEach<CharMap> );
SHOW_CHECK( can_STL_ForEach<BoolList> );
SHOW_CHECK( can_STL_ForEach<ShortDeque> );
SHOW_CHECK( can_STL_ForEach<CustomCont> );
CHECK ( true == can_STL_ForEach<TimeVector>::value );
CHECK ( true == can_STL_ForEach<LongSet> ::value );
CHECK ( true == can_STL_ForEach<CharMap> ::value );
CHECK ( true == can_STL_ForEach<BoolList> ::value );
CHECK ( true == can_STL_ForEach<ShortDeque>::value );
CHECK ( true == can_STL_ForEach<CustomCont>::value );
SHOW_CHECK( can_STL_ForEach<ForwardRangeIter> );
SHOW_CHECK( can_STL_ForEach<TransformedForwardIter> );
SHOW_CHECK( can_STL_ForEach<FilteredForwardIter> );
SHOW_CHECK( can_STL_ForEach<CustomForwardIter> );
CHECK (false == can_STL_ForEach<LumieraRangeIter> ::value );
CHECK (false == can_STL_ForEach<LumieraFilteredIter> ::value );
CHECK (false == can_STL_ForEach<LumieraTransformedIter>::value );
CHECK (false == can_STL_ForEach<CustomLumieraIter> ::value );
CHECK (false == can_STL_ForEach<CustomStateCore>::value );
CHECK (false == can_STL_ForEach<LumieraCoreIter>::value );
// detect Lumiera Forward Iterator
SHOW_CHECK( can_IterForEach<LongVector> );
SHOW_CHECK( can_IterForEach<TimeSet> );
SHOW_CHECK( can_IterForEach<CharMap> );
SHOW_CHECK( can_IterForEach<BoolList> );
SHOW_CHECK( can_IterForEach<ShortDeque> );
SHOW_CHECK( can_IterForEach<CustomCont> );
CHECK (false == can_IterForEach<TimeVector>::value );
CHECK (false == can_IterForEach<LongSet> ::value );
CHECK (false == can_IterForEach<CharMap> ::value );
CHECK (false == can_IterForEach<BoolList> ::value );
CHECK (false == can_IterForEach<ShortDeque>::value );
CHECK (false == can_IterForEach<CustomCont>::value );
SHOW_CHECK( can_IterForEach<ForwardRangeIter> );
SHOW_CHECK( can_IterForEach<TransformedForwardIter> );
SHOW_CHECK( can_IterForEach<FilteredForwardIter> );
SHOW_CHECK( can_IterForEach<CustomForwardIter> );
CHECK ( true == can_IterForEach<LumieraRangeIter> ::value );
CHECK ( true == can_IterForEach<LumieraFilteredIter> ::value );
CHECK ( true == can_IterForEach<LumieraTransformedIter>::value );
CHECK ( true == can_IterForEach<CustomLumieraIter> ::value );
CHECK (false == can_IterForEach<CustomStateCore>::value );
CHECK ( true == can_IterForEach<LumieraCoreIter>::value ); // Note: Lumiera iterator capability added as wrapper
// detect State Core
CHECK (false == is_StateCore<TimeVector>::value );
CHECK (false == is_StateCore<LongSet> ::value );
CHECK (false == is_StateCore<CharMap> ::value );
CHECK (false == is_StateCore<BoolList> ::value );
CHECK (false == is_StateCore<ShortDeque>::value );
CHECK (false == is_StateCore<CustomCont>::value );
CHECK (false == is_StateCore<LumieraRangeIter> ::value );
CHECK (false == is_StateCore<LumieraFilteredIter> ::value );
CHECK (false == is_StateCore<LumieraTransformedIter>::value );
CHECK (false == is_StateCore<CustomLumieraIter> ::value );
CHECK ( true == is_StateCore<CustomStateCore>::value );
CHECK ( true == is_StateCore<LumieraCoreIter>::value );
}
};

View file

@ -219,8 +219,8 @@ namespace interact {
{
return Symbol{id};
};
return depth==UIC_PERSP? buildNavigator (node, depth, singleValIterator (internedString (node.getType())))
: buildNavigator (node, depth, transformIterator (node.keys(), internedString));
return depth==UIC_PERSP? buildNavigator (node, depth, singletonIter (internedString (node.getType())))
: buildNavigator (node, depth, transformIter (node.keys(), internedString));
}

View file

@ -57,7 +57,7 @@ using std::string;
using lib::Symbol;
using lib::append_all;
using lib::transformIterator;
using lib::transformIter;
using lib::diff::Rec;
using lib::diff::GenNode;
using lib::diff::DataCap;
@ -487,8 +487,8 @@ namespace test{
{
EventLog::ArgSeq strings;
strings.reserve (argData.childSize());
append_all (transformIterator (childData (argData.scope())
, util::toString<DataCap>)
append_all (transformIter (childData (argData.scope())
, util::toString<DataCap>)
,strings);
return strings;
}

View file

@ -34,7 +34,7 @@ namespace test {
using ::test::randTime;
using steam::fixture::Segment;
using lib::singleValIterator;
using lib::singletonIter;
using util::isSameObject;
using util::seqTuple;
@ -351,7 +351,7 @@ namespace test {
.genNode())
.genNode()};
auto start = singleValIterator (mockSegs[Time::ZERO].jobTicket(0));
auto start = singletonIter (mockSegs[Time::ZERO].jobTicket(0));
auto it = lib::explore(start)
.expand ([](JobTicket& ticket)

File diff suppressed because it is too large Load diff