application via TreeMutator is now the default
remove the intermediary header
This commit is contained in:
parent
a066650eb7
commit
4267d3d1d7
5 changed files with 332 additions and 451 deletions
|
|
@ -2,7 +2,7 @@
|
|||
TREE-DIFF-APPLICATION.hpp - consume and apply a tree diff
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2014, Hermann Vosseler <Ichthyostega@web.de>
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
|
|
@ -30,67 +30,99 @@
|
|||
** ## Design considerations
|
||||
** 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. More so,
|
||||
** since the Record, which serves as foundation for our »External Tree Description«,
|
||||
** was made to look and behave like a list-like entity, but built with two distinct
|
||||
** scopes at implementation level: the attribute scope and the contents scope. This
|
||||
** carries over to the fine points of the list diff language semantics, especially
|
||||
** when it comes to fault tolerance and strictness vs fuzziness in diff application.
|
||||
** The implementation is thus faced with having to deal with an internal focus and
|
||||
** a switch from scope to scope, which adds a lot of complexity. So the list diff
|
||||
** 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. So the list diff
|
||||
** application strategy can be seen as blueprint and demonstration of principles.
|
||||
**
|
||||
** Another point in question is whether to treat the diff application as
|
||||
** manipulating a target data structure, or rather building a reshaped copy.
|
||||
** The fact that GenNode and Record are designed as immutable values seems to favour
|
||||
** the latter, yet the very reason to engage into building this diff framework was
|
||||
** how to handle partial updates within a expectedly very large UI model, reflecting
|
||||
** the actual session model in Proc-Layer. So we end up working on a Mutator,
|
||||
** which clearly signals we're going to reshape and re-rig the target data.
|
||||
** ### 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.
|
||||
**
|
||||
** \par related
|
||||
** Closely related to this generic application of tree changes is the situation,
|
||||
** where we want to apply structural changes to some non-generic and private data
|
||||
** structure. In fact, it is possible to _use the same tree diff language_ for
|
||||
** this specific case, with the help of an _adapter_. Thus, within our diff
|
||||
** framework, we provide a _similar binding_ for the DiffApplicator, but
|
||||
** then targeted towards such an [structure adapter](\ref TreeMutator)
|
||||
** ### 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.
|
||||
**
|
||||
** ## State and nested scopes
|
||||
** Within the level of a single #Record, our tree diff language works similar to
|
||||
** the list diff (with the addition of the \c after(ID) verb, which is just a
|
||||
** shortcut to accept parts of the contents unaltered). But after possibly rearranging
|
||||
** the contents of an "object" (Record), the diff might open some of its child "objects"
|
||||
** by entering a nested scope. This is done with the \c mut(ID)....emu(ID) bracketing
|
||||
** construct. On the implementation side, this means we need to use a stack somehow.
|
||||
** The decision was to manage this stack explicitly, as a std::stack (heap memory).
|
||||
** Each entry on this stack is a "context frame" for list diff. Which makes the
|
||||
** tree diff applicator a highly statefull component.
|
||||
** 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.
|
||||
**
|
||||
** Even more so, since -- for \em performance reasons -- we try to alter the
|
||||
** tree shaped data structure \em in-place. We want to avoid the copy of possibly
|
||||
** deep sub-trees, when in the end we might be just rearranging their sequence order.
|
||||
** This design decision comes at a price tag though
|
||||
** - it subverts the immutable nature of \c Record<GenNode> and leads to
|
||||
** high dependency on data layout and implementation details of the latter.
|
||||
** This is at least prominently marked by working on a diff::Record::Mutator,
|
||||
** so the client has first to "open up" the otherwise immutable tree
|
||||
** - the actual list diff on each level works by first \em moving the entire
|
||||
** Record contents away into a temporary buffer and then \em moving them
|
||||
** back into new shape one by one. In case of a diff conflict (i.e. a
|
||||
** mismatch between the actual data structure and the assumptions made
|
||||
** for the diff message on the sender / generator side), an exception
|
||||
** is thrown, leaving the client with a possibly corrupted tree, where
|
||||
** parts might even still be stashed away in the temporary buffer,
|
||||
** and thus be lost.
|
||||
** We consider this unfortunate, yet justified by the very nature of applying a diff.
|
||||
** When the user needs safety or transactional behaviour, a deep copy should be made
|
||||
** before attaching the #DiffApplicator
|
||||
** #### 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*.
|
||||
**
|
||||
** @note as of 2/2016, there is the possibility this solution will become part
|
||||
** of a more generic solution, currently being worked out in tree-diff-mutator-binding.hpp
|
||||
** 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
|
||||
** 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.
|
||||
**
|
||||
** #### 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
|
||||
** 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, 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 active
|
||||
** - how to determine a diff verb "matches" the actual data
|
||||
** - how to set a value or how to recurse into a sub scope
|
||||
**
|
||||
** @see DiffTreeApplication_test
|
||||
** @see DiffComplexApplication_test
|
||||
** @see DiffListApplication_test
|
||||
** @see GenNodeBasic_test
|
||||
** @see tree-diff.hpp
|
||||
|
|
@ -103,7 +135,9 @@
|
|||
|
||||
|
||||
#include "lib/diff/tree-diff.hpp"
|
||||
#include "lib/diff/tree-diff-mutator-binding.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"
|
||||
|
|
@ -114,6 +148,104 @@
|
|||
namespace lib {
|
||||
namespace diff{
|
||||
|
||||
/**
|
||||
* Management interface to deal with storage for
|
||||
* TreeMutators dedicated to nested scopes
|
||||
*/
|
||||
class ScopeManager
|
||||
: boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual ~ScopeManager(); ///< this is an interface
|
||||
|
||||
virtual TreeMutator::Handle openScope() =0;
|
||||
virtual TreeMutator& closeScope() =0;
|
||||
virtual void clear() =0;
|
||||
|
||||
virtual size_t depth() const =0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Typical standard implementation of the ScopeManager.
|
||||
* Using Heap memory for the nested scopes, we create a stack
|
||||
* of opaque InPlaceBuffers for each scope, which allows the
|
||||
* PlantingHandle mechanism to let the target object corresponding
|
||||
* to this scope build its own TreeMutator implementation into
|
||||
* this buffer space for this scope.
|
||||
*/
|
||||
template<size_t buffSiz>
|
||||
class StackScopeManager
|
||||
: public ScopeManager
|
||||
{
|
||||
using MutatorBuffer = InPlaceBuffer<TreeMutator, buffSiz>;
|
||||
using MutatorStack = std::stack<MutatorBuffer>;
|
||||
|
||||
/** Allocate Heap Storage for nested TreeMutator(s) */
|
||||
MutatorStack scopes_;
|
||||
|
||||
|
||||
public:
|
||||
StackScopeManager()
|
||||
: scopes_()
|
||||
{ }
|
||||
|
||||
TreeMutator&
|
||||
currentScope() const
|
||||
{
|
||||
if (0 == depth())
|
||||
throw error::Logic("Attempt to access the current scope "
|
||||
"without establishing a root scope beforehand."
|
||||
, error::LUMIERA_ERROR_LIFECYCLE);
|
||||
return *scopes_.top();
|
||||
}
|
||||
|
||||
|
||||
/* ==== ScopeManager interface ==== */
|
||||
|
||||
virtual TreeMutator::Handle
|
||||
openScope()
|
||||
{
|
||||
scopes_.emplace();
|
||||
TreeMutator::Handle placementHandle (scopes_.top());
|
||||
|
||||
static_assert (buffSiz >= sizeof(typename MutatorStack::value_type)
|
||||
,"insufficient working buffer for TreeMutator");
|
||||
return placementHandle;
|
||||
}
|
||||
|
||||
virtual TreeMutator&
|
||||
closeScope()
|
||||
{
|
||||
scopes_.pop();
|
||||
REQUIRE (0 < depth(), "attempt to return beyond root scope");
|
||||
return *scopes_.top();
|
||||
}
|
||||
|
||||
virtual void
|
||||
clear()
|
||||
{
|
||||
while (0 < scopes_.size())
|
||||
scopes_.pop();
|
||||
|
||||
ENSURE (scopes_.empty());
|
||||
}
|
||||
|
||||
|
||||
virtual size_t
|
||||
depth() const
|
||||
{
|
||||
return scopes_.size();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ======= Implementation of Tree Diff Application via TreeMutator ======= */
|
||||
|
||||
using util::unConst;
|
||||
using util::cStr;
|
||||
using util::_Fmt;
|
||||
|
|
@ -121,11 +253,138 @@ namespace diff{
|
|||
using std::swap;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the tree-diff-language to work on arbitrary tree-like data.
|
||||
* This is the core part of the implementation, which maps the _diff verbs_
|
||||
* onto the corresponding _primitive operations_ of the TreeMutator interface.
|
||||
* The concrete implementation of TreeMutator then is responsible of translating
|
||||
* those operations into the correct manipulation of target data.
|
||||
* @note implementation of these functions is emitted in tree-diff.cpp and thus
|
||||
* within the library module. For an actual diff-applicator, we also need
|
||||
* to bind to a concrete TreeMutator, for which we need to instantiate
|
||||
* the template DiffApplicationStrategy with the concrete target type
|
||||
* as parameter (see below). This concrete instantiation happens
|
||||
* inline from within the usage context, while inheriting the
|
||||
* actual implementation logic from this baseclass here
|
||||
*
|
||||
* @throws lumiera::error::State when diff application fails structurally.
|
||||
* @throws _unspecified errors_ when delegated operations fail.
|
||||
* @see TreeDiffInterpreter explanation of the verbs
|
||||
* @see DiffComplexApplication_test demonstration of usage
|
||||
*/
|
||||
class TreeDiffMutatorBinding
|
||||
: public TreeDiffInterpreter
|
||||
{
|
||||
protected:
|
||||
TreeMutator* treeMutator_;
|
||||
ScopeManager* scopeManger_;
|
||||
|
||||
private:
|
||||
|
||||
/* == error handling helpers == */
|
||||
|
||||
void __failMismatch (Literal oper, GenNode const& spec);
|
||||
void __expect_further_elements (GenNode const& elm);
|
||||
void __fail_not_found (GenNode const& elm);
|
||||
void __expect_end_of_scope (GenNode::ID const& idi);
|
||||
void __expect_valid_parent_scope (GenNode::ID const& idi);
|
||||
|
||||
|
||||
|
||||
|
||||
/* == Implementation of the list diff application primitives == */
|
||||
|
||||
virtual void ins (GenNode const& n) override;
|
||||
virtual void del (GenNode const& n) override;
|
||||
virtual void pick (GenNode const& n) override;
|
||||
virtual void skip (GenNode const& n) override;
|
||||
virtual void find (GenNode const& n) override;
|
||||
|
||||
|
||||
/* == Implementation of the tree diff application primitives == */
|
||||
|
||||
virtual void after(GenNode const& n) override;
|
||||
virtual void set (GenNode const& n) override;
|
||||
virtual void mut (GenNode const& n) override;
|
||||
virtual void emu (GenNode const& n) override;
|
||||
|
||||
|
||||
public:
|
||||
TreeDiffMutatorBinding()
|
||||
: treeMutator_(nullptr)
|
||||
, scopeManger_(nullptr)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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_APPLICATION_H*/
|
||||
|
|
|
|||
|
|
@ -1,383 +0,0 @@
|
|||
/*
|
||||
TREE-DIFF-MUTATOR-BINDING.hpp - consume a tree diff, but target arbitrary private data
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 2 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/** @file tree-diff-mutator-binding.hpp
|
||||
** 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
|
||||
** 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. So the list diff
|
||||
** application strategy can be seen as blueprint and demonstration of principles.
|
||||
**
|
||||
** ### 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
|
||||
** 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.
|
||||
**
|
||||
** #### 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
|
||||
** 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, 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 active
|
||||
** - 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
|
||||
**
|
||||
** @see DiffComplexApplication_test
|
||||
** @see DiffTreeApplication_test
|
||||
** @see DiffListApplication_test
|
||||
** @see GenNodeBasic_test
|
||||
** @see tree-diff.hpp
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_DIFF_TREE_DIFF_MUTATOR_BINDING_H
|
||||
#define LIB_DIFF_TREE_DIFF_MUTATOR_BINDING_H
|
||||
|
||||
|
||||
#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"
|
||||
|
||||
#include <utility>
|
||||
#include <stack>
|
||||
|
||||
namespace lib {
|
||||
namespace diff{
|
||||
|
||||
/**
|
||||
* Management interface to deal with storage for
|
||||
* TreeMutators dedicated to nested scopes
|
||||
*/
|
||||
class ScopeManager
|
||||
: boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual ~ScopeManager(); ///< this is an interface
|
||||
|
||||
virtual TreeMutator::Handle openScope() =0;
|
||||
virtual TreeMutator& closeScope() =0;
|
||||
virtual void clear() =0;
|
||||
|
||||
virtual size_t depth() const =0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Typical standard implementation of the ScopeManager.
|
||||
* Using Heap memory for the nested scopes, we create a stack
|
||||
* of opaque InPlaceBuffers for each scope, which allows the
|
||||
* PlantingHandle mechanism to let the target object corresponding
|
||||
* to this scope build its own TreeMutator implementation into
|
||||
* this buffer space for this scope.
|
||||
*/
|
||||
template<size_t buffSiz>
|
||||
class StackScopeManager
|
||||
: public ScopeManager
|
||||
{
|
||||
using MutatorBuffer = InPlaceBuffer<TreeMutator, buffSiz>;
|
||||
using MutatorStack = std::stack<MutatorBuffer>;
|
||||
|
||||
/** Allocate Heap Storage for nested TreeMutator(s) */
|
||||
MutatorStack scopes_;
|
||||
|
||||
|
||||
public:
|
||||
StackScopeManager()
|
||||
: scopes_()
|
||||
{ }
|
||||
|
||||
TreeMutator&
|
||||
currentScope() const
|
||||
{
|
||||
if (0 == depth())
|
||||
throw error::Logic("Attempt to access the current scope "
|
||||
"without establishing a root scope beforehand."
|
||||
, error::LUMIERA_ERROR_LIFECYCLE);
|
||||
return *scopes_.top();
|
||||
}
|
||||
|
||||
|
||||
/* ==== ScopeManager interface ==== */
|
||||
|
||||
virtual TreeMutator::Handle
|
||||
openScope()
|
||||
{
|
||||
scopes_.emplace();
|
||||
TreeMutator::Handle placementHandle (scopes_.top());
|
||||
|
||||
static_assert (buffSiz >= sizeof(typename MutatorStack::value_type)
|
||||
,"insufficient working buffer for TreeMutator");
|
||||
return placementHandle;
|
||||
}
|
||||
|
||||
virtual TreeMutator&
|
||||
closeScope()
|
||||
{
|
||||
scopes_.pop();
|
||||
REQUIRE (0 < depth(), "attempt to return beyond root scope");
|
||||
return *scopes_.top();
|
||||
}
|
||||
|
||||
virtual void
|
||||
clear()
|
||||
{
|
||||
while (0 < scopes_.size())
|
||||
scopes_.pop();
|
||||
|
||||
ENSURE (scopes_.empty());
|
||||
}
|
||||
|
||||
|
||||
virtual size_t
|
||||
depth() const
|
||||
{
|
||||
return scopes_.size();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* ======= Implementation of Tree Diff Application via TreeMutator ======= */
|
||||
|
||||
using util::unConst;
|
||||
using util::cStr;
|
||||
using util::_Fmt;
|
||||
using std::move;
|
||||
using std::swap;
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* diff. This is ensured with the help of a _customisable adapter_ the TreeMutator.
|
||||
* @throws lumiera::error::State when diff application fails structurally.
|
||||
* @throws _unspecified errors_ when delegated operations fail.
|
||||
* @see TreeDiffInterpreter explanation of the verbs
|
||||
* @see DiffComplexApplication_test demonstration of usage
|
||||
*/
|
||||
class TreeDiffMutatorBinding
|
||||
: public TreeDiffInterpreter
|
||||
{
|
||||
protected:
|
||||
TreeMutator* treeMutator_;
|
||||
ScopeManager* scopeManger_;
|
||||
|
||||
private:
|
||||
|
||||
/* == error handling helpers == */
|
||||
|
||||
void __failMismatch (Literal oper, GenNode const& spec);
|
||||
void __expect_further_elements (GenNode const& elm);
|
||||
void __fail_not_found (GenNode const& elm);
|
||||
void __expect_end_of_scope (GenNode::ID const& idi);
|
||||
void __expect_valid_parent_scope (GenNode::ID const& idi);
|
||||
|
||||
|
||||
|
||||
|
||||
/* == Implementation of the list diff application primitives == */
|
||||
|
||||
virtual void ins (GenNode const& n) override;
|
||||
virtual void del (GenNode const& n) override;
|
||||
virtual void pick (GenNode const& n) override;
|
||||
virtual void skip (GenNode const& n) override;
|
||||
virtual void find (GenNode const& n) override;
|
||||
|
||||
|
||||
/* == Implementation of the tree diff application primitives == */
|
||||
|
||||
virtual void after(GenNode const& n) override;
|
||||
virtual void set (GenNode const& n) override;
|
||||
virtual void mut (GenNode const& n) override;
|
||||
virtual void emu (GenNode const& n) override;
|
||||
|
||||
|
||||
public:
|
||||
TreeDiffMutatorBinding()
|
||||
: treeMutator_(nullptr)
|
||||
, scopeManger_(nullptr)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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*/
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
TreeDiffMutatorBinding - implementation of diff application to opaque data
|
||||
TreeDiff - implementation of diff application to opaque data
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file tree-diff-mutator-binding.cpp
|
||||
/** @file tree-diff.cpp
|
||||
** Implementation of diff application to unspecific private data structures.
|
||||
** This binding is the link between a generic interpreter for our
|
||||
** »tree diff language« and a concrete TreeMutator implementation,
|
||||
|
|
@ -33,15 +33,15 @@
|
|||
** up some specifics of the concrete usage situation and thus needs to be
|
||||
** generated in usage context.
|
||||
**
|
||||
** @see tree-diff.cpp
|
||||
** @see tree-diff-mutator-binding.cpp
|
||||
** @see tree-diff.hpp
|
||||
** @see tree-diff-application.hpp
|
||||
** @see DiffComplexApplication_test
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/diff/tree-diff-mutator-binding.hpp"
|
||||
#include "lib/diff/tree-diff-application.hpp"
|
||||
|
||||
|
||||
|
||||
|
|
@ -54,6 +54,8 @@ namespace diff{
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ======= Implementation of Tree Diff Application via TreeMutator ======= */
|
||||
|
||||
using util::unConst;
|
||||
|
|
@ -79,6 +81,8 @@ namespace diff{
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
/* == Forwarding: error handling == */
|
||||
|
||||
|
||||
|
|
@ -129,6 +133,7 @@ namespace diff{
|
|||
|
||||
|
||||
|
||||
|
||||
/* == Implementation of the list diff application primitives == */
|
||||
|
||||
void
|
||||
|
|
@ -49,6 +49,12 @@
|
|||
** is represented by "opening" the nested record, followed by a recursive diff.
|
||||
** By implementing the #TreeDiffInterpreter interface (visitor), a concrete usage
|
||||
** can receive a diff description and possibly apply it to suitable target data.
|
||||
** @remarks the standard usage is to create a DiffApplicator(target) and fed
|
||||
** a diff sequence to it. We provide a standard implementation of the
|
||||
** DiffApplicator + DiffApplicationStrategy, based on a _custimisable intermediary,_
|
||||
** the TreeMutator. This allows to apply a given tree to any suitably compatible
|
||||
** target data structure; especially there is a preconfigured setup for our
|
||||
** _"generic tree representation"_, diff::Record<GenNode>.
|
||||
**
|
||||
** @see diff-language.cpp
|
||||
** @see tree-diff-application.cpp
|
||||
|
|
@ -116,12 +122,7 @@ namespace diff{
|
|||
* altered target structure. The `mut(ID)` verb then opens this nested
|
||||
* record for diff handling, and all subsequent diff verbs are to be
|
||||
* interpreted relative to this scope, until the corresponding
|
||||
* \c emu(ID) verb is encountered. As a special notation, right
|
||||
* after handling an element with the list diff verbs (i.e. \c ins
|
||||
* or \c pick or \c find), it is allowed immediately to open the
|
||||
* nested scope with \c mut(Ref::THIS) -- which circumvents the
|
||||
* problem that it is sometimes difficult to know the precise ID,
|
||||
* especially when hand-writing a diff to populate a data structure. ////////TICKET #996 : `Ref::THIS` is a questionable feature
|
||||
* \c emu(ID) verb is encountered.
|
||||
* - \c emu bracketing construct and counterpart to \c mut(ID). This verb
|
||||
* must be given precisely at the end of the nested scope (it is
|
||||
* not allowed to "return" from the middle of a scope, for sake
|
||||
|
|
@ -143,7 +144,7 @@ namespace diff{
|
|||
virtual void skip(GenNode const& n) =0;
|
||||
|
||||
virtual void after(GenNode const&n) =0;
|
||||
virtual void set (GenNode const&n) =0;
|
||||
virtual void set (GenNode const& n) =0;
|
||||
virtual void mut (GenNode const& n) =0;
|
||||
virtual void emu (GenNode const& n) =0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -290,7 +290,6 @@ namespace test{
|
|||
* @see DiffTreeApplication_test generic variant of tree diff application
|
||||
* @see TreeMutatorBinding_test coverage of the "building blocks"
|
||||
* @see TreeMutator_test base operations of the adapter
|
||||
* @see tree-diff-mutator-binding.hpp
|
||||
* @see diff-tree-application.hpp
|
||||
* @see tree-diff.hpp
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue