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:
parent
e00d6c2a4c
commit
05768e4ac5
3 changed files with 304 additions and 11 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue