round-up and document the attribute binding and test

This commit is contained in:
Fischlurch 2016-06-09 01:10:52 +02:00
parent b5ab5df929
commit ef27c09fa2
5 changed files with 104 additions and 52 deletions

View file

@ -23,7 +23,7 @@
/** @file tree-diff-mutator-binding.hpp
** Concrete implementation to apply structural changes to unspecific
** private datea structures with hierarchical nature. This is a variation
** private data structures with hierarchical nature. This is a variation
** of the generic [tree diff applicator](\ref tree-diff-application.hpp),
** using the same implementation concept, while relying on an abstract
** adapter type, the \ref TreeMutator. Similar to the generic case, when
@ -47,9 +47,9 @@
** language in itself is necessarily a double dispatch (we have to abstract the
** verbs and we have to abstract the implementation side). And now we're decoupling
** the implementation side from a concrete data structure. Which means, that the
** use will have to provide a set of closures (which might even partially generated
** use will have to provide a set of closures (which might even partially be generated
** functors) to translate the _implementation actions_ underlying the language into
** _concrete actions_ on local data.
** _concrete actions_ working on local data.
**
** @todo this is WIP as of 2/2016 -- in the end it might be merged back or even
** replace the tree-diff-application.hpp
@ -87,7 +87,7 @@ namespace diff{
/**
* Interpreter for the tree-diff-language to work on arbitrary, undiclosed
* Interpreter for the tree-diff-language to work on arbitrary, undisclosed
* local data structures. The key point to note is that this local data is
* not required to implement any specific interface. The only requirement is
* the ability somehow to support the basic operations of applying a structural
@ -329,13 +329,13 @@ namespace diff{
void
assignElm (GenNode const& n)
{
UNIMPLEMENTED("locate allready accepted element and assign given new payload");
UNIMPLEMENTED("locate already accepted element and assign given new payload");
}
void
open_subScope (GenNode const& n)
{
UNIMPLEMENTED("locate allready accepted element and open recursive sub-scope for mutation");
UNIMPLEMENTED("locate already accepted element and open recursive sub-scope for mutation");
}
void
@ -397,7 +397,7 @@ namespace diff{
__fail_not_found (n);
}
/** assignement of changed value in one step */
/** assignment of changed value in one step */
virtual void
set (GenNode const& n) override
{

View file

@ -32,9 +32,58 @@
** adaptation is done by implementing binding templates, in the way of building
** blocks, attached and customised through lambdas. It is possible to layer
** several bindings on top of a single TreeMutator -- and this header defines
** a building block for one such layer, especially for binding to object fields
** through getter / setter lambdas.
**
** a building block for one specific kind of layer, used to bind object fields
** through "setter" lambdas.
**
** ## architecture considerations
** Together with the (\ref tree-mutator-collection-binding.hpp), the attribute binding
** is the most relevant building block -- yet it is special in several respects. There
** is kind of a "impedance mismatch" between the concept of an "attribute", as used in
** the context of diff messages and »External Tree Description«, and the nature of
** data fields as used within the imperative or object oriented implementation: the
** latter is rooted within a _class definition_ -- which is conceived as a _type_,
** a conceptual entity used for construction of code, yet not really embodied into
** the actual code at execution time. Thus, with respect to the _behaviour_ at execution,
** the structure defined through typing and classes appears as static backdrop. This leads
** to the consequence, that, on a generic (unspecific) level, we don't have any correlate
** to the notion of _ordering_ and _sequence_, as found within the diff language.
**
** On the other hand, this very notion of _ordering_ and _sequence_ is essential to the
** meaning of "diff", as far as collections of "children" are involved. This leaves us
** with the decision, either to increase complexity of the diff language's definition
** and concept, absorb this discrepancy within the complexity of implementation.
** Deliberately, the whole concept of a "diff language" builds onto the notion of
** _layered semantics,_ where the precise meaning of some terms remains a private
** extension within specific usage context. There is a lot of leeway within the
** language, and the _correct usage protocol_ is linked to the actual scope of
** usage. We need the diff language to be a connecting medium, to link some
** remote partners based on a locally shared common understanding of structure.
**
** And so we use the same approach when it comes to "attributes": We'll assume that
** the partners connected through diff messages are _structurally compatible_ -- thus
** any "change" message emitted at one side is assumed to basically make sense on the
** receiving side. Consequently, the binding of an "attribute" to an object or data field
** will either ignore or reject any specifics about field order. It will _reject_ an
** explicit demand to re-order a field, and it will silently pass down other notions
** related to ordering -- down to lower "onion layers" of the concrete binding. So
** it depends on the concrete setup of the data binding (TreeMutator), if some
** expression in diff language will be deemed incompatible -- which happens when
** in the end no "onion layer" of the concrete binding was able to absorb and
** comply to the diff message.
**
** Another architectural consideration is relevant to the way attribute bindings are
** constructed: we rather construct a separate binding for each individual attribute,
** instead of building a collective binding for all attributes of a given object.
** This gives us the benefit of a simple and flexible solution plus it avoids the
** overhead of managing a _collection of attribute definitions_ (which would likely
** cause a heap allocation for storage). The downside is that we loose any coherence
** between attributes of "the same object", we loose possible consistency checks and
** we get a linear search for access to any attribute binding. Moreover, we cannot
** protect against creation of a nonsensical binding, e.g. a binding which ties
** the same attribute several times in contradictory fashion. The client code
** constructing the concrete TreeMutator needs to have adequate understanding
** regarding mode of operation and "mechanics" of such a binding.
**
** @note the header tree-mutator-attribute-binding.hpp with specific builder templates
** is included way down, after the class definitions. This is done so for sake
** of readability.
@ -96,7 +145,7 @@
virtual bool
hasSrc() override
{
return true;
return true; // x or true == true
}
/** ensure the given spec is deemed appropriate at that point.
@ -203,7 +252,7 @@
return true;
}
/** invoke the setter lambda, in case this binding layer is in charge */
/** invoke the setter lambda, when this binding layer is in charge */
virtual bool
assignElm (GenNode const& spec) override
{

View file

@ -70,7 +70,7 @@
** or reshape _all of the target's contents_. After that, you must not refer to the
** exhausted TreeMutator anymore, just let it fall out of scope. Incidentally, this
** also means that _any failure or exception encountered_ while applying a diff will
** **corrupt the target data structure**. The basic assumption is that
** leave a **corrupted target data structure**. The basic assumption is that
** - the target data structure will actually be built through diff messages solely
** - and that all received diff messages are sane, as being drawn from a
** semantically and structurally equivalent source structure
@ -82,6 +82,8 @@
** is defined in separate headers and included towards the bottom of this header.
**
** @see tree-mutator-test.cpp
** @see tree-mutator-binding-test.cpp
** @see diff-language.hpp
** @see DiffDetector
**
*/

View file

@ -33,9 +33,8 @@
#include "lib/error.hpp"
#include "lib/util.hpp"
//#include <utility>
#include <vector>
#include <string>
//#include <vector>
using util::join;
using util::isnil;
@ -45,9 +44,6 @@ using lib::iter_stl::eachElm;
using lib::time::Time;
using std::string;
//using std::vector;
//using std::swap;
using util::typeStr;
@ -91,7 +87,7 @@ namespace test{
* - use a dummy diagnostic implementation to verify the interface
* - verify an adapter to apply structure modification to a generic collection
* - use closures to translate mutation into manipulation of private attributes
* - integrate the standard case of tree diff application to `Rec<GenNode>`
* @todo - integrate the standard case of tree diff application to `Rec<GenNode>`
*
* @remark even while this is a very long and detail oriented test, it barely
* scratches the surface of what is possible with _layering multiple bindings_
@ -107,7 +103,7 @@ namespace test{
* diff application to an arbitrary hierarchical data structure. In this way, the
* following test cases demonstrate the intermediary steps executed when applying
* this test diff through the concrete binding exemplified in each case
* @remark the **test diff** referred here reads as follows
* @remark the **test diff** implied here reads as follows
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ins(ATTRIB1)
* ins(ATTRIB3)
@ -658,7 +654,14 @@ namespace test{
/** @test translate generic mutation into attribute manipulation */
/** @test translate generic mutation into attribute manipulation
* - here we bind directly to data fields local to this scope
* - we execute the same diff primitives used in the preceding tests
* - yet binding to data fields has certain intrinsic limits; due to the
* fixed non-dynamic nature of data fields, it is impossible to define an
* "ordering" and consequently there is no _sequence of diff application._
* - so the only form of actually _applying_ a change is to invoke the given
* setter or use the given mechanism to construct a nested mutator. */
void
mutateAttribute ()
{
@ -690,14 +693,10 @@ namespace test{
gamma = val;
});
#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
CHECK (sizeof(mutator1) <= sizeof(void*) // the TreeMutator VTable
+ 2 * sizeof(void*) // one closure reference for each lambda
+ 2 * sizeof(GenNode::ID)); // one attribute-key for each binding
// --- first round: introduce new "attributes" ---
@ -706,8 +705,8 @@ namespace test{
CHECK (-1 == beta);
CHECK (-1 == gamma);
CHECK (mutator1.hasSrc()); // NOTE: the attribute binding always has an implicit "source sequence"
// (which is in fact fixed, because it relies on a likewise fixed class definition)
CHECK (mutator1.hasSrc()); // NOTE: the attribute binding has no "reference source sequence" and thus no dynamic state.
// (in fact it is predetermined, because it relies on a likewise fixed class definition)
CHECK (mutator1.completeScope()); // NOTE: this is always true and NOP, for the same reason: the structure of the binding is fixed
mutator1.injectNew (ATTRIB1);
@ -728,7 +727,7 @@ namespace test{
CHECK (not mutator1.injectNew (ATTRIB2)); // ...because we didn't define a binding for ATTRIB2 (aka "beta")
// any changes to something other than attributes are just delegated to the next "onion layer"
// since in this case here, there is only one layer (our attribute binding), these other changes will be silently ignored
// since in this case here, there is only one layer (our attribute binding), these other changes will be ignored silently
mutator1.injectNew (CHILD_B);
mutator1.injectNew (CHILD_B);
mutator1.injectNew (CHILD_T);
@ -740,6 +739,7 @@ namespace test{
cout << "successfully 'injected' new attributes." <<endl;
// --- second round: reordering ---
@ -765,18 +765,18 @@ namespace test{
gamma = val;
});
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
// we have two lambdas now and thus can save on the size of one function pointer....
CHECK (sizeof(mutator1) - sizeof(mutator2) == sizeof(void*));
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
CHECK (sizeof(mutator1) <= sizeof(void*) // the TreeMutator VTable
+ 3 * sizeof(void*) // one closure reference for each lambda
+ 3 * sizeof(GenNode::ID)); // one attribute-key for each binding
CHECK ( 1 == alpha);
CHECK (-1 == beta);
CHECK (3.45 == gamma); // values not affected by attaching a new mutator
CHECK (mutator2.matchSrc (ATTRIB1)); // current head element of src "matches" the given spec
CHECK ( 1 == alpha); // the match didn't change anything...
CHECK (mutator2.matchSrc (ATTRIB1)); // this "match" is positive, since our binding supports this attribute
CHECK ( 1 == alpha); // the (NOP) match didn't change anything...
CHECK (-1 == beta);
CHECK (3.45 == gamma);
@ -785,8 +785,8 @@ namespace test{
// If we hadn't defined a binding for "γ", then the same operation
// would have been passed on silently to other binding layers.
CHECK (mutator2.matchSrc (ATTRIB1)); // element at head of src is still ATTRIB1 (as before)
CHECK (mutator2.acceptSrc (ATTRIB1)); // now pick and accept this src element (also a NOP) // acceptSrc
CHECK (mutator2.matchSrc (ATTRIB1)); // behaviour of the binding remains unaffected
CHECK (mutator2.acceptSrc (ATTRIB1)); // now pick and "accept" this src element (also a NOP) // acceptSrc
mutator2.skipSrc(); // and 'skip' likewise is just not implemented for attributes // skipSrc
CHECK ( 1 == alpha);
@ -817,7 +817,7 @@ namespace test{
CHECK ( 2 == beta);
CHECK (3.45 == gamma); // no further effect on our attribute fields
cout << "all 'reordering' operations ignored as expected..." <<endl;
cout << "ignored all 'reordering' operations (as expected)..." <<endl;
@ -825,8 +825,8 @@ namespace test{
// This third part of the test covers the actual purpose of attribute binding:
// the ability to assign values or even to open a sub-scope to recurse into
// a nested object stored within a data field.
// the ability to assign values or even to open a sub-scope enabling recursion
// into a nested object stored within a data field.
auto mutator3 =
TreeMutator::build()
.change("γ", [&](double val)
@ -848,10 +848,10 @@ namespace test{
<< join(delta.getLog(), "\n") <<endl;
});
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
// we have two lambdas now and thus can save on the size of one function pointer....
CHECK (sizeof(mutator1) - sizeof(mutator2) == sizeof(void*));
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
CHECK (sizeof(mutator1) <= sizeof(void*) // the TreeMutator VTable
+ 2 * sizeof(void*) // one closure reference for each lambda
+ 2 * sizeof(GenNode::ID)); // one attribute-key for each binding
VERIFY_ERROR (LOGIC, mutator3.accept_until (ATTRIB3)); // rejected; no support for ordering // accept_until
@ -884,7 +884,7 @@ namespace test{
// It is implemented as `TestMutationTarget delta`, which allows us to verify a fully operational nested mutator.
const size_t BUFF_SIZ = sizeof(TreeMutator::build().attachDummy (delta));
// just use some suitable size, not the point in focus for this test
// use some suitable size here, not the point in focus for this test
InPlaceBuffer<TreeMutator, BUFF_SIZ> subMutatorBuffer;
TreeMutator::MutatorBuffer placementHandle(subMutatorBuffer);

View file

@ -8214,7 +8214,7 @@ On receiving the terms of this &quot;diff language&quot;, it is possible to gene
i.e. a ''unified diff'' or the ''predicate notation'' used above to describe the list diffing algorithm, just by accumulating changes.
</pre>
</div>
<div title="TreeMutator" creator="Ichthyostega" modifier="Ichthyostega" created="201503292115" modified="201606051455" tags="Model Concepts GuiPattern design draft" changecount="87">
<div title="TreeMutator" creator="Ichthyostega" modifier="Ichthyostega" created="201503292115" modified="201606082305" tags="Model Concepts GuiPattern design draft" changecount="91">
<pre>The TreeMutator is an intermediary to translate a generic structure pattern into heterogeneous local invocation sequences.
within the [[diff framework|TreeDiffModel]], this is a crucial joint, since here the abstract, generic, ~DOM-like ExternalTreeDescription meeds opaque, local and undisclosed data structures.
@ -8311,7 +8311,7 @@ builder interface, several ways of binding can be //layered on top of each other
each layer is responsible for a specific aspect of the binding. This is crucial to deal with &quot;objects&quot; comprised of a mixture of children, like e.g. the [[Pipe]]
attached to a clip, or the mixture of clips, effects and labels found within a [[Fork]] (&quot;track&quot;).
;the selector
:this is a lambda {{{isApplicableIf : bool(GenNode const&amp;)}}}, provided by each //onion layer//
:this is a lambda {{{isApplicableIf : bool(GenNode const&amp;)}}}, provided separately by the concrete //onion layer//
:* the selector is //optional// and only necessary when there is more than one binding layer
:* this predicate decides if a given //spec,// as taken from a //diff verb// to be applied, is of relevance for the given //onion layer//
:* if this onion layer is not concerned or affected by this message, it will be passed down to further layers, possibly ignoring it at the end
@ -8338,8 +8338,9 @@ attached to a clip, or the mixture of clips, effects and labels found within a [
:** the lambda is expected to //place// the generated mutator into the given buffer smart handle, which takes ownership of this object
;object field binding
:this maps the concept of &quot;Attribute&quot; onto concrete, mutable data fields
:any re-ordering, inserting and deleting of fields is ''prohibited'', while defaultable optional fields are tolerated
:there is only one common »binding layer« for object fields (i.e. we don't offer a customisable selector)
:any re-ordering and deletion of fields is ''prohibited'', while defaultable optional fields may be tolerated
:there is only one common combined binding for object fields (i.e. we don't offer a customisable selector),
:where each individual binding is implemented on its own, as a self contained, isolated binding layer
:binding is created ''alternatively'' through the following closures
:* ''change'': &quot;key&quot;, setter lambda {{{void(T val)}}}
:** binding for a regular setter to assign a new value