2015-04-02 03:30:20 +02:00
|
|
|
/*
|
|
|
|
|
TREE-MUTATOR.hpp - flexible binding to map generic tree changing operations
|
|
|
|
|
|
|
|
|
|
Copyright (C) Lumiera.org
|
|
|
|
|
2015, 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.hpp
|
|
|
|
|
** Customisable intermediary to abstract generic tree mutation operations.
|
|
|
|
|
** This is the foundation for generic treatment of tree altering operations,
|
2015-04-13 15:49:38 +02:00
|
|
|
** and especially the handling of changes (diff) to hierarchical data structures.
|
2015-04-02 03:30:20 +02:00
|
|
|
** The goal is to represent a standard set of conceptual operations working on
|
|
|
|
|
** arbitrary data structures, without the need for these data structures to
|
|
|
|
|
** comply to any interface or base type. Rather, we allow each instance to
|
2015-04-13 15:49:38 +02:00
|
|
|
** define binding closures, which allows to tap into arbitrary internal data
|
2015-04-02 03:30:20 +02:00
|
|
|
** representation, without any need of disclosure. The only assumption is
|
|
|
|
|
** that the data to be treated is \em hierarchical and \em object-like,
|
|
|
|
|
** i.e. it has (named) attributes and it may have a collection of children.
|
|
|
|
|
** If necessary, typing constraints can be integrated through symbolic
|
|
|
|
|
** representation of types as chained identifiers. (path dependent types).
|
|
|
|
|
**
|
|
|
|
|
** The interface implemented by the TreeMutator is shaped such as to support
|
|
|
|
|
** the primitives of Lumiera's tree \link diff-language.hpp diff handling language. \endlink
|
|
|
|
|
** By default, each of these primitives is implemented as a \c NOP -- but each operation
|
|
|
|
|
** can be replaced by a binding closure, which allows to invoke arbitrary code in the
|
|
|
|
|
** context of the given object's implementation internals.
|
|
|
|
|
**
|
2016-02-27 01:47:33 +01:00
|
|
|
** ## Builder/Adapter concept
|
|
|
|
|
** TreeMutator is both an interface and a set of building blocks.
|
|
|
|
|
** On concrete usage, the (private, non disclosed) target data structure is assumed
|
|
|
|
|
** to _build a subclass of TreeMutator._ To this end, the TreeMutator is complemented
|
2016-03-26 02:01:31 +01:00
|
|
|
** by a **builder DSL**. Each call on this builder -- typically providing some closure --
|
|
|
|
|
** will add yet another _decorating layer_ on top of the basic TreeMutator (recall all
|
2016-02-27 01:47:33 +01:00
|
|
|
** the "mutation primitives" are implemented NOP within the base class). So the actual
|
|
|
|
|
** TreeMutator will be structured like an onion, where each layer cares for the sole
|
|
|
|
|
** concrete aspect it was tied for by the supplied closure. For example, there might
|
|
|
|
|
** be a decorator to handle setting of a "foobar" attribute. Thus, when the diff
|
|
|
|
|
** dictates to mutate "foobar", the corresponding closure will be invoked.
|
|
|
|
|
**
|
|
|
|
|
** \par test dummy target
|
|
|
|
|
** There is a special adapter binding to support writing unit tests. The corresponding
|
|
|
|
|
** API is only declared (forward) by default. The TestMutationTarget is a helper class,
|
|
|
|
|
** which can be attached through this binding and allows a unit test fixture to record
|
|
|
|
|
** and verify all the mutation operations encountered.
|
2016-03-26 02:01:31 +01:00
|
|
|
**
|
|
|
|
|
** ## Lifecycle
|
|
|
|
|
** The TreeMutator is conceived as a disposable, one-way-off object. On initialisation,
|
|
|
|
|
** it will _"grab" the contents of its target_ and push them back into place one by one
|
|
|
|
|
** while consuming a mutation diff. For this reason, TreeMutator is made **non-copyable**,
|
|
|
|
|
** just supporting move construction, as will happen when using the DSL functions on
|
|
|
|
|
** the builder. This is also the only supported usage pattern: you create an anonymous
|
|
|
|
|
** TreeMutator sub type by using the Builder functions right within the scope about to
|
|
|
|
|
** consume one strike of diff messages. These messages should cover anything to confirm
|
|
|
|
|
** or reshape _all of the target's contents_. After that, you must not refer to the
|
|
|
|
|
** exhausted TreeMutator anymore, just let it fall out of scope. Incidentally, this
|
|
|
|
|
** also means that _any failure or exception encountered_ while applying a diff will
|
2016-06-09 01:10:52 +02:00
|
|
|
** leave a **corrupted target data structure**. The basic assumption is that
|
2016-03-26 02:01:31 +01:00
|
|
|
** - the target data structure will actually be built through diff messages solely
|
|
|
|
|
** - and that all received diff messages are sane, as being drawn from a
|
|
|
|
|
** semantically and structurally equivalent source structure
|
|
|
|
|
** If unable to uphold this consistency assumption, it is the client's responsibility
|
|
|
|
|
** to care for _transactional behaviour,_ i.e. create a clone copy of the data structure
|
|
|
|
|
** beforehand, and "commit" or "roll back" the result atomically.
|
2016-03-18 20:03:27 +01:00
|
|
|
**
|
|
|
|
|
** @note to improve readability, the actual implementation of the "binding layers"
|
|
|
|
|
** is defined in separate headers and included towards the bottom of this header.
|
2016-02-27 01:47:33 +01:00
|
|
|
**
|
2016-02-26 22:57:49 +01:00
|
|
|
** @see tree-mutator-test.cpp
|
2016-06-09 01:10:52 +02:00
|
|
|
** @see tree-mutator-binding-test.cpp
|
|
|
|
|
** @see diff-language.hpp
|
2015-04-02 03:30:20 +02:00
|
|
|
** @see DiffDetector
|
|
|
|
|
**
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef LIB_DIFF_TREE_MUTATOR_H
|
|
|
|
|
#define LIB_DIFF_TREE_MUTATOR_H
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "lib/error.hpp"
|
|
|
|
|
#include "lib/symbol.hpp"
|
2016-03-18 00:31:04 +01:00
|
|
|
#include "lib/meta/trait.hpp"
|
2015-05-02 01:11:39 +02:00
|
|
|
#include "lib/diff/gen-node.hpp"
|
2016-03-06 02:26:42 +01:00
|
|
|
#include "lib/opaque-holder.hpp"
|
2016-03-25 20:46:48 +01:00
|
|
|
#include "lib/iter-adapter-stl.hpp"
|
2016-05-28 01:49:03 +02:00
|
|
|
#include "lib/format-string.hpp"
|
2016-05-28 03:41:03 +02:00
|
|
|
#include "lib/idi/entry-id.hpp"
|
2015-04-02 03:30:20 +02:00
|
|
|
|
2016-03-26 02:01:31 +01:00
|
|
|
#include <utility>
|
2015-04-02 03:30:20 +02:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
|
|
|
|
namespace diff{
|
|
|
|
|
|
|
|
|
|
namespace error = lumiera::error;
|
|
|
|
|
|
2016-05-28 03:41:03 +02:00
|
|
|
using lib::Symbol;
|
2015-04-02 03:30:20 +02:00
|
|
|
using std::string;
|
2016-05-28 01:49:03 +02:00
|
|
|
using util::_Fmt;
|
2016-06-04 14:20:59 +02:00
|
|
|
using lib::idi::BareEntryID;
|
2015-04-02 03:30:20 +02:00
|
|
|
|
2016-02-27 01:47:33 +01:00
|
|
|
|
2016-03-06 02:26:42 +01:00
|
|
|
|
|
|
|
|
|
2016-02-27 01:47:33 +01:00
|
|
|
class TestMutationTarget; // for unit testing
|
|
|
|
|
|
|
|
|
|
|
2015-04-02 03:30:20 +02:00
|
|
|
namespace {
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
template<class PAR>
|
2015-08-16 00:16:30 +02:00
|
|
|
struct Builder;
|
2015-04-02 03:30:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2016-03-03 23:11:36 +01:00
|
|
|
* Customisable intermediary to abstract mutating operations
|
|
|
|
|
* on arbitrary, hierarchical object-like data.
|
2015-04-03 20:10:22 +02:00
|
|
|
* The TreeMutator exposes two distinct interfaces
|
|
|
|
|
* - the \em operation API -- similar to what a container exposes --
|
|
|
|
|
* is the entirety of abstract operations that can be done to the
|
|
|
|
|
* subsumed, tree like target structure
|
|
|
|
|
* - the \em binding API allows to link some or all of these generic
|
|
|
|
|
* activities to concrete manipulations known within target scope.
|
2015-04-02 03:30:20 +02:00
|
|
|
*/
|
|
|
|
|
class TreeMutator
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public:
|
2016-09-04 23:21:15 +02:00
|
|
|
virtual ~TreeMutator(); ///< this is an interface
|
|
|
|
|
|
2016-03-26 02:01:31 +01:00
|
|
|
/** only allow default and move construction */
|
|
|
|
|
TreeMutator () =default;
|
|
|
|
|
TreeMutator (TreeMutator&&) =default;
|
|
|
|
|
TreeMutator (TreeMutator const&) =delete;
|
|
|
|
|
|
|
|
|
|
TreeMutator& operator= (TreeMutator const&) =delete;
|
|
|
|
|
TreeMutator& operator= (TreeMutator&&) =delete;
|
|
|
|
|
|
|
|
|
|
|
2015-04-03 20:10:22 +02:00
|
|
|
|
|
|
|
|
/* ==== operation API ==== */
|
|
|
|
|
|
2016-10-03 23:54:09 +02:00
|
|
|
/** initialisation immediately before start of diff application
|
|
|
|
|
* @remark allows for setup of state which is dependent on memory location,
|
|
|
|
|
* like e.g. iterators. Due to the invokation via Builder DSL, the
|
|
|
|
|
* implementation object may be moved after construction, but prior
|
|
|
|
|
* to invoking this hook
|
|
|
|
|
*/
|
|
|
|
|
virtual void
|
|
|
|
|
init() { }
|
|
|
|
|
|
2016-03-04 23:18:25 +01:00
|
|
|
virtual bool
|
2016-08-07 01:58:26 +02:00
|
|
|
hasSrc () ////////////////////////////////////TODO questionable if we need it. Can not be sensibly implemented on multiple onion-layers!
|
2016-03-04 23:18:25 +01:00
|
|
|
{
|
2016-05-24 21:34:08 +02:00
|
|
|
return false;
|
2016-03-04 23:18:25 +01:00
|
|
|
// do nothing by default
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-28 01:17:45 +02:00
|
|
|
/** establish new element at current position
|
|
|
|
|
* @return `true` when successfully inserted something */
|
|
|
|
|
virtual bool
|
2016-03-04 21:26:25 +01:00
|
|
|
injectNew (GenNode const&)
|
2015-04-03 20:10:22 +02:00
|
|
|
{
|
2016-03-04 21:26:25 +01:00
|
|
|
// do nothing by default
|
2016-05-28 01:17:45 +02:00
|
|
|
return false;
|
2015-04-03 20:10:22 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 21:26:25 +01:00
|
|
|
/** ensure the next source element matches with given spec */
|
2016-03-04 21:13:49 +01:00
|
|
|
virtual bool
|
2016-03-04 21:26:25 +01:00
|
|
|
matchSrc (GenNode const&)
|
2015-04-03 20:10:22 +02:00
|
|
|
{
|
2016-03-04 21:26:25 +01:00
|
|
|
// do nothing by default
|
|
|
|
|
return false;
|
2015-04-03 20:10:22 +02:00
|
|
|
}
|
|
|
|
|
|
investigate and confirm the logic underlying the matchSrc, skipSrc and acceptSrc primitives
In Theory, acceptSrc and skipSrc are to operate symmetrically,
with the sole difference that skipSrc does not move anything
into the new content.
BUT, since skipSrc is also used to implement the `skip` verb,
which serves to discard garbage left back by a preceeding `find`,
we cannot touch the data found in the src position without risk
of SEGFAULT. For this reason, there is a dedicated matchSrc operation,
which shall be used to generate the verification step to properly
implement the `del` verb.
I've spent quite some time to verify the logic of predicate evaluation.
It seems to be OK: whenever the SELECTOR applies, then we'll perform
the local match, and then also we'll perform the skipSrc. Otherwise,
we'll delegate both operations likewise to the next lower layer,
without touching anything here.
2016-08-09 23:42:42 +02:00
|
|
|
/** skip next src element and advance abstract source position.
|
|
|
|
|
* The argument shall be used to determine applicability
|
|
|
|
|
* @remarks this operation is used both to implement the `del` verb
|
|
|
|
|
* and the `skip` verb. Since the latter discards garbage
|
|
|
|
|
* left back by `find` we must not touch the contents,
|
|
|
|
|
* to prevent a SEGFAULT. Thus `skipSrc` can not match
|
|
|
|
|
* and thus can not return anything. Consequently the
|
|
|
|
|
* `del` implementation has to use `matchSrc` explicitly,
|
|
|
|
|
* and the latter must invoke the selector prior to
|
|
|
|
|
* performing the local match. */
|
|
|
|
|
virtual void
|
|
|
|
|
skipSrc (GenNode const&)
|
|
|
|
|
{
|
|
|
|
|
// do nothing by default
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 21:26:25 +01:00
|
|
|
/** accept existing element, when matching the given spec */
|
2016-03-04 21:13:49 +01:00
|
|
|
virtual bool
|
2016-03-04 21:26:25 +01:00
|
|
|
acceptSrc (GenNode const&)
|
2016-03-04 21:13:49 +01:00
|
|
|
{
|
2016-03-04 21:26:25 +01:00
|
|
|
// do nothing by default
|
|
|
|
|
return false;
|
2016-03-04 21:13:49 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-06 03:55:31 +01:00
|
|
|
/** repeatedly accept, until after the designated location */
|
|
|
|
|
virtual bool
|
2016-09-02 18:40:16 +02:00
|
|
|
accept_until (GenNode const& spec)
|
2016-03-06 03:55:31 +01:00
|
|
|
{
|
2016-09-02 18:40:16 +02:00
|
|
|
return (Ref::END == spec or Ref::ATTRIBS == spec);
|
|
|
|
|
// contents are exhausted by default,
|
|
|
|
|
// yet we're unable to find something specific
|
2016-03-06 03:55:31 +01:00
|
|
|
}
|
|
|
|
|
|
2016-03-04 21:26:25 +01:00
|
|
|
/** locate designated element and accept it at current position */
|
2016-03-04 21:13:49 +01:00
|
|
|
virtual bool
|
2016-03-04 21:26:25 +01:00
|
|
|
findSrc (GenNode const&)
|
2015-04-03 20:10:22 +02:00
|
|
|
{
|
2016-03-04 21:26:25 +01:00
|
|
|
// do nothing by default
|
|
|
|
|
return false;
|
2015-04-03 20:10:22 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-06 02:26:42 +01:00
|
|
|
/** locate the designated target element
|
|
|
|
|
* (must be already accepted into the target sequence).
|
2016-03-25 21:40:30 +01:00
|
|
|
* Perform an assignment with the given payload value
|
|
|
|
|
* @throw when assignment fails (typically error::Logic)
|
2016-03-06 02:26:42 +01:00
|
|
|
* @return false when unable to locate the target */
|
|
|
|
|
virtual bool
|
|
|
|
|
assignElm (GenNode const&)
|
2015-04-03 20:10:22 +02:00
|
|
|
{
|
2016-03-06 02:26:42 +01:00
|
|
|
// do nothing by default
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-06-09 01:18:21 +02:00
|
|
|
using Handle = PlantingHandle<TreeMutator>;
|
2016-03-06 02:26:42 +01:00
|
|
|
|
|
|
|
|
/** locate the designated target element
|
2016-03-25 21:40:30 +01:00
|
|
|
* and build a suitable sub-mutator for this element
|
2016-03-06 02:26:42 +01:00
|
|
|
* into the provided target buffer
|
|
|
|
|
* @throw error::Fatal when buffer is insufficient
|
2016-08-26 02:42:19 +02:00
|
|
|
* @return `false` when unable to locate the target */
|
2016-03-06 02:26:42 +01:00
|
|
|
virtual bool
|
2016-06-09 01:18:21 +02:00
|
|
|
mutateChild (GenNode const&, Handle)
|
2016-03-06 02:26:42 +01:00
|
|
|
{
|
|
|
|
|
// do nothing by default
|
|
|
|
|
return false;
|
2015-04-03 20:10:22 +02:00
|
|
|
}
|
|
|
|
|
|
2016-05-24 22:23:06 +02:00
|
|
|
/** ensure the scope addressed by this TreeMutator
|
|
|
|
|
* was processed and exhausted without mismatch
|
|
|
|
|
* @return `true` when all "open ends" are closed
|
|
|
|
|
* and no pending work remains to be done. */
|
|
|
|
|
virtual bool
|
|
|
|
|
completeScope()
|
|
|
|
|
{
|
|
|
|
|
// nothing to clean-up or verify by default
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-28 03:41:03 +02:00
|
|
|
|
2015-04-03 20:10:22 +02:00
|
|
|
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
/**
|
2016-05-28 03:41:03 +02:00
|
|
|
* DSL: start building a custom adapted tree mutator,
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
* where the operations are tied by closures or
|
|
|
|
|
* wrappers into the current implementation context.
|
|
|
|
|
*/
|
|
|
|
|
static Builder<TreeMutator> build();
|
2015-04-02 03:30:20 +02:00
|
|
|
};
|
|
|
|
|
|
2016-03-03 23:11:36 +01:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-02-27 01:47:33 +01:00
|
|
|
namespace { // Mutator-Builder decorator components...
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
|
2017-03-19 02:07:18 +01:00
|
|
|
using lib::meta::_Fun;
|
2016-03-26 02:01:31 +01:00
|
|
|
using std::forward;
|
|
|
|
|
using std::move;
|
2016-03-18 00:31:04 +01:00
|
|
|
|
2016-03-25 02:25:51 +01:00
|
|
|
|
|
|
|
|
template<typename FUN, typename SIG>
|
|
|
|
|
struct has_Sig
|
2017-03-19 02:07:18 +01:00
|
|
|
: std::is_same<SIG, typename _Fun<FUN>::Sig>
|
2016-03-25 02:25:51 +01:00
|
|
|
{ };
|
2015-05-03 05:24:06 +02:00
|
|
|
|
2016-06-04 14:20:59 +02:00
|
|
|
/** 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_));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-05-03 05:24:06 +02:00
|
|
|
|
2016-03-18 19:35:48 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-03-18 20:52:35 +01:00
|
|
|
/**
|
|
|
|
|
* Builder-DSL to create and configure a concrete TreeMutator
|
|
|
|
|
* @remarks all generated follow-up builders are chained and
|
|
|
|
|
* derive from the implementation of the preceding
|
|
|
|
|
* "binding layer" and the TreeMutator interface.
|
2016-03-26 02:01:31 +01:00
|
|
|
* @note on each chained builder call, the compound is
|
|
|
|
|
* moved "inside out" into the next builder.
|
2016-03-18 20:52:35 +01:00
|
|
|
*/
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
template<class PAR>
|
|
|
|
|
struct Builder
|
|
|
|
|
: PAR
|
|
|
|
|
{
|
2016-03-26 00:48:38 +01:00
|
|
|
Builder(PAR&& par)
|
2016-03-26 02:01:31 +01:00
|
|
|
: PAR{forward<PAR> (par)}
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
{ }
|
|
|
|
|
|
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
|
|
|
template<typename BIN, typename...ARGS>
|
|
|
|
|
Builder<BIN>
|
|
|
|
|
chainedBuilder (ARGS&&...args)
|
|
|
|
|
{
|
|
|
|
|
return Builder<BIN> (BIN{forward<ARGS>(args)..., move(*this)});
|
|
|
|
|
}
|
2016-03-18 19:35:48 +01:00
|
|
|
|
2016-02-27 01:47:33 +01:00
|
|
|
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
|
|
|
|
|
/* ==== binding API ==== */
|
|
|
|
|
|
2016-05-28 03:41:03 +02:00
|
|
|
/** set up a binding to represent an "attribute"
|
|
|
|
|
* through a data or object field. This binding will allow
|
|
|
|
|
* to apply basic diff operations, _but no re-ordering or deletion._
|
|
|
|
|
* Rationale is the fixed nature of a class definition, which does not
|
|
|
|
|
* support any notion of ordering, or adding and removal of members.
|
|
|
|
|
* @param attributeID symbolic key to denote this "attribute"
|
|
|
|
|
* @param setterClosure functor or lambda to apply a new value
|
|
|
|
|
* @note the nominal value type of the "attribute" is picked up from
|
|
|
|
|
* the setterClosure's (single) argument. It must be one of the
|
|
|
|
|
* types supported as payload for GenNode. In case the target
|
|
|
|
|
* data field needs any other value type, it is the closure's
|
|
|
|
|
* responsibility to convert appropriately.
|
|
|
|
|
* @note the combination of attributeID and nominal value type is used
|
|
|
|
|
* to build an (\ref EntryID). The hash of this EntryID needs to
|
|
|
|
|
* match the GenNode::ID in any diff verb considered to be
|
|
|
|
|
* "applicable" to this attribute and binding. Similar to
|
|
|
|
|
* GenNode, the provided attributeID is used as-is,
|
|
|
|
|
* without further sanitising.
|
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
|
|
|
* @return a _chained builder,_ which establishes this building and
|
|
|
|
|
* can then be used to define additional binding layers on top
|
2016-05-28 03:41:03 +02:00
|
|
|
*/
|
2015-05-03 05:24:06 +02:00
|
|
|
template<typename CLO>
|
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
|
|
|
auto change (Symbol attributeID, CLO setterClosure);
|
|
|
|
|
|
2016-02-27 01:47:33 +01:00
|
|
|
|
2016-08-29 22:14:03 +02:00
|
|
|
/** set up a binding for an object valued "attribute" or _named scope_.
|
|
|
|
|
* This covers the rather special case, where some relevant sub object is
|
|
|
|
|
* accessed as a (named) property of a managing parent object. On implementation
|
|
|
|
|
* level, this corresponds to using a _getter_ to access a subcomponent or "PImpl".
|
|
|
|
|
* On a formal level, for tree diff handling, such counts as _attribute_, yet with
|
|
|
|
|
* the special twist that we can not just assign a new "value", but rather have to
|
|
|
|
|
* enter a sub scope and handle a nested diff -- similar to how nested child objects
|
|
|
|
|
* are dealt with in general. Thus, all we need here is a way how to build a nested
|
|
|
|
|
* TreeMutator for this sub-scope.
|
|
|
|
|
* @param attributeID symbolic key to denote this "attribute"
|
|
|
|
|
* @param mutatorBuilderClosure functor or lambda to emplace a custom sub TreeMutator
|
|
|
|
|
* into the given buffer (handle). Such a nested mutator shall be wired internally
|
|
|
|
|
* to the object representation of the attribute in question.
|
|
|
|
|
* @see CollectionBindingBuilder::buildChildMutator
|
|
|
|
|
*/
|
2016-06-05 17:26:48 +02:00
|
|
|
template<typename CLO>
|
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
|
|
|
auto mutateAttrib (Symbol attributeID, CLO mutatorBuilderClosure);
|
2016-06-05 17:26:48 +02:00
|
|
|
|
2016-08-29 22:14:03 +02:00
|
|
|
///////////////////////////////////////TODO define variant taking a GenNode::ID ??
|
|
|
|
|
|
2016-06-05 17:26:48 +02:00
|
|
|
|
2016-03-18 20:52:35 +01:00
|
|
|
/** set up a binding to a structure of "child objects",
|
|
|
|
|
* implemented through a typical STL container
|
|
|
|
|
* @param collectionBindingSetup as created by invoking a nested DSL,
|
|
|
|
|
* initiated by a builder function `collection(implRef)`, where `implRef`
|
|
|
|
|
* is a (language) reference to a STL compliant container existing somewhere
|
|
|
|
|
* within the otherwise opaque implementation. The type of the container and
|
|
|
|
|
* thus the type of the elements will be picked up, and the returned builder
|
2016-03-25 21:40:30 +01:00
|
|
|
* can be further outfitted with the builder methods, which take lambdas as
|
2016-03-18 20:52:35 +01:00
|
|
|
* callback into the implementation.
|
2016-08-29 22:14:03 +02:00
|
|
|
* - the _matcher closure_ (CollectionBindingBuilder::matchElement) defines
|
|
|
|
|
* how to determine, if an implementation data element "matches" a given diff spec
|
|
|
|
|
* - the _constructor closure_ (CollectionBindingBuilder::constructFrom) defines how
|
2016-10-03 20:08:54 +02:00
|
|
|
* to build a new implementation data element from the spec of an `INS` diff verb.
|
|
|
|
|
* Note: the result will be moved (move-constructed) into the target container.
|
2016-08-29 22:14:03 +02:00
|
|
|
* - the optional _selector closure_ (CollectionBindingBuilder::isApplicableIf)
|
|
|
|
|
* allows to limit applicability of this whole binding (layer) to only some
|
|
|
|
|
* diff specs. E.g., we may set up a binding for elements with value semantics
|
2016-09-02 18:40:16 +02:00
|
|
|
* and another binding layer on top to deal with object like children (sub scopes).
|
|
|
|
|
* Please note that this selector also gets to judge the Ref::ATTRIBS spec, which
|
|
|
|
|
* means this layer's contents can be considered "attributes".
|
2016-08-29 22:14:03 +02:00
|
|
|
* - the optional _setter closure_ (CollectionBindingBuilder::assignElement) accepts
|
|
|
|
|
* a diff spec (GenNode) and should assign an equivalent value to the internal
|
|
|
|
|
* data representation of the corresponding element (typically by constructing
|
|
|
|
|
* an implementation data element and then invoking the corresponding setter)
|
|
|
|
|
* - the optional _mutator closure_ (CollectionBindingBuilder::buildChildMutator)
|
|
|
|
|
* allows for recursive descent into nested child scopes. On invocation, it has
|
|
|
|
|
* to build a suitable custom TreeMutator implementation into the provided buffer
|
|
|
|
|
* (handle), and this nested TreeMutator should be wired with the internal
|
|
|
|
|
* representation of the nested scope to enter. The code invoking this closure
|
|
|
|
|
* typically pushes the buffer on some internal stack and switches then to use
|
|
|
|
|
* this nested mutator until encountering the corresponding `EMU` bracket verb.
|
2016-09-02 18:40:16 +02:00
|
|
|
* @note the `after(Ref::ATTRIBS)` verb can only processed if the selector responds
|
|
|
|
|
* correct to a Ref::ATTRIBS spec. The implicit default selector does so, i.e.
|
|
|
|
|
* it rejects `Ref::ATTRIBS`. Please be sure to accept this token _only_ if
|
|
|
|
|
* your layer indeed holds something meant to implement "attributes", because
|
|
|
|
|
* in that case, the verb `after(Ref::ATTRIBS)` will fast forward and accept
|
|
|
|
|
* all the current contents of this layer
|
|
|
|
|
* @warning please note the _nested DSL_. The builder functions used to define
|
|
|
|
|
* the various closures are to be invoked on the _argument_ ("`collection(xyz)`"),
|
|
|
|
|
* not on the top level builder...
|
2016-03-18 20:52:35 +01:00
|
|
|
*/
|
2016-03-18 19:35:48 +01:00
|
|
|
template<typename BIN>
|
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
|
|
|
auto attach (BIN&& collectionBindingSetup);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** set up binding to a GenNode tree: Special setup to build a concrete `TreeMutator`.
|
|
|
|
|
* This decorator is already outfitted with the necessary closures to work on a
|
|
|
|
|
* diff::Record<GenNode> -- which is typically used as "meta representation" of
|
|
|
|
|
* object-like structures. Thus this binding allows to apply a diff message onto
|
|
|
|
|
* such a given »External Tree Description«, mutating it into new shape.
|
|
|
|
|
* @remarks our meta representation of "objects" is based on Record<GenNode>, which
|
|
|
|
|
* is implemented through two STL collections, one for the attributes and
|
|
|
|
|
* one for the child elements. Thus we'll using two binding layers, based
|
|
|
|
|
* on the ChildCollectionMutator, configured with the necessary lambdas.
|
|
|
|
|
*/
|
|
|
|
|
auto attach (Rec::Mutator& targetTree);
|
2016-03-18 19:35:48 +01:00
|
|
|
|
2016-08-31 18:40:09 +02:00
|
|
|
|
2016-03-18 20:52:35 +01:00
|
|
|
/** set up a diagnostic layer, binding to TestMutationTarget.
|
|
|
|
|
* This can be used to monitor the behaviour of the resulting TreeMutator for tests.
|
|
|
|
|
*/
|
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
|
|
|
auto attachDummy (TestMutationTarget& dummy);
|
2016-02-27 01:47:33 +01:00
|
|
|
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
};
|
2016-02-27 01:47:33 +01:00
|
|
|
|
2016-03-18 20:52:35 +01:00
|
|
|
}//(END) Mutator-Builder...
|
2016-02-27 01:47:33 +01:00
|
|
|
|
2015-04-02 03:30:20 +02:00
|
|
|
|
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
|
|
|
inline Builder<TreeMutator>
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
TreeMutator::build ()
|
|
|
|
|
{
|
2015-04-13 15:49:38 +02:00
|
|
|
return TreeMutator();
|
settle on a concrete implementation approach based on inheritance chain
After some reconsideration, I decide to stick to the approach with the closures,
but to use a metaprotramming technique to build an inheritance chain.
While I can not decide on the real world impact of storing all those closures,
in theory this approach should enable the compiler to remove all of the
storage overhead. Since, when storing the result into an auto variable
right within scope (as demonstrated in the test), the compiler
sees the concrete type and might be able to boil down the actual
generated virtual function implementations, thereby inlining the
given closures.
Whereas, on the other hand, if we'd go the obvious conventional route
and place the closures into a Map allocated on the stack, I wouldn't
expect the compiler to do data flow analysis to prove this allocation
is not necessary and inline it away.
NOTE: there is now guarantee this inlining trick will ever work.
And, moreover, we don't know anything regarding the runtime effect.
The whole picture is way more involved as it might seem at first sight.
Even if we go the completely conventional route and require every
participating object to supply an implementation of some kind of
"Serializable" interface, we'll end up with a (hand written!)
implementation class for each participating setup, which takes
up space in the code segment of the executable. While the closure
based approach chosen here, consumes data segment (or heap) space
per instance for the functors (or function pointers) representing
the closures, plus code segment space for the closures, but the
latter with a way higher potential for inlining, since the closure
code and the generated virtual functions are necessarily emitted
within the same compilation unit and within a local (inline, not
publickly exposed) scope.
2015-04-05 18:26:49 +02:00
|
|
|
}
|
2015-04-02 03:30:20 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
}} // namespace lib::diff
|
|
|
|
|
#endif /*LIB_DIFF_TREE_MUTATOR_H*/
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
/* == implementation detail headers == */
|
|
|
|
|
|
|
|
|
|
#include "lib/diff/tree-mutator-gen-node-binding.hpp"
|
|
|
|
|
#include "lib/diff/tree-mutator-attribute-binding.hpp"
|
|
|
|
|
#include "lib/diff/tree-mutator-collection-binding.hpp"
|
|
|
|
|
|