diff --git a/src/lib/diff/tree-mutator-collection-binding.hpp b/src/lib/diff/tree-mutator-collection-binding.hpp index 28c7ca874..1b2b696b7 100644 --- a/src/lib/diff/tree-mutator-collection-binding.hpp +++ b/src/lib/diff/tree-mutator-collection-binding.hpp @@ -198,7 +198,7 @@ - /* ==== re-Implementation of the operation API ==== */ + /* ==== Implementation of TreeNode operation API ==== */ /** fabricate a new element, based on * the given specification (GenNode), diff --git a/src/lib/diff/tree-mutator-gen-node-binding.hpp b/src/lib/diff/tree-mutator-gen-node-binding.hpp new file mode 100644 index 000000000..72015a541 --- /dev/null +++ b/src/lib/diff/tree-mutator-gen-node-binding.hpp @@ -0,0 +1,563 @@ +/* + TREE-MUTATOR-GEN-NODE-BINDING.hpp - diff::TreeMutator implementation building block + + Copyright (C) Lumiera.org + 2016, Hermann Vosseler + + 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-gen-node-binding.hpp + ** Special binding implementation for TreeMutator, allowing to map + ** tree diff operations onto an »External Tree Description«. Such is is a + ** DOM like representation of tree like structures, comprised of GenNode elements. + ** 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, layered on top of each other. This header defines a special setup, based + ** on the two layered bindings for STL collections. The reason is that our »External + ** Tree Description« of object-like structures is comprised of recursively nested + ** Record to represent "objects", and this representation is actually implemented + ** internally based on two collections -- one to hold the _attributes_ and one to hold the + ** _children._ So this special setup relies on implementation inside knowledge to apply + ** structural changes to such a representation. There is an implicit convention that + ** "objects" are to be spelled out by first giving the metadata, then enumerating the + ** attributes (key-value properties) and finally the child elements located within the + ** scope of this "object" node. This implicit convention is in accordance with the + ** structure of our _diff language_ -- thus it is sufficient just to layer two collection + ** bindings, together with suitable closures (lambdas) for layer selection, matching, etc. + ** + ** @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_GEN_NODE_BINDING_H +#define LIB_DIFF_TREE_MUTATOR_GEN_NODE_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; + + + + + /** + * 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 + struct CollectionBinding + { + using Coll = typename Strip::TypeReferred; + using Elm = typename Coll::value_type; + + using iterator = typename lib::iter_stl::_SeqT::Range; + using const_iterator = typename lib::iter_stl::_SeqT::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)); + } + + 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 ChildCollectionMutator + : public PAR + { + using Iter = typename BIN::iterator; + + BIN binding_; + Iter pos_; + + + public: + ChildCollectionMutator(BIN wiringClosures, PAR&& chain) + : PAR(std::forward(chain)) + , binding_(wiringClosures) + , pos_(binding_.initMutation()) + { } + + + + + /* ==== re-Implementation of the 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 + struct CollectionBindingBuilder + : CollectionBinding + { + using CollectionBinding::CollectionBinding; + + template + CollectionBindingBuilder + 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 + CollectionBindingBuilder + constructFrom (FUN constructor) ///< expected lambda: `Elm (GenNode const&)` + { + return { this->collection + , this->matches + , constructor + , this->isApplicable + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + isApplicableIf (FUN selector) ///< expected lambda: `bool(GenNode const&)` + { + return { this->collection + , this->matches + , this->construct + , selector + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + assignElement (FUN setter) ///< expected lambda: `bool(Elm&, GenNode const&)` + { + return { this->collection + , this->matches + , this->construct + , this->isApplicable + , setter + , this->openSub + }; + } + + template + CollectionBindingBuilder + buildChildMutator (FUN childMutationBuilder) ///< expected lambda: `bool(Elm&, GenNode::ID const&, TreeMutator::Handle)` + { + 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 + 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 + struct _DefaultPayload>> + { + static bool + match (GenNode const& spec, ELM const& elm) + { + return spec.matches(elm); + } + + static ELM + construct (GenNode const& spec) + { + return spec.data.get(); + } + }; + + /** + * 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 + struct _DefaultBinding + { + using Coll = typename Strip::TypeReferred; + using Elm = typename Coll::value_type; + + using Payload = _DefaultPayload; + + 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; + } + + + using FallbackBindingConfiguration + = CollectionBindingBuilder; + + 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 + auto + collection (COLL& coll) -> decltype(_DefaultBinding::attachTo(coll)) + { + return _DefaultBinding::attachTo(coll); + } + + + +#endif /*LIB_DIFF_TREE_MUTATOR_GEN_NODE_BINDING_H*/ diff --git a/src/lib/diff/tree-mutator.hpp b/src/lib/diff/tree-mutator.hpp index 5072ba008..b1462b9c7 100644 --- a/src/lib/diff/tree-mutator.hpp +++ b/src/lib/diff/tree-mutator.hpp @@ -378,6 +378,7 @@ namespace diff{ #include "lib/diff/tree-mutator-attribute-binding.hpp" #include "lib/diff/tree-mutator-collection-binding.hpp" +#include "lib/diff/tree-mutator-gen-node-binding.hpp" diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 7f22cae21..f8cd640e0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -45,8 +45,7 @@ heißt: Element registriert sich am UI-Bus

- - + @@ -58,8 +57,7 @@ heißt: Element deregistriert sich am UI-Bus

- -
+
@@ -73,8 +71,7 @@ ...ist immer ein tangible

- - +
@@ -134,8 +131,7 @@ dafür genügt der normale Reset

- - +
@@ -148,8 +144,7 @@ mark "clearMsg"

- - +
@@ -161,8 +156,7 @@ mark "clearErr"

- -
+
@@ -174,8 +168,7 @@ mark "reset"

- -
+
@@ -223,8 +216,7 @@ was haben alle UI-Elemente wirklich gemeinsam?

- - + @@ -242,8 +234,7 @@ oder handelt es sich nur um ein Implementierungsdetail der UI-Bus-Anbindung?

- -
+ @@ -331,8 +322,7 @@ Dann mußte das allerdigns jeweils für alle Elemente sinnvoll sein

- - +
@@ -345,8 +335,7 @@ und der muß vom konkreten Widget implementiert werden

- - +
@@ -508,8 +497,7 @@ Und ich muß das in einem Test zumindest emulieren können!

- - +
@@ -549,8 +537,7 @@ - - + @@ -603,8 +590,7 @@ ist jedoch schon prototypisch implementiert

- - + @@ -849,8 +835,7 @@ - - + @@ -877,8 +862,7 @@ - - + @@ -1030,8 +1014,7 @@ since skipSrc performs both the `del` and the `skip` verb, it can not perform the match itself...

- - +
@@ -1070,8 +1053,7 @@ to the next lower layer in both cases, and the result and behaviour depends on this next lower layer solely

- - +
@@ -1121,8 +1103,7 @@ of this specific onion layer to accept forward until meeting this element.

- - +
@@ -1151,8 +1132,7 @@ will check the bool return value and throw an exception in that case

- - +
@@ -1233,8 +1213,7 @@ Wichtigster solcher Fall ist die Bindung auf Objekt-Felder

- - +
@@ -1283,8 +1262,7 @@ to construct the new target data efficiently in place.

- - +
@@ -1887,8 +1865,7 @@ durch den das Problem mit der "absrakten, opaquen" Position entschärft wird

- - +
@@ -2116,8 +2093,7 @@ und protokolliert somit "nebenbei" was an Anforderungen an ihm vorbeigeht

- - +
@@ -2367,8 +2343,7 @@ für spezifische Arten von Bindings

- - + @@ -2410,8 +2385,7 @@ denn sonst würde er es für darunter liegende Layer verschatten.

- - +
@@ -2656,8 +2630,7 @@ eine womöglich irreführende Meldung generiert

- - +
@@ -2869,8 +2842,7 @@ dann müssen Attribute irgendwie sinnvoll integriert sein

- - +
@@ -4274,7 +4246,7 @@ - + @@ -4306,6 +4278,24 @@ + + + + + + + +

+ zwei Collection-Bindings +

+ + +
+ +
+ + + @@ -4360,8 +4350,7 @@ In jedem Fall gerät dadurch die relative Verzahnung der Elemente untereinander aus dem Takt

- - +
@@ -4389,14 +4378,13 @@ und diese Elemente müssen geschlossen hintereinander in der Reihenfolge liegen

- - +
- + @@ -4434,8 +4422,7 @@ - - + @@ -4458,8 +4445,7 @@ Letzten Endes lief  das auch in diesem Fall auf inline-Storage hinaus...

- - + @@ -4540,8 +4526,7 @@ Standard == Interface DiffMutable implementieren

- - +
@@ -4659,8 +4644,7 @@ daß sie uns im gegebenen Kontext umbringen...

- - +
@@ -4852,8 +4836,7 @@ - - +
@@ -4880,8 +4863,7 @@ -- wie bzw. von wem bekommen wir dann ein Binding, das einen passenden TreeMutator konstruiert?

- - + @@ -4931,8 +4913,7 @@ und zwar per handle.get()

- - +
@@ -4955,8 +4936,7 @@ Ich empfinde das als schlechten Stil

- - +
@@ -4975,8 +4955,7 @@ weil die Implementierung den Zeiger auf den geschachtelen sub-Mutator umsetzen muß.

- - +
@@ -5003,8 +4982,7 @@ dieser zumindest einmal per ins "angelegt" wurde.

- - +
@@ -5052,8 +5030,7 @@ und daher auch jeweils eigens per Unit-Test abgedeckt werden.

- - +
@@ -5074,8 +5051,7 @@ der konkreten Bindings mit mehreren "onion layers"!

- -
+
@@ -5105,8 +5081,7 @@ ...denn es ist sehr verwirrend, welche Signatur denn nun die Lambdas haben müssen

- - +
@@ -5120,8 +5095,7 @@ ...denn es kann keinen Default-Matcher geben....

- - +
@@ -5155,8 +5129,7 @@ daß der Client hier eigentlich ein Protokoll implementieren muß.

- - +
@@ -5202,8 +5175,7 @@ wenn überhaupt, dann im Matcher im Binding-Layer implementieren

- - +
@@ -5664,8 +5636,7 @@ Implementierung der real-world-Variante fehlt!

- - +