From a066650eb7fd441c97fdd9bb392dc93220edb4de Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 5 Sep 2016 04:05:31 +0200 Subject: [PATCH] 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 --- src/lib/diff/tree-diff-application.hpp | 368 --------------------- src/lib/diff/tree-diff-mutator-binding.hpp | 170 ++++++++-- 2 files changed, 141 insertions(+), 397 deletions(-) diff --git a/src/lib/diff/tree-diff-application.hpp b/src/lib/diff/tree-diff-application.hpp index 450e1f20c..b91a33b8f 100644 --- a/src/lib/diff/tree-diff-application.hpp +++ b/src/lib/diff/tree-diff-application.hpp @@ -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 DiffApplicationStrategy>> - : public TreeDiffMutatorBinding - { - using Scopes = StackScopeManager::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 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 - : 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 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()); - 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(); - - 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 diff --git a/src/lib/diff/tree-diff-mutator-binding.hpp b/src/lib/diff/tree-diff-mutator-binding.hpp index 7afe32e21..0e7176318 100644 --- a/src/lib/diff/tree-diff-mutator-binding.hpp +++ b/src/lib/diff/tree-diff-mutator-binding.hpp @@ -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 + ** - 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 DiffApplicationStrategy>> + : public TreeDiffMutatorBinding + { + using Scopes = StackScopeManager::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*/