LUMIERA.clone/src/lib/diff/tree-mutator-collection-binding.hpp
Ichthyostega e00d6c2a4c reorganise inclusion of TreeMutator-DSL builders
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!
2016-09-02 01:29:32 +02:00

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*/