first part of unit-test for GenNode TreeMutator-binding PASS

needed to use a forward function declaration within the
lambda for recursive scope mutator building, since otherwise
everything is inline and thus the compilation fails when it
comes to deducing the auto return type of the builder.

Other than that, the whole mechanics seem to work out of the box!
This commit is contained in:
Fischlurch 2016-09-02 03:10:27 +02:00
parent e00d6c2a4c
commit 05768e4ac5
3 changed files with 304 additions and 11 deletions

View file

@ -545,6 +545,10 @@ namespace diff{
}
};
/** @internal forward declaration for recursive mutator builder call */
void buildNestedMutator(Rec& nestedScope, TreeMutator::Handle buff);
template<>
struct _DefaultBinding<GenNode>
{
@ -571,10 +575,7 @@ namespace diff{
if (target.idi == subID // require match on already existing child object
and target.data.isNested())
{
Rec& nestedScope = target.data.get<Rec>();
buff.create (
TreeMutator::build()
.attach (mutateInPlace (nestedScope)));
buildNestedMutator(target.data.get<Rec>(), buff);
return true;
}
else

View file

@ -92,14 +92,23 @@ namespace diff{
inline auto
Builder<PAR>::attach (Rec::Mutator& targetTree)
{
return this-> attach (collection (accessChildren(targetTree)))
.attach (collection (accessAttribs(targetTree)))
.isApplicableIf ([&](GenNode const& spec)
{
return spec.isNamed(); // »Selector« : treat key-value elements here
});
return this->attach (collection (accessChildren(targetTree)))
.attach (collection (accessAttribs(targetTree))
.isApplicableIf ([](GenNode const& spec) -> bool
{
return spec.isNamed(); // »Selector« : treat key-value elements here
}));
}
inline void
buildNestedMutator(Rec& nestedScope, TreeMutator::Handle buff)
{
buff.create (
TreeMutator::build()
.attach (mutateInPlace (nestedScope)));
}
}//(END)Mutator-Builder decorator components...

View file

@ -935,10 +935,293 @@ namespace test{
/** @test apply mutation primitives to a GenNode tree.
* - again we perform _literally_ the same diff steps as before
* - but we use the pre-configured binding for Record<GenNode>
* - internally this is comprised of two collection binding layers
* - we start with an empty root node, to be populated and transformed
*/
void
mutateGenNode()
{
TODO ("define how to fit GenNode tree mutation into the framework");
MARK_TEST_FUN;
// private target data be mutated
Rec::Mutator target;
// set up a GenNode binding to work on this root node...
auto mutator1 =
TreeMutator::build()
.attach (target);
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
CHECK (sizeof(mutator1) <= sizeof(VecD) // the buffer for pending elements
+ sizeof(VecD*) // the reference to the original collection
+ sizeof(void*) // the reference from the ChildCollectionMutator to the CollectionBinding
+ 2 * sizeof(VecD::iterator) // one Lumiera RangeIter (comprised of pos and end iterators)
+ 4 * sizeof(void*) // the four unused default configured binding functions
+ 1 * sizeof(void*)); // one back reference from the closure to this scope
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
// --- first round: populate the collection ---
CHECK (isnil (target));
CHECK (not mutator1.hasSrc());
mutator1.injectNew (ATTRIB1);
CHECK (!isnil (target));
CHECK (contains(renderRecord(target), "α = 1"));
mutator1.injectNew (ATTRIB3);
mutator1.injectNew (ATTRIB3);
mutator1.injectNew (CHILD_B);
mutator1.injectNew (CHILD_B);
mutator1.injectNew (CHILD_T);
CHECK (mutator1.completeScope());
Rec& root = target;
CHECK (!isnil (root)); // nonempty -- content has been added
CHECK (Rec::TYPE_NIL == root.getType()); // type field was not touched
CHECK (1 == root.get("α").data.get<int>()); // has gotten our int attribute "α"
CHECK (3.45 == root.get("γ").data.get<double>()); // ... and double attribute "γ"
auto scope = root.scope(); // look into the scope contents...
CHECK ( *scope == CHILD_B); // there we find is CHILD_B
CHECK (*++scope == CHILD_B); // followed by a second CHILD_B
CHECK (*++scope == CHILD_T); // and another one CHILD_T
cout << "injected......"<<renderRecord(target)<<endl;
// --- second round: reorder the collection ---
// Mutators are one-time disposable objects,
// thus we'll have to build a new one for the second round...
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
auto mutator2 =
TreeMutator::build()
.attach (collection(target)
.constructFrom ([&](GenNode const& spec) -> Data
{
cout << "constructor invoked on "<<spec<<endl;
return {spec.idi.getSym(), render(spec.data)};
})
.matchElement ([&](GenNode const& spec, Data const& elm)
{
cout << "match? "<<spec.idi.getSym()<<"=?="<<elm.key<<endl;
return spec.idi.getSym() == elm.key;
}));
// we have two lambdas now and thus can save on the size of one function pointer....
CHECK (sizeof(mutator1) - sizeof(mutator2) == sizeof(void*));
CHECK (isnil (target)); // the "visible" new content is still void
CHECK (mutator2.matchSrc (ATTRIB1)); // current head element of src "matches" the given spec
CHECK (isnil (target)); // the match didn't change anything
CHECK (not mutator2.accept_until(Ref::ATTRIBS));
// NOTE: collection values can be anything; thus this
// collection binding layer can not have any notion of
// "this is an attribute". It will just delegate to the
// next lower layer and thus finally return false
CHECK (mutator2.accept_until(ATTRIB3)); // ...but of course we can fast forward to dedicated values // accept_until
CHECK (!isnil (target)); // the fast forward did indeed accept some entries
CHECK (mutator2.acceptSrc(ATTRIB3)); // we have a duplicate in list, need to accept that as well // accept
CHECK (mutator2.hasSrc());
CHECK (mutator2.matchSrc (CHILD_B)); // ...now we're located behind the attributes, at first child
mutator2.injectNew (ATTRIB2); // injectNew
CHECK (mutator2.matchSrc (CHILD_B)); // first child waiting in src is CHILD_B
mutator2.skipSrc (CHILD_B); // ...which will be skipped (and thus discarded) // skipSrc
mutator2.injectNew (SUB_NODE); // inject a nested sub-structure (implementation defined) // injectNew
CHECK (mutator2.matchSrc (CHILD_B)); // yet another B-child is waiting
CHECK (not mutator2.findSrc (CHILD_A)); // unsuccessful find operation won't do anything
CHECK (mutator2.hasSrc());
CHECK (mutator2.matchSrc (CHILD_B)); // child B still waiting, unaffected
CHECK (not mutator2.acceptSrc (CHILD_T)); // refusing to accept/pick a non matching element
CHECK (mutator2.matchSrc (CHILD_B)); // child B still patiently waiting, unaffected
CHECK (mutator2.hasSrc());
CHECK (mutator2.findSrc (CHILD_T)); // search for an element further down into src... // findSrc
CHECK (mutator2.matchSrc (CHILD_B)); // element at head of src is still CHILD_B (as before)
CHECK (mutator2.acceptSrc (CHILD_B)); // now pick and accept this src element as child // acceptSrc
CHECK (mutator2.hasSrc()); // next we have to clean up waste
mutator2.skipSrc (CHILD_T); // left behind by the findSrc() operation // skipSrc
CHECK (not mutator2.hasSrc()); // source contents exhausted
CHECK (not mutator2.acceptSrc (CHILD_T)); // ...anything beyond is NOP
CHECK (mutator2.completeScope()); // no pending elements left, everything resolved
// verify reordered shape
contents = stringify(eachElm(target));
CHECK ("α1≻" == *contents);
++contents;
CHECK ("γ3.45≻" == *contents);
++contents;
CHECK ("γ3.45≻" == *contents);
++contents;
CHECK ("≺β2≻" == *contents);
++contents;
CHECK (contains(*contents, "Rec()≻"));
++contents;
CHECK (contains(*contents, "78:56:34.012≻"));
++contents;
CHECK (contains(*contents, "b≻"));
++contents;
CHECK (isnil (contents));
cout << "Content after reordering...."
<< join(target) <<endl;
// --- third round: mutate data and sub-scopes ---
// This time we build the Mutator bindings in a way to allow mutation
// For one, "mutation" means to assign a changed value to a simple node / attribute.
// And beyond that, mutation entails to open a nested scope and delve into that recursively.
// Here, as this is really just a test and demonstration, we implement those nested scopes aside
// managed within a map and keyed by the sub node's ID.
auto mutator3 =
TreeMutator::build()
.attach (collection(target)
.constructFrom ([&](GenNode const& spec) -> Data
{
cout << "constructor invoked on "<<spec<<endl;
return {spec.idi.getSym(), render(spec.data)};
})
.matchElement ([&](GenNode const& spec, Data const& elm) -> bool
{
cout << "match? "<<spec.idi.getSym()<<"=?="<<elm.key<<endl;
return spec.idi.getSym() == elm.key;
})
.assignElement ([&](Data& target, GenNode const& spec) -> bool
{
cout << "assign "<<target<<" <- "<<spec<<endl;
CHECK (target.key == spec.idi.getSym(), "assignment to target with wrong identity");
target.val = render(spec.data);
return true;
})
.buildChildMutator ([&](Data& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
{
// use our "inside knowledge" to get at the nested scope implementation
VecD& subScope = subScopes[subID];
buff.create (
TreeMutator::build()
.attach (collection(subScope)
.constructFrom ([&](GenNode const& spec) -> Data
{
cout << "SubScope| constructor invoked on "<<spec<<endl;
return {spec.idi.getSym(), render(spec.data)};
})));
// NOTE: mutation of sub scope has not happened yet
// we can only document the sub scope to be opened now
cout << "openSub("<<subID.getSym()<<") ⟻ "<<target<<endl;
target.val = "Rec(--"+subID.getSym()+"--)";
return true;
}));
CHECK (isnil (target));
CHECK (mutator3.matchSrc (ATTRIB1)); // new mutator starts out anew at the beginning
CHECK (mutator3.accept_until (CHILD_T)); // fast forward behind the second-last child (CHILD_T) // accept_until
CHECK (mutator3.matchSrc (CHILD_B)); // this /would/ be the next source element, but rather...
CHECK (not mutator3.completeScope()); // CHILD_B is still pending, not done yet...
CHECK (mutator3.accept_until (Ref::END)); // fast forward, since we do not want to re-order anything // accept_until
CHECK ( mutator3.completeScope()); // now any pending elements where default-resolved
CHECK (not contains(join(target), "γ3.1415927≻"));
CHECK (mutator3.assignElm(GAMMA_PI)); // ...we assign a new payload to the current element first // assignElm
CHECK ( contains(join(target), "γ3.1415927≻"));
CHECK ( mutator3.completeScope());
cout << "Content after assignment; "
<< join(target) <<endl;
// prepare for recursion into sub scope..
// Since this is a demonstration, we do not actually recurse into anything,
// rather we invoke the operations on a nested mutator right from here.
InPlaceBuffer<TreeMutator, sizeof(mutator1)> subMutatorBuffer;
TreeMutator::Handle placementHandle(subMutatorBuffer);
CHECK (mutator3.mutateChild (SUB_NODE, placementHandle)); // mutateChild
CHECK (isnil (subScopes[SUB_NODE.idi])); // ...this is where the nested mutator is expected to work on
CHECK (not subMutatorBuffer->hasSrc());
// now use the Mutator *interface* to talk to the nested mutator...
// This code might be confusing, because in fact we're playing two roles here!
// For one, above, in the definition of mutator3 and in the declaration of MapD subScopes,
// the test code represents what a private data structure and binding would do.
// But below we enact the TreeDiffAplicator, which *would* use the Mutator interface
// to talk to an otherwise opaque nested mutator implementation. Actually, here this
// nested opaque mutator is created on-the-fly, embedded within the .buildChildMutator(..lambda...)
// Incidentally, we "just happen to know" how large the buffer needs to be to hold that mutator,
// since this is a topic beyond the scope of this test. In real usage, the DiffApplicator cares
// to provide a stack of suitably sized buffers for the nested mutators.
subMutatorBuffer->injectNew (TYPE_X); // >> // injectNew
subMutatorBuffer->injectNew (ATTRIB2); // >> // injectNew
subMutatorBuffer->injectNew (CHILD_B); // >> // injectNew
subMutatorBuffer->injectNew (CHILD_A); // >> // injectNew
CHECK (not isnil (subScopes[SUB_NODE.idi])); // ...and "magically" these instructions happened to insert
cout << "Sub|" << join(subScopes[SUB_NODE.idi]) <<endl; // some new content into our implementation defined sub scope!
// verify contents of nested scope after mutation
contents = stringify(eachElm(subScopes[SUB_NODE.idi]));
CHECK ("≺typeξ≻" == *contents);
++contents;
CHECK ("≺β2≻" == *contents);
++contents;
CHECK (contains(*contents, "b≻"));
++contents;
CHECK (contains(*contents, "a≻"));
++contents;
CHECK (isnil (contents));
// now back to parent scope....
// ...add a new attribute and immediately recurse into it
mutator1.injectNew (ATTRIB_NODE);
CHECK (mutator3.mutateChild (ATTRIB_NODE, placementHandle)); // NOTE: we're just recycling the buffer. InPlaceHolder handles lifecycle properly
subMutatorBuffer->injectNew (TYPE_Z);
subMutatorBuffer->injectNew (CHILD_A);
subMutatorBuffer->injectNew (CHILD_A);
subMutatorBuffer->injectNew (CHILD_A);
CHECK (subMutatorBuffer->completeScope()); // no pending "open ends" left in sub-scope
CHECK (mutator3.completeScope()); // and likewise in the enclosing main scope
// and thus we've gotten a second nested scope, populated with new values
cout << "Sub|" << join(subScopes[ATTRIB_NODE.idi]) <<endl;
// verify contents of this second nested scope
contents = stringify(eachElm(subScopes[ATTRIB_NODE.idi]));
CHECK ("≺typeζ≻" == *contents);
++contents;
CHECK (contains(*contents, "a≻"));
++contents;
CHECK (contains(*contents, "a≻"));
++contents;
CHECK (contains(*contents, "a≻"));
++contents;
CHECK (isnil (contents));
// back to parent scope....
// verify the marker left by our "nested sub-scope lambda"
CHECK (contains (join(target), "Rec(--"+SUB_NODE.idi.getSym()+"--)"));
CHECK (contains (join(target), "Rec(--"+ATTRIB_NODE.idi.getSym()+"--)"));
cout << "Content after nested mutation; "
<< join(target) <<endl;
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
}
};