previously they where included in the middle of tree-mutator.hpp This was straight forward, since the builder relies on the classes defined in the detail headers. However, the GenNode-binding needs to use a specifically configured collection binding, and this in turn requires writing a recursive lambda to deal with nested scopes. This gets us into trouble with circular definition dependencies. As a workaround we now only *declare* the DSL builder functions in the tree-mutator-builder object, and additionally use auto on all return types. This allows us to spell out the complete builder definition, without mentioning any of the implementation classes. Obviously, the detail headers have then to be included *after* the builder definition, at bottom of tree-mutator.hpp This also allows us to turn these implementation headers into completely normal headers, with namespaces and transitive #includes In the end, the whole setup looks much more "innocent" now. But beware: the #include of the implementation headers at bottom of tree-mutator.hpp needs to be given in reverse dependency order, due to the circular inclusion (back to tree-mutator.hpp) in conjunction with the inclusion guards!
623 lines
21 KiB
C++
623 lines
21 KiB
C++
/*
|
|
TREE-MUTATOR-COLLECTION-BINDING.hpp - diff::TreeMutator implementation building block
|
|
|
|
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-mutator-collection-binding.hpp
|
|
** Special binding implementation for TreeMutator, allowing to map
|
|
** tree diff operations onto a STL collection of native implementation objects.
|
|
** TreeMutator is a customisable intermediary, which enables otherwise opaque
|
|
** implementation data structures to receive and respond to generic structural
|
|
** change messages ("tree diff").
|
|
**
|
|
** Each concrete TreeMutator instance will be configured differently, and this
|
|
** adaptation is done by implementing binding templates, in the way of building
|
|
** blocks, attached and customised through lambdas. It is possible to layer
|
|
** several bindings on top of a single TreeMutator -- and this header defines
|
|
** a building block for one such layer, especially for binding to a representation
|
|
** of "child objects" managed within a typical STL container.
|
|
**
|
|
** @note the header tree-mutator-collection-binding.hpp was split off for sake of readability
|
|
** and is included automatically from bottom of tree-mutator.hpp
|
|
**
|
|
** @see tree-mutator-test.cpp
|
|
** @see TreeMutator::build()
|
|
**
|
|
*/
|
|
|
|
|
|
#ifndef LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H
|
|
#define LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H
|
|
|
|
|
|
#include "lib/error.hpp"
|
|
#include "lib/meta/trait.hpp"
|
|
#include "lib/diff/gen-node.hpp"
|
|
#include "lib/diff/tree-mutator.hpp"
|
|
#include "lib/iter-adapter-stl.hpp"
|
|
|
|
#include <utility>
|
|
|
|
|
|
namespace lib {
|
|
namespace diff{
|
|
|
|
namespace { // Mutator-Builder decorator components...
|
|
|
|
|
|
|
|
using lib::meta::Strip;
|
|
using lib::diff::GenNode;
|
|
using lib::iter_stl::eachElm;
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Attach to collection: Concrete binding setup.
|
|
* This record holds all the actual binding and closures
|
|
* used to attach the tree mutator to an external pre-existing
|
|
* STL container with child elements/objects. It serves as flexible
|
|
* connection, configuration and adaptation element, and will be embedded
|
|
* as a whole into the (\ref ChildCollectionMutator), which in turn implements
|
|
* the `TreeMutator` interface. The resulting compound is able to consume
|
|
* tree diff messages and apply the respective changes and mutations to
|
|
* an otherwise opaque implementation data structure.
|
|
*
|
|
* @tparam COLL a STL compliant collection type holding "child elements"
|
|
* @tparam MAT a functor to determine if a child matches a diff spec (GenNode)
|
|
* @tparam CTR a functor to construct a new child element from a given diff spec
|
|
* @tparam SEL predicate to determine if this binding layer has to process a diff message
|
|
* @tparam ASS a functor to assign / set a new value from a given diff spec
|
|
* @tparam MUT a functor to construct a nested mutator for some child element
|
|
*/
|
|
template<class COLL, class MAT, class CTR, class SEL, class ASS, class MUT>
|
|
struct CollectionBinding
|
|
{
|
|
using Coll = typename Strip<COLL>::TypeReferred;
|
|
using Elm = typename Coll::value_type;
|
|
|
|
using iterator = typename lib::iter_stl::_SeqT<Coll>::Range;
|
|
using const_iterator = typename lib::iter_stl::_SeqT<const Coll>::Range;
|
|
|
|
|
|
ASSERT_VALID_SIGNATURE (MAT, bool(GenNode const& spec, Elm const& elm))
|
|
ASSERT_VALID_SIGNATURE (CTR, Elm (GenNode const&))
|
|
ASSERT_VALID_SIGNATURE (SEL, bool(GenNode const&))
|
|
ASSERT_VALID_SIGNATURE (ASS, bool(Elm&, GenNode const&))
|
|
ASSERT_VALID_SIGNATURE (MUT, bool(Elm&, GenNode::ID const&, TreeMutator::Handle))
|
|
|
|
|
|
Coll& collection;
|
|
|
|
MAT matches;
|
|
CTR construct;
|
|
SEL isApplicable;
|
|
ASS assign;
|
|
MUT openSub;
|
|
|
|
CollectionBinding(Coll& coll, MAT m, CTR c, SEL s, ASS a, MUT u)
|
|
: collection(coll)
|
|
, matches(m)
|
|
, construct(c)
|
|
, isApplicable(s)
|
|
, assign(a)
|
|
, openSub(u)
|
|
{ }
|
|
|
|
|
|
/* === content manipulation API === */
|
|
|
|
Coll contentBuffer;
|
|
|
|
iterator
|
|
initMutation ()
|
|
{
|
|
contentBuffer.clear();
|
|
swap (collection, contentBuffer);
|
|
return eachElm (contentBuffer);
|
|
}
|
|
|
|
void
|
|
inject (Elm&& elm)
|
|
{
|
|
collection.emplace_back (forward<Elm>(elm));
|
|
}
|
|
|
|
iterator
|
|
search (GenNode const& targetSpec, iterator pos)
|
|
{
|
|
while (pos and not matches(targetSpec, *pos))
|
|
++pos;
|
|
return pos;
|
|
}
|
|
|
|
iterator
|
|
locate (GenNode const& targetSpec)
|
|
{
|
|
if (not collection.empty()
|
|
and (Ref::THIS.matches(targetSpec.idi)
|
|
or matches (targetSpec, collection.back())))
|
|
return lastElm();
|
|
else
|
|
return search (targetSpec, eachElm(collection));
|
|
}
|
|
|
|
private:
|
|
/** @internal technicality
|
|
* Our iterator is actually a Lumiera RangeIter, and thus we need
|
|
* to construct a raw collection iterator pointing to the aftmost element
|
|
* and then create a range from this iterator and the `end()` iterator.
|
|
*/
|
|
iterator
|
|
lastElm()
|
|
{
|
|
using raw_iter = typename CollectionBinding::Coll::iterator;
|
|
return iterator (raw_iter(&collection.back()), collection.end());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Attach to collection: Building block for a concrete `TreeMutator`.
|
|
* This decorator will be outfitted with actual binding and closures
|
|
* and then layered on top of the (\ref TreeMutaor) base. The resulting
|
|
* compound is able to consume tree diff messages and apply the respective
|
|
* changes and mutations to an otherwise opaque implementation data structure.
|
|
* @remarks in practice, this is the most relevant and typical `TreeMutator` setup.
|
|
*/
|
|
template<class PAR, class BIN>
|
|
class ChildCollectionMutator
|
|
: public PAR
|
|
{
|
|
using Iter = typename BIN::iterator;
|
|
|
|
BIN binding_;
|
|
Iter pos_;
|
|
|
|
|
|
public:
|
|
ChildCollectionMutator(BIN wiringClosures, PAR&& chain)
|
|
: PAR(std::forward<PAR>(chain))
|
|
, binding_(wiringClosures)
|
|
, pos_(binding_.initMutation())
|
|
{ }
|
|
|
|
|
|
|
|
|
|
/* ==== Implementation of TreeNode operation API ==== */
|
|
|
|
/** fabricate a new element, based on
|
|
* the given specification (GenNode),
|
|
* and insert it at current position
|
|
* into the target sequence.
|
|
*/
|
|
virtual bool
|
|
injectNew (GenNode const& n) override
|
|
{
|
|
if (binding_.isApplicable(n))
|
|
{
|
|
binding_.inject (binding_.construct(n));
|
|
return true;
|
|
}
|
|
else
|
|
return PAR::injectNew (n);
|
|
}
|
|
|
|
virtual bool
|
|
hasSrc () override
|
|
{
|
|
return bool(pos_) or PAR::hasSrc();
|
|
}
|
|
|
|
/** ensure the next recorded source element
|
|
* matches on a formal level with given spec */
|
|
virtual bool
|
|
matchSrc (GenNode const& spec) override
|
|
{
|
|
if (binding_.isApplicable(spec))
|
|
return pos_ and binding_.matches (spec, *pos_);
|
|
else
|
|
return PAR::matchSrc (spec);
|
|
}
|
|
|
|
/** skip next pending src element,
|
|
* causing this element to be discarded
|
|
* @note can not perform a match on garbage data
|
|
*/
|
|
virtual void
|
|
skipSrc (GenNode const& n) override
|
|
{
|
|
if (binding_.isApplicable(n))
|
|
{
|
|
if (pos_)
|
|
++pos_;
|
|
}
|
|
else
|
|
PAR::skipSrc (n);
|
|
}
|
|
|
|
/** accept existing element, when matching the given spec */
|
|
virtual bool
|
|
acceptSrc (GenNode const& n) override
|
|
{
|
|
if (binding_.isApplicable(n))
|
|
{
|
|
bool isSrcMatch = pos_ and binding_.matches (n, *pos_);
|
|
if (isSrcMatch) //NOTE: crucial to perform only our own match check here
|
|
{
|
|
binding_.inject (move(*pos_));
|
|
++pos_;
|
|
}
|
|
return isSrcMatch;
|
|
}
|
|
else
|
|
return PAR::acceptSrc (n);
|
|
}
|
|
|
|
/** locate designated element and accept it at current position */
|
|
virtual bool
|
|
findSrc (GenNode const& refSpec) override
|
|
{
|
|
if (binding_.isApplicable(refSpec))
|
|
{
|
|
Iter found = binding_.search (refSpec, pos_);
|
|
if (found)
|
|
{
|
|
binding_.inject (move(*found));
|
|
}
|
|
return found;
|
|
}
|
|
else
|
|
return PAR::findSrc (refSpec);
|
|
}
|
|
|
|
/** repeatedly accept, until after the designated location */
|
|
virtual bool
|
|
accept_until (GenNode const& spec)
|
|
{
|
|
if (spec.matches (Ref::END))
|
|
{
|
|
for ( ; pos_; ++pos_)
|
|
binding_.inject (move(*pos_));
|
|
return true;
|
|
}
|
|
else
|
|
if (spec.matches (Ref::ATTRIBS))
|
|
return PAR::accept_until (spec);
|
|
else
|
|
if (binding_.isApplicable(spec))
|
|
{
|
|
bool foundTarget = false;
|
|
while (pos_ and not binding_.matches (spec, *pos_))
|
|
{
|
|
binding_.inject (move(*pos_));
|
|
++pos_;
|
|
}
|
|
if (binding_.matches (spec, *pos_))
|
|
{
|
|
binding_.inject (move(*pos_));
|
|
++pos_;
|
|
foundTarget = true;
|
|
}
|
|
return foundTarget;
|
|
}
|
|
else
|
|
return PAR::accept_until (spec);
|
|
}
|
|
|
|
/** locate element already accepted into the target sequence
|
|
* and assign the designated payload value to it. */
|
|
virtual bool
|
|
assignElm (GenNode const& spec)
|
|
{
|
|
if (binding_.isApplicable(spec))
|
|
{
|
|
Iter target_found = binding_.locate (spec);
|
|
return target_found and binding_.assign (*target_found, spec);
|
|
}
|
|
else
|
|
return PAR::assignElm (spec);
|
|
}
|
|
|
|
/** locate the designated target element and build a suitable
|
|
* sub-mutator for this element into the provided target buffer */
|
|
virtual bool
|
|
mutateChild (GenNode const& spec, TreeMutator::Handle targetBuff)
|
|
{
|
|
if (binding_.isApplicable(spec))
|
|
{
|
|
Iter target_found = binding_.locate (spec);
|
|
return target_found and binding_.openSub (*target_found, spec.idi, targetBuff);
|
|
}
|
|
else
|
|
return PAR::mutateChild (spec, targetBuff);
|
|
}
|
|
|
|
/** verify all our pending (old) source elements where mentioned.
|
|
* @note allows chained "onion-layers" to clean-up and verify.*/
|
|
virtual bool
|
|
completeScope()
|
|
{
|
|
return PAR::completeScope()
|
|
and isnil(this->pos_);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Nested DSL to define the specifics of a collection binding.
|
|
*/
|
|
template<class COLL, class MAT, class CTR, class SEL, class ASS, class MUT>
|
|
struct CollectionBindingBuilder
|
|
: CollectionBinding<COLL,MAT,CTR,SEL,ASS,MUT>
|
|
{
|
|
using CollectionBinding<COLL,MAT,CTR,SEL,ASS,MUT>::CollectionBinding;
|
|
|
|
template<class FUN>
|
|
CollectionBindingBuilder<COLL, FUN ,CTR,SEL,ASS,MUT>
|
|
matchElement (FUN matcher) ///< expected lambda: `bool(GenNode const& spec, Elm const& elm)`
|
|
{
|
|
return { this->collection
|
|
, matcher
|
|
, this->construct
|
|
, this->isApplicable
|
|
, this->assign
|
|
, this->openSub
|
|
};
|
|
}
|
|
|
|
template<class FUN>
|
|
CollectionBindingBuilder<COLL,MAT, FUN ,SEL,ASS,MUT>
|
|
constructFrom (FUN constructor) ///< expected lambda: `Elm (GenNode const&)`
|
|
{
|
|
return { this->collection
|
|
, this->matches
|
|
, constructor
|
|
, this->isApplicable
|
|
, this->assign
|
|
, this->openSub
|
|
};
|
|
}
|
|
|
|
template<class FUN>
|
|
CollectionBindingBuilder<COLL,MAT,CTR, FUN ,ASS,MUT>
|
|
isApplicableIf (FUN selector) ///< expected lambda: `bool(GenNode const&)`
|
|
{
|
|
return { this->collection
|
|
, this->matches
|
|
, this->construct
|
|
, selector
|
|
, this->assign
|
|
, this->openSub
|
|
};
|
|
}
|
|
|
|
template<class FUN>
|
|
CollectionBindingBuilder<COLL,MAT,CTR,SEL, FUN ,MUT>
|
|
assignElement (FUN setter) ///< expected lambda: `bool(Elm&, GenNode const&)`
|
|
{
|
|
return { this->collection
|
|
, this->matches
|
|
, this->construct
|
|
, this->isApplicable
|
|
, setter
|
|
, this->openSub
|
|
};
|
|
}
|
|
|
|
template<class FUN>
|
|
CollectionBindingBuilder<COLL,MAT,CTR,SEL,ASS, FUN >
|
|
buildChildMutator (FUN childMutationBuilder) ///< expected lambda: `bool(Elm&, GenNode::ID const&, TreeMutator::Handle)`
|
|
{
|
|
return { this->collection
|
|
, this->matches
|
|
, this->construct
|
|
, this->isApplicable
|
|
, this->assign
|
|
, childMutationBuilder
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
/** builder function to synthesise builder type from given functors */
|
|
template<class COLL, class MAT, class CTR, class SEL, class ASS, class MUT>
|
|
inline auto
|
|
createCollectionBindingBuilder (COLL& coll, MAT m, CTR c, SEL s, ASS a, MUT u)
|
|
{
|
|
using Coll = typename Strip<COLL>::TypeReferred;
|
|
|
|
return CollectionBindingBuilder<Coll, MAT,CTR,SEL,ASS,MUT> {coll, m,c,s,a,u};
|
|
}
|
|
|
|
|
|
|
|
template<class ELM>
|
|
struct _EmptyBinding
|
|
{
|
|
static bool
|
|
__ERROR_missing_matcher (GenNode const&, ELM const&)
|
|
{
|
|
throw error::Logic ("unable to build a sensible default matching predicate");
|
|
}
|
|
|
|
static ELM
|
|
__ERROR_missing_constructor (GenNode const&)
|
|
{
|
|
throw error::Logic ("unable to build a sensible default for creating new elements");
|
|
}
|
|
|
|
static bool
|
|
ignore_selector (GenNode const&)
|
|
{
|
|
return true; // by default apply diff unconditionally
|
|
}
|
|
|
|
static bool
|
|
disable_assignment (ELM&, GenNode const&)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
disable_childMutation (ELM&, GenNode::ID const&, TreeMutator::Handle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
template<class COLL>
|
|
static auto
|
|
attachTo (COLL& coll)
|
|
{
|
|
return createCollectionBindingBuilder (coll
|
|
,__ERROR_missing_matcher
|
|
,__ERROR_missing_constructor
|
|
,ignore_selector
|
|
,disable_assignment
|
|
,disable_childMutation
|
|
);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
using lib::meta::enable_if;
|
|
using lib::diff::can_wrap_in_GenNode;
|
|
|
|
/**
|
|
* starting point for configuration of a binding to STL container.
|
|
* When using the "nested DSL" to setup a binding to child elements
|
|
* managed within a STL collection, all the variable and flexible
|
|
* aspects of the binding are preconfigured to a more or less
|
|
* disabled and inactive state. The resulting binding layer
|
|
* offers just minimal functionality. Typically you'd use
|
|
* the created (\ref CollectionBindingBuilder) to replace
|
|
* those defaults with lambdas tied into the actual
|
|
* implementation of the target data structure.
|
|
* @note depending on the payload type within the collection,
|
|
* we provide some preconfigured default specialisations
|
|
*/
|
|
template<class ELM, typename SEL =void>
|
|
struct _DefaultBinding
|
|
: _EmptyBinding<ELM>
|
|
{ };
|
|
|
|
template<class ELM>
|
|
struct _DefaultBinding<ELM, enable_if<can_wrap_in_GenNode<ELM>>>
|
|
{
|
|
template<class COLL>
|
|
static auto
|
|
attachTo (COLL& coll)
|
|
{
|
|
return _EmptyBinding<ELM>::attachTo(coll)
|
|
.matchElement([](GenNode const& spec, ELM const& elm)
|
|
{
|
|
return spec.matches(elm);
|
|
})
|
|
.constructFrom([](GenNode const& spec) -> ELM
|
|
{
|
|
return spec.data.get<ELM>();
|
|
});
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct _DefaultBinding<GenNode>
|
|
{
|
|
template<class COLL>
|
|
static auto
|
|
attachTo (COLL& coll)
|
|
{
|
|
return _EmptyBinding<GenNode>::attachTo(coll)
|
|
.matchElement([](GenNode const& spec, GenNode const& elm)
|
|
{
|
|
return spec.matches(elm);
|
|
})
|
|
.constructFrom([](GenNode const& spec) -> GenNode
|
|
{
|
|
return GenNode{spec};
|
|
})
|
|
.assignElement ([](GenNode& target, GenNode const& spec) -> bool
|
|
{
|
|
target.data = spec.data;
|
|
return true;
|
|
})
|
|
.buildChildMutator ([](GenNode& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
|
{
|
|
if (target.idi == subID // require match on already existing child object
|
|
and target.data.isNested())
|
|
{
|
|
Rec& nestedScope = target.data.get<Rec>();
|
|
buff.create (
|
|
TreeMutator::build()
|
|
.attach (mutateInPlace (nestedScope)));
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Entry point to a nested DSL
|
|
* for setup and configuration of a collection binding.
|
|
* This function shall be used right within Builder::attach()
|
|
* and wrap a language reference to the concrete collection
|
|
* implementing the "object children". The result is a default configured
|
|
* binding, which should be further adapted with the builder functions,
|
|
* using lambdas as callback into the otherwise opaque implementation code.
|
|
*/
|
|
template<class COLL>
|
|
inline auto
|
|
collection (COLL& coll)
|
|
{
|
|
using Elm = typename COLL::value_type;
|
|
|
|
return _DefaultBinding<Elm>::attachTo(coll);
|
|
}
|
|
|
|
|
|
|
|
/** Entry point for DSL builder */
|
|
template<class PAR>
|
|
template<class BIN>
|
|
inline auto
|
|
Builder<PAR>::attach (BIN&& collectionBindingSetup)
|
|
{
|
|
return chainedBuilder<ChildCollectionMutator<PAR,BIN>> (forward<BIN>(collectionBindingSetup));
|
|
}
|
|
|
|
|
|
}//(END)Mutator-Builder decorator components...
|
|
|
|
}} // namespace lib::diff
|
|
#endif /*LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H*/
|