remove the now obsolete, dedicated first diff implementation

yay! this piece of code has served its purpose:
it was the blueprint to build a way better design and implementation,
which can now cover this "generic tree" case as a special case as well
This commit is contained in:
Fischlurch 2016-09-05 04:05:31 +02:00
parent 7a29e260e9
commit a066650eb7
2 changed files with 141 additions and 397 deletions

View file

@ -103,7 +103,6 @@
#include "lib/diff/tree-diff.hpp"
#include "lib/diff/tree-diff-traits.hpp"
#include "lib/diff/tree-diff-mutator-binding.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/format-string.hpp"
@ -124,375 +123,8 @@ namespace diff{
/**
* Interpreter for the tree-diff-language to work on arbitrary
* opaque target data structures. A concrete strategy to apply a structural diff
* to otherwise undisclosed, recursive, tree-like target data. The only requirement
* is for this target structure to expose a hook for building a customised
* TreeMutator able to work on and transform the private target data.
*
* 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, 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
* diff application and mutation task.
*
* @throws lumiera::error::State when diff application fails due to the
* target sequence being different than assumed by the given diff.
* @see DiffComplexApplication_test usage example of this combined machinery
* @see #TreeDiffInterpreter explanation of the verbs
*/
template<class TAR>
class DiffApplicationStrategy<TAR, enable_if<TreeDiffTraits<TAR>>>
: public TreeDiffMutatorBinding
{
using Scopes = StackScopeManager<TreeMutatorSizeTraits<TAR>::siz>;
TAR& subject_;
Scopes scopes_;
TreeMutator*
buildMutator (DiffMutable& targetBinding)
{
scopes_.clear();
TreeMutator::Handle buffHandle = scopes_.openScope();
targetBinding.buildMutator (buffHandle);
return buffHandle.get();
}
public:
explicit
DiffApplicationStrategy(TAR& subject)
: TreeDiffMutatorBinding()
, subject_(subject)
, scopes_()
{ }
void
initDiffApplication()
{
auto target = mutatorBinding (subject_);
buildMutator (target);
TreeDiffMutatorBinding::scopeManger_ = &scopes_;
TreeDiffMutatorBinding::treeMutator_ = &scopes_.currentScope();
REQUIRE (this->treeMutator_);
}
};
/**
* Interpreter for the tree-diff-language to work on GenNode elements
* A concrete strategy to apply a structural diff to a target data structure
* made from #Record<GenNode> elements. This data structure is assumed to be
* recursive, tree-like. But because Record elements are conceived as immutable
* and value-like, the tree diff application actually works on a Rec::Mutator
* wrapping the target record to be altered through consuming the diff.
* @throws lumiera::error::State when diff application fails due to the
* target sequence being different than assumed by the given diff.
* @see #TreeDiffInterpreter explanation of the verbs
*/
template<>
class DiffApplicationStrategy<Rec::Mutator>
: public TreeDiffInterpreter
{
using Mutator = Rec::Mutator;
using Content = Rec::ContentMutator;
using Iter = Content::Iter;
struct ScopeFrame
{
Mutator& target;
Content content;
ScopeFrame(Mutator& toModify)
: target(toModify)
, content()
{ }
void init()
{
target.swapContent (content);
content.resetPos();
if (not target.empty()) // re-entrance:
{ // discard garbage from previous usage.
Rec pristineSequence; // must start new sequence from scratch
target.swap (pristineSequence);
}
}
};
/** Storage: a stack of workspaces
* used to handle nested child objects */
std::stack<ScopeFrame> scopes_;
Mutator& out() { return scopes_.top().target; }
Content& src() { return scopes_.top().content; }
Iter& srcPos() { return scopes_.top().content.pos; }
bool endOfData() { return srcPos() == src().end(); }
Rec& alteredRec() { return out(); }
void
__expect_in_target (GenNode const& elm, Literal oper)
{
if (endOfData())
throw error::State(_Fmt("Unable to %s element %s from target as demanded; "
"no (further) elements in target sequence") % oper % elm
, LUMIERA_ERROR_DIFF_CONFLICT);
if (elm.matches(Ref::CHILD) and not srcPos()->isNamed())
return; // allow for anonymous pick or delete of children
if (not srcPos()->matches(elm))
throw error::State(_Fmt("Unable to %s element %s from target as demanded; "
"found element %s on current target position instead")
% oper % elm % *srcPos()
, LUMIERA_ERROR_DIFF_CONFLICT);
}
void
__expect_further_elements (GenNode const& elm)
{
if (endOfData())
throw error::State(_Fmt("Premature end of target sequence, still expecting element %s; "
"unable to apply diff further.") % elm
, LUMIERA_ERROR_DIFF_CONFLICT);
}
void
__expect_found (GenNode const& elm, Iter const& targetPos)
{
if (targetPos == src().end())
throw error::State(_Fmt("Premature end of sequence; unable to locate "
"element %s in the remainder of the target.") % elm
, LUMIERA_ERROR_DIFF_CONFLICT);
}
void
__expect_successful_location (GenNode const& elm)
{
if (endOfData()
and not ( elm.matches(Ref::END) // after(_END_) -> its OK we hit the end
or (elm.matches(Ref::ATTRIBS) and src().children.empty()))) // after(_ATTRIBS_) -> if there are no children, it's OK to hit the end
throw error::State(_Fmt("Unable locate position 'after(%s)'") % elm.idi
, LUMIERA_ERROR_DIFF_CONFLICT);
}
void
__expect_valid_parent_scope (GenNode::ID const& idi)
{
if (scopes_.empty())
throw error::State(_Fmt("Unbalanced child scope bracketing tokens in diff; "
"When leaving scope %s, we fell out of root scope.") % idi.getSym()
, LUMIERA_ERROR_DIFF_CONFLICT);
if (alteredRec().empty())
throw error::State(_Fmt("Corrupted state. When leaving scope %s, "
"we found an empty parent scope.") % idi.getSym()
, LUMIERA_ERROR_DIFF_CONFLICT);
}
void
__expect_end_of_scope (GenNode::ID const& idi)
{
if (not endOfData())
throw error::State(_Fmt("Incomplete diff: when about to leave scope %s, "
"not all previously existing elements have been confirmed by the diff. "
"At least one spurious element %s was left over") % idi.getSym() % *srcPos()
, LUMIERA_ERROR_DIFF_CONFLICT);
}
Iter
find_in_current_scope (GenNode const& elm)
{
Iter end_of_scope = src().currIsAttrib()? src().attribs.end()
: src().children.end();
return std::find_if (srcPos()
,end_of_scope
,[&](auto& entry)
{
return entry.matches(elm);
});
}
GenNode const&
find_child (GenNode::ID const& idi)
{
if (alteredRec().empty())
throw error::State(_Fmt("Attempt to mutate element %s, but current target data scope is empty. "
"Sender and receiver out of sync?") % idi.getSym()
, LUMIERA_ERROR_DIFF_CONFLICT);
// Short-cut-mutation: look at the last element.
// this should be the one just added. BUT NOTE: this fails
// when adding an attribute after entering the child scope.
// Since attributes are typically values and not mutated,
// this inaccuracy was deemed acceptable
auto& current = out().accessLast();
if (Ref::THIS.matches(idi) or current.matches(idi))
return current;
for (auto & child : alteredRec())
if (child.idi == idi)
return child;
throw error::State(_Fmt("Attempt to mutate non existing child record; unable to locate child %s "
"after applying the diff. Current scope: %s") % idi.getSym() % alteredRec()
, LUMIERA_ERROR_DIFF_CONFLICT);
}
void
move_into_new_sequence (Iter pos)
{
if (src().currIsAttrib())
out().appendAttrib (move(*pos)); //////////////TICKET #969 was it a good idea to allow adding attributes "after the fact"?
else
out().appendChild (move(*pos));
}
/* == Implementation of the list diff application primitives == */
virtual void
ins (GenNode const& n) override
{
if (n.isNamed())
if (n.isTypeID())
out().setType (n.data.get<string>());
else
out().appendAttrib(n); //////////////TICKET #969 dto.
else
{
out().appendChild(n);
if (src().currIsAttrib())
src().jumpToChildScope();
}
}
virtual void
del (GenNode const& n) override
{
__expect_in_target(n, "remove");
++src();
}
virtual void
pick (GenNode const& n) override
{
__expect_in_target(n, "pick");
move_into_new_sequence (srcPos());
++src();
}
virtual void
skip (GenNode const& n) override
{
__expect_further_elements (n);
++src();
} // assume the actual content has been moved away by a previous find()
virtual void
find (GenNode const& n) override
{
__expect_further_elements (n);
Iter found = find_in_current_scope(n);
__expect_found (n, found);
move_into_new_sequence (found);
} // consume and leave waste, expected to be cleaned-up by skip() later
/* == Implementation of the tree diff application primitives == */
/** cue to a position behind the named node,
* thereby picking (accepting) all traversed elements
* into the reshaped new data structure as-is */
virtual void
after (GenNode const& n) override
{
if (n.matches(Ref::ATTRIBS))
while (not endOfData() and srcPos()->isNamed())
{
move_into_new_sequence (srcPos());
++src();
}
else
if (n.matches(Ref::END))
while (not endOfData())
{
move_into_new_sequence (srcPos());
++src();
}
else
while (not (endOfData() or srcPos()->matches(n)))
{
move_into_new_sequence (srcPos());
++src();
}
__expect_successful_location(n);
if (not endOfData() and srcPos()->matches(n))
{
move_into_new_sequence (srcPos());
++src(); // get /after/ an explicitly given position
}
}
/** assignment of changed value in one step */
virtual void
set (GenNode const& n) override
{
GenNode const& elm = find_child (n.idi);
unConst(elm).data = n.data;
}
/** open nested scope to apply diff to child object */
virtual void
mut (GenNode const& n) override
{
GenNode const& child = find_child (n.idi);
Rec const& childRecord = child.data.get<Rec>();
TRACE (diff, "tree-diff: ENTER scope %s", cStr(childRecord));
scopes_.emplace (mutateInPlace (unConst(childRecord)));
scopes_.top().init();
}
/** finish and leave child object scope, return to parent */
virtual void
emu (GenNode const& n) override
{
TRACE (diff, "tree-diff: LEAVE scope %s", cStr(alteredRec()));
__expect_end_of_scope (n.idi);
scopes_.pop();
__expect_valid_parent_scope (n.idi);
}
public:
explicit
DiffApplicationStrategy(Rec::Mutator& mutableTargetRecord)
: scopes_()
{
scopes_.emplace(mutableTargetRecord);
}
void
initDiffApplication()
{
REQUIRE (1 == scopes_.size());
scopes_.top().init();
}
};
}} // namespace lib::diff

