finish integration test and TreeMutator binding (#992)
This implementation draft is now roughly complete
This commit is contained in:
parent
2814276387
commit
ffd40d86e7
4 changed files with 100 additions and 25 deletions
|
|
@ -133,7 +133,7 @@ namespace diff{
|
|||
*
|
||||
* In the extended configuration for tree-diff-application to given opaque target
|
||||
* data, the setup uses the [metaprogramming adapter traits](\ref TreeDiffTraits)
|
||||
* to pave a way for building the custom TreeMutator implementation internally wired
|
||||
* to pave a way for building the custom TreeMutator implementation, wired internally
|
||||
* to the given opaque target. Moreover, based on the concrete target type, a suitable
|
||||
* ScopeManager implementation can be provided. Together, these two dynamically created
|
||||
* adapters allow the generic TreeDiffMutatorBinding to perform all of the actual
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ namespace diff{
|
|||
ScopeManager::~ScopeManager() { }; ///< emit VTable here...
|
||||
|
||||
|
||||
|
||||
/* ======= Implementation of Tree Diff Application via TreeMutator ======= */
|
||||
|
||||
using util::unConst;
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@
|
|||
** Unfortunately this leads to yet another indirection layer: Implementing a
|
||||
** 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 be generated
|
||||
** the implementation side from a concrete data structure. Which means, that the user
|
||||
** 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_ working on local data.
|
||||
**
|
||||
|
|
@ -56,14 +56,27 @@
|
|||
** interpretation and the concrete yet undisclosed private data structure, and
|
||||
** most of this implementation is entirely generic, since the specifics are
|
||||
** abstracted away behind the TreeMutator interface. For this reason, most of
|
||||
** this explicit template specialisation code, especially. the virtual functions,
|
||||
** can be emitted right here, within the library module. This helps to reduce
|
||||
** "template bloat" and simplifies the dynamic linking. Thus, this header
|
||||
** only contains the definition and the ctor code, which indeed needs to
|
||||
** be adapted to each usage situation, while the main body of the
|
||||
** functionality has been moved to the corresponding implementation
|
||||
** file, where this template is explicitly instantiated, to force
|
||||
** code generation into the library module.
|
||||
** this _delegating implementation_ can be emitted right here, within the
|
||||
** library module. With the sole exception of the ctor, which needs to
|
||||
** figure out a way how to "get" a suitable TreeMutator implementation
|
||||
** for the given concrete target data.
|
||||
**
|
||||
** ### the TreeMutator DSL
|
||||
** In the end, this concrete TreeMutator needs to be built or provided within
|
||||
** the realm of the actual data implementation anyway, so the knowledge about the
|
||||
** actual data layout remains confined there. Unfortunately, implementing a TreeMutator
|
||||
** is quite an involved and technical task, requiring intimate knowledge of structure
|
||||
** and semantics of the diff language. On a second thought, it turns out that most
|
||||
** data implementation will rely on some very common representation techniques,
|
||||
** like using object fields as "attributes" and a STL collection to hold the
|
||||
** "children". Based on this insight, it is possible to provide standard adapters
|
||||
** and building blocks, in the form of an DSL, to generate the actual TreeMutator.
|
||||
** The usage site thus needs to supply only some lambda expressions to specify
|
||||
** how to deal with the representation data values
|
||||
** - how to construct a new entity
|
||||
** - when the binding actually becomes relevant
|
||||
** - how to determine a diff verb "matches" the actual data
|
||||
** - how to set a value or how to recurse into a sub scope
|
||||
**
|
||||
** @todo this is WIP as of 2/2016 -- in the end it might be merged back or even
|
||||
** replace the tree-diff-application.hpp
|
||||
|
|
@ -217,7 +230,7 @@ namespace diff{
|
|||
|
||||
private:
|
||||
|
||||
/* == Forwarding: error handling == */
|
||||
/* == error handling helpers == */
|
||||
|
||||
void __failMismatch (Literal oper, GenNode const& spec);
|
||||
void __expect_further_elements (GenNode const& elm);
|
||||
|
|
|
|||
|
|
@ -27,26 +27,23 @@
|
|||
#include "lib/diff/test-mutation-target.hpp"
|
||||
#include "lib/iter-adapter-stl.hpp"
|
||||
#include "lib/time/timevalue.hpp"
|
||||
#include "lib/format-cout.hpp" //////////TODO necessary?
|
||||
#include "lib/format-string.hpp"
|
||||
//#include "lib/format-util.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
using lib::iter_stl::snapshot;
|
||||
using util::isnil;
|
||||
using util::join;
|
||||
using util::_Fmt;
|
||||
using util::join;
|
||||
using util::BOTTOM_INDICATOR;
|
||||
using lib::iter_stl::snapshot;
|
||||
using lib::time::Time;
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using lib::time::Time;
|
||||
using util::BOTTOM_INDICATOR;
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
|
@ -131,6 +128,14 @@ namespace test{
|
|||
return *this;
|
||||
}
|
||||
|
||||
bool verifyType(string x) const { return x == type_; }
|
||||
bool verifyAlpha(int x) const { return x == alpha_;}
|
||||
bool verifyBeta(int64_t x) const { return x == beta_; }
|
||||
bool verifyGamma(double x) const { return x == gamma_;}
|
||||
bool verifyData(string desc) const { return desc == join(nestedData_); }
|
||||
const Opaque* nestedDelta() const { return not delta_? NULL : delta_.get(); }
|
||||
const Opaque* nestedObj_1() const { return isnil(nestedObj_)? NULL : &nestedObj_[0]; }
|
||||
|
||||
|
||||
operator string() const
|
||||
{
|
||||
|
|
@ -160,6 +165,28 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** the _only way_ this opaque object exposes itself for mutation through diff messages.
|
||||
* This function builds a TreeMutator implementation into the given buffer space
|
||||
* @note some crucial details for this binding to work properly...
|
||||
* - we define several "onion layers" of binding to deal with various scopes.
|
||||
* - the priority of these bindings is ordered from lowest to highest
|
||||
* - actually this is a quite complicated setup, including object fields
|
||||
* to represent attributes, where one special attribute which actually holds
|
||||
* a nested object, then both a collection of child objects and a collection
|
||||
* of data values
|
||||
* - the selector predicate (`isApplicableIf`) actually decides if a binding layer
|
||||
* becomes responsible for a given diff verb. Here, this decision is based on
|
||||
* the classification of the verb or spec to be handled, either being an
|
||||
* attribute (named, key-value pair), a nested sub-scope ("object") and
|
||||
* finally just any unnamed (non attribute) value
|
||||
* - the recursive mutation of nested scopes is simply initiated by invoking
|
||||
* the same Opaque::buildMutator on the respective children recursively.
|
||||
* - such an unusually complicated TreeMutator binding leads to increased
|
||||
* buffer space requirements for the actual TreeMutator to be generated;
|
||||
* Thus we need to implement the _extension point_ treeMutatorSize()
|
||||
* to supply a sufficient buffer size value. This function is
|
||||
* picked up through ADL, based on the target type `Opaque`
|
||||
*/
|
||||
void
|
||||
buildMutator (TreeMutator::Handle buff)
|
||||
{
|
||||
|
|
@ -330,30 +357,64 @@ namespace test{
|
|||
run (Arg)
|
||||
{
|
||||
Opaque subject;
|
||||
// auto target = mutatorBinding (subject);
|
||||
// DiffApplicator<DiffMutable> application(target);
|
||||
DiffApplicator<Opaque> application(subject);
|
||||
//
|
||||
// TODO verify results
|
||||
cout << "before..."<<endl << subject<<endl;
|
||||
CHECK (subject.verifyAlpha(-1));
|
||||
CHECK (subject.verifyBeta(-1));
|
||||
CHECK (subject.verifyGamma(-1));
|
||||
CHECK (not subject.nestedDelta());
|
||||
CHECK (not subject.nestedObj_1());
|
||||
CHECK (subject.verifyData(""));
|
||||
|
||||
|
||||
// Part I : apply attribute changes
|
||||
application.consume(populationDiff());
|
||||
//
|
||||
// TODO verify results
|
||||
cout << "after...I"<<endl << subject<<endl;
|
||||
// ==> ATTRIB1, ATTRIB3, (ATTRIB3), CHILD_B, CHILD_B, CHILD_T
|
||||
CHECK (subject.verifyAlpha(1));
|
||||
CHECK (subject.verifyGamma(ATTRIB3.data.get<double>()));
|
||||
CHECK (subject.verifyData("b, b, 78:56:34.012"));
|
||||
// unchanged...
|
||||
CHECK (subject.verifyBeta(-1));
|
||||
CHECK (not subject.nestedDelta());
|
||||
CHECK (not subject.nestedObj_1());
|
||||
|
||||
|
||||
// Part II : apply child population
|
||||
application.consume(reorderingDiff());
|
||||
//
|
||||
// TODO verify results
|
||||
cout << "after...II"<<endl << subject<<endl;
|
||||
// ==> ATTRIB1, ATTRIB3, (ATTRIB3), ATTRIB2, SUB_NODE, CHILD_T, CHILD_B
|
||||
CHECK (subject.verifyAlpha(1));
|
||||
CHECK (subject.verifyBeta (2)); // attribute β has been set
|
||||
CHECK (subject.verifyGamma(3.45));
|
||||
CHECK (subject.verifyData("78:56:34.012, b")); // one child deleted, the other ones re-ordered
|
||||
CHECK (subject.nestedObj_1()); // plus inserted a nested child object
|
||||
CHECK (subject.nestedObj_1()->verifyType(Rec::TYPE_NIL));
|
||||
CHECK (subject.nestedObj_1()->verifyBeta(-1)); // ...which is empty (default constructed)
|
||||
CHECK (subject.nestedObj_1()->verifyData(""));
|
||||
|
||||
|
||||
// Part III : apply child mutations
|
||||
application.consume(mutationDiff());
|
||||
//
|
||||
// TODO verify results
|
||||
cout << "after...III"<<endl << subject<<endl;
|
||||
// ==> ATTRIB1, ATTRIB3 := π, (ATTRIB3), ATTRIB2,
|
||||
// ATTRIB_NODE{ type ζ, CHILD_A, CHILD_A, CHILD_A }
|
||||
// SUB_NODE{ type ξ, ATTRIB2, CHILD_B, CHILD_A },
|
||||
// CHILD_T, CHILD_B
|
||||
CHECK (subject.verifyAlpha(1));
|
||||
CHECK (subject.verifyBeta (2));
|
||||
CHECK (subject.verifyGamma(GAMMA_PI.data.get<double>())); // new value assigned to attribute γ
|
||||
CHECK (subject.nestedDelta()); // attribute δ (object valued) is now present
|
||||
CHECK (subject.nestedDelta()->verifyType("ζ")); // ...and has an explicitly defined type field
|
||||
CHECK (subject.nestedDelta()->verifyData("a, a, a"));//...plus three similar child values
|
||||
CHECK (subject.verifyData("78:56:34.012, b")); // the child values weren't altered
|
||||
CHECK (subject.nestedObj_1()->verifyType("ξ")); // but the nested child object's type has been set
|
||||
CHECK (subject.nestedObj_1()->verifyBeta(2)); // ...and the attribute β has been set on the nested object
|
||||
CHECK (subject.nestedObj_1()->verifyData("b, a")); // ...plus some child values where added here
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue