round-up and document the attribute binding and test
This commit is contained in:
parent
b5ab5df929
commit
ef27c09fa2
5 changed files with 104 additions and 52 deletions
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
**
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -8214,7 +8214,7 @@ On receiving the terms of this "diff language", 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 "objects" 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]] ("track").
|
||||
;the selector
|
||||
:this is a lambda {{{isApplicableIf : bool(GenNode const&)}}}, provided by each //onion layer//
|
||||
:this is a lambda {{{isApplicableIf : bool(GenNode const&)}}}, 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 "Attribute" 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'': "key", setter lambda {{{void(T val)}}}
|
||||
:** binding for a regular setter to assign a new value
|
||||
|
|
|
|||
Loading…
Reference in a new issue