LUMIERA.clone/src/lib/diff/tree-mutator-collection-binding.hpp
Ichthyostega 835c43027d add support for Ref::THIS (questionable, #996)
while simple to add into the implementation, this whole feature
seems rather qestionable to me now, thus I've added a Ticket
to be revisited later.

In a nutshell, right here, when implementing the binding layer
for STL collections, it is easy to enable the framework to treat
Ref::THIS properly, but the *actual implementation* will necessarily
be offloaded onto each and every concrete binding implementation.
Thus client code would have to add support for an rather obscure
shortcut within the Diff language. The only way to avoid this
would be to change the semantics of the "match"-lambda: if this
binding would rather be a back-translation of implementation data
into GenNode::ID values, then we'd be able to implement Ref::THIS
natively. But such an approach looks like a way inferiour deisgn
to me; having delegated the meaning of a "match" to the client
seems like an asset, since it is both natural and opens a lot
of flexibility, without adding complexity.

For that reason I tend to avoid that shortcut now, in the hope
to be able to drop it entirely from the language
2016-04-18 01:21:38 +02:00

509 lines
17 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 with specific builder templates
** is included way down, after the class definitions. This is done so for sake
** of readability.
**
** @see tree-mutator-test.cpp
** @see TreeMutator::build()
**
*/
#ifndef LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H
#define LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H
#ifndef LIB_DIFF_TREE_MUTATOR_H
#error "this header shall not be used standalone (see tree-mutator.hpp)"
#endif
//== anonymous namespace...
using lib::meta::Strip;
using lib::diff::GenNode;
using lib::iter_stl::eachElm;
/** verify the installed functors or lambdas expose the expected signature */
#define ASSERT_VALID_SIGNATURE(_FUN_, _SIG_) \
static_assert (has_Sig<_FUN_, _SIG_>::value, "Function " STRINGIFY(_FUN_) " unsuitable, expected signature: " STRINGIFY(_SIG_));
/**
* 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 closure to determine if a child matches a diff spec (GenNode)
* @tparam CTR a closure 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 closure to assign / set a new value from a given diff spec
* @tparam MUT a closure 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::MutatorBuffer))
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())
{ }
/* ==== re-Implementation of the operation API ==== */
/** skip next pending src element,
* causing this element to be discarded
*/
virtual void
skipSrc () override
{
if (pos_)
++pos_;
}
/** fabricate a new element, based on
* the given specification (GenNode),
* and insert it at current position
* into the target sequence.
*/
virtual void
injectNew (GenNode const& n) override
{
binding_.inject (binding_.construct(n));
}
virtual bool
emptySrc () override
{
return !pos_;
}
/** ensure the next recorded source element
* matches on a formal level with given spec */
virtual bool
matchSrc (GenNode const& spec) override
{
return pos_? binding_.matches (spec, *pos_)
: false;
}
/** accept existing element, when matching the given spec */
virtual bool
acceptSrc (GenNode const& n) override
{
bool isSrcMatch = ChildCollectionMutator::matchSrc(n);
if (isSrcMatch) // NOTE: crucial to call our own method here, not the virtual function
{
binding_.inject (move(*pos_));
++pos_;
}
return isSrcMatch;
}
/** locate designated element and accept it at current position */
virtual bool
findSrc (GenNode const& refSpec) override
{
Iter found = binding_.search (refSpec, pos_);
if (found)
{
binding_.inject (move(*found));
}
return found;
}
/** repeatedly accept, until after the designated location */
virtual bool
accept_until (GenNode const& spec)
{
bool foundTarget = true;
if (spec.matches (Ref::END))
for ( ; pos_; ++pos_)
binding_.inject (move(*pos_));
else
{
while (pos_ and not ChildCollectionMutator::matchSrc(spec))
{
binding_.inject (move(*pos_));
++pos_;
}
if (ChildCollectionMutator::matchSrc(spec))
{
binding_.inject (move(*pos_));
++pos_;
}
else
foundTarget = false;
}
return foundTarget;
}
/** locate element already accepted into the target sequence
* and assign the designated payload value to it. */
virtual bool
assignElm (GenNode const& spec)
{
Iter target_found = binding_.locate (spec);
return target_found? binding_.assign (*target_found, spec)
: false;
}
/** 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::MutatorBuffer targetBuff)
{
Iter target_found = binding_.locate (spec);
return target_found? binding_.openSub (*target_found, spec.idi, targetBuff)
: false;
}
};
/**
* 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)
{
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)
{
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)
{
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)
{
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)
{
return { this->collection
, this->matches
, this->construct
, this->isApplicable
, this->assign
, childMutationBuilder
};
}
};
using lib::meta::enable_if;
using lib::diff::can_wrap_in_GenNode;
template<typename ELM, typename SEL =void>
struct _DefaultPayload
{
static bool
match (GenNode const&, ELM const&)
{
throw error::Logic ("unable to build a sensible default matching predicate");
}
static ELM
construct (GenNode const&)
{
throw error::Logic ("unable to build a sensible default for creating new elements");
}
};
template<typename ELM>
struct _DefaultPayload<ELM, enable_if<can_wrap_in_GenNode<ELM>>>
{
static bool
match (GenNode const& spec, ELM const& elm)
{
return spec.matches(elm);
}
static ELM
construct (GenNode const& spec)
{
return spec.data.get<ELM>();
}
};
/**
* 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.
*/
template<class COLL>
struct _DefaultBinding
{
using Coll = typename Strip<COLL>::TypeReferred;
using Elm = typename Coll::value_type;
using Payload = _DefaultPayload<Elm>;
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::MutatorBuffer)
{
return false;
}
using FallbackBindingConfiguration
= CollectionBindingBuilder<Coll
,decltype(&Payload::match)
,decltype(&Payload::construct)
,decltype(&ignore_selector)
,decltype(&disable_assignment)
,decltype(&disable_childMutation)
>;
static FallbackBindingConfiguration
attachTo (Coll& coll)
{
return FallbackBindingConfiguration{ coll
, Payload::match
, Payload::construct
, ignore_selector
, disable_assignment
, disable_childMutation
};
}
};
/**
* 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>
auto
collection (COLL& coll) -> decltype(_DefaultBinding<COLL>::attachTo(coll))
{
return _DefaultBinding<COLL>::attachTo(coll);
}
#endif /*LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H*/