View file

@ -22,27 +22,71 @@
/** @file tree-diff-mutator-binding.hpp
** Concrete implementation to apply structural changes to unspecific
** 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
** combined with the generic #DiffApplicator, this allows to receive
** linearised structural diff descriptions and apply them to a given
** target data structure, which in this case is even a decoupled
** private data structure.
** Concrete implementation to apply structural changes to hierarchical
** data structures. Together with the generic #DiffApplicator, this allows
** to receive linearised structural diff descriptions and apply them to
** a given target data structure, to effect the corresponding changes.
**
** ## Design considerations
** So this use case is implemented on the same conceptual framework used for
** the generic tree diff application, which in turn is -- conceptually -- an
** extension of applying a list diff. But, again, we follow the route _not_ to
** explicate those conceptual relations in the form of inheritance. This would
** While -- conceptually -- our tree diff handling can be seen as an extension
** and generalisation of list diffing, the decision was \em not to embody this
** extension into the implementation technically, for sake of clarity. This would
** be implementation re-use, as opposed to building a new viable abstraction.
** No one outside the implementation realm would benefit from such an abstraction,
** so we prefer to understand the tree diff language as the abstraction, which
** needs to embodied into two distinct contexts of implementation.
** needs to embodied into two distinct contexts of implementation. So the list diff
** application strategy can be seen as blueprint and demonstration of principles.
**
** ### Yet another indirection
** ### Use cases
** Initially, we'd have to distinguish two usage situations
** - apply a diff to a generic tree representation, based on Record<GenNode>
** - apply a diff to some tree shaped implementation data structure
** _Conceptually_ we use the former as blueprint and base to define the semantics
** of our »tree-diff language«, while the latter is an extension and can be supported
** within the limits of precisely these tree-diff semantics. That is, we support diff
** application to all implementation data structures which are _conceptually congruent_
** to the generic tree representation. This extension happens in accordance to the
** goals of our "diff framework", since we want to allow collaboration between
** loosely coupled subsystems, without the need of a shared data structure.
**
** ### Implementation
** On the implementation level though, relations are the other way round: the
** framework and technique to enable applying a diff onto private implementation data
** is used also to apply the diff onto the (likewise private) implementation of our
** generic tree representation. Because the common goal is loose coupling, we strive
** at imposing as few requirements or limitations onto the implementation as possible.
**
** Rather we require the implementation to provide a _binding,_ which can then be used
** to _execute_ the changes as dictated by the incoming diff. But since this binding
** has to consider intricate details of the diff language's semantics and implementation,
** we provide a *Builder DSL*, so the client may assemble the concrete binding from
** preconfigured building blocks for the most common cases
** - binding "attributes" to object fields
** - binding "children" to a STL collection of values
** - binding especially to a collection of GenNode elements,
** which basically covers also the generic tree representation.
**
** #### State and nested scopes
** For performance reasons, the diff is applied _in place_, directly mutating the
** target data structure. This makes the diff application _stateful_ -- and in case of
** a *diff conflict*, the target *will be corrupted*.
**
** Our tree like data structures are conceived as a system of nested scopes. Within
** each scope, we have a list of elements, to which a list-diff is applied. On start
** of diff application, a one time adapter and intermediary is constructed: the TreeMutator.
** This requires the help of the target data structure to set up the necessary bindings,
** since the diff applicator as such has no knowledge about the target data implementation.
** At this point, the existing (old) contents of the initial scope are moved away into an
** internal _source sequence buffer,_ from where they may be "picked" and moved back into
** place step by step through the diff. After possibly establishing a new order, inserting
** or omitting content within a given "object" (Record), the tree diff language allows in
** a second step to _open_ some of the child "objects" by entering nested scope, to effect
** further changes within the selected child node. This is done within the `mut(ID)....emu(ID)`
** bracketing construct of the diff language. On the implementation side, this recursive
** descent and diff application is implemented with the help of a stack, where a new
** TreeMutator is constructed whenever we enter (push) a new nested scope.
**
** #### Yet another indirection
** 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
@ -51,8 +95,8 @@
** functors) to translate the _implementation actions_ underlying the language into
** _concrete actions_ working on local data.
**
** ### Generic and variable parts
** So this is a link between generic [»tree diff language«](\ref tree-diff.hpp)
** #### Generic and variable parts
** So there is a link between generic [»tree diff language«](\ref tree-diff.hpp)
** 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
@ -62,19 +106,18 @@
** 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
** In the end, for each target structure, a concrete TreeMutator needs to be built
** or provided within the realm of the actual data implementation, so the knowledge
** about the actual data layout remains confined there. While this requires some
** understanding regarding structure and semantics of the diff language, 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, we provide a DSL with standard adapters and building blocks,
** to ease the task of generating ("binding") the actual TreeMutator. The usage site
** needs to supply only some functors or lambda expressions to specify how to deal
** with the actual representation data values:
** - how to construct a new entity
** - when the binding actually becomes relevant
** - when the binding actually becomes active
** - how to determine a diff verb "matches" the actual data
** - how to set a value or how to recurse into a sub scope
**
@ -97,6 +140,7 @@
#include "lib/diff/tree-diff.hpp"
#include "lib/diff/tree-mutator.hpp"
#include "lib/diff/diff-mutable.hpp"
#include "lib/diff/tree-diff-traits.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/format-string.hpp"
#include "lib/util.hpp"
@ -267,5 +311,73 @@ namespace diff{
/**
* Interpreter for the tree-diff-language to work on arbitrary
* opaque target data structures. A concrete strategy to apply a structural diff
* to otherwise undisclosed, recursive, tree-like target data. The only requirement
* is for this target structure to expose a hook for building a customised
* TreeMutator able to work on and transform the private target data.
*
* This generic setup for diff application covers especially the case where the
* target data is a "GenNode tree", and the root is accessible as Rec::Mutator
* (We use the Mutator as entry point, since GenNode trees are by default immutable).
*
* 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, 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
* diff application and mutation task.
*
* @throws lumiera::error::State when diff application fails due to the
* target sequence being different than assumed by the given diff.
* @see DiffComplexApplication_test usage example of this combined machinery
* @see #TreeDiffInterpreter explanation of the verbs
*/
template<class TAR>
class DiffApplicationStrategy<TAR, enable_if<TreeDiffTraits<TAR>>>
: public TreeDiffMutatorBinding
{
using Scopes = StackScopeManager<TreeMutatorSizeTraits<TAR>::siz>;
TAR& subject_;
Scopes scopes_;
TreeMutator*
buildMutator (DiffMutable& targetBinding)
{
scopes_.clear();
TreeMutator::Handle buffHandle = scopes_.openScope();
targetBinding.buildMutator (buffHandle);
return buffHandle.get();
}
public:
explicit
DiffApplicationStrategy(TAR& subject)
: TreeDiffMutatorBinding()
, subject_(subject)
, scopes_()
{ }
void
initDiffApplication()
{
auto target = mutatorBinding (subject_);
buildMutator (target);
TreeDiffMutatorBinding::scopeManger_ = &scopes_;
TreeDiffMutatorBinding::treeMutator_ = &scopes_.currentScope();
REQUIRE (this->treeMutator_);
}
};
}} // namespace lib::diff
#endif /*LIB_DIFF_TREE_DIFF_MUTATOR_BINDING_H*/