LUMIERA.clone/src/lib/diff/tree-mutator-attribute-binding.hpp
Ichthyostega acb674a9d2 Project: update and clean-up Doxygen configuration
...in an attempt to clarify why numerous cross links are not generated.
In the end, this attempt was not very successful, yet I could find some breadcrumbs...

- file comments generally seem to have a problem with auto link generation;
  only fully qualified names seem to work reliably

- cross links to entities within a namespace do not work,
  if the corresponding namespace is not documented in Doxygen

- documentation for entities within anonymous namespaces
  must be explicitly enabled. Of course this makes only sense
  for detailed documentation (but we do generate detailed
  documentation here, including implementation notes)

- and the notorious problem: each file needs a valid @file comment

- the hierarchy of Markdown headings must be consistent within each
  documentation section. This entails also to individual documented
  entities. Basically, there must be a level-one heading (prefix "#"),
  otherwise all headings will just disappear...

- sometimes the doc/devel/doxygen-warnings.txt gives further clues
2021-01-24 19:35:45 +01:00

384 lines
16 KiB
C++

/*
TREE-MUTATOR-ATTRIBUTE-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-attribute-binding.hpp
** Special binding implementation for TreeMutator, allowing to map
** tree diff operations onto native object attributes. 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 specific kind of layer, used to bind object fields
** through "setter" lambdas.
**
** # architecture considerations
** Together with the (\ref tree-mutator-collection-binding.hpp), the attribute binding
** is the most relevant building block -- yet it is special in several respects. There
** is kind of a "impedance mismatch" between the concept of an "attribute", as used in
** the context of diff messages and »External Tree Description«, and the nature of
** data fields as used within the imperative or object oriented implementation: the
** latter is rooted within a _class definition_ -- which is conceived as a _type_,
** a conceptual entity used for construction of code, yet not really embodied into
** the actual code at execution time. Thus, with respect to the _behaviour_ at execution,
** the structure defined through typing and classes appears as static backdrop. This leads
** to the consequence, that, on a generic (unspecific) level, we don't have any correlate
** to the notion of _ordering_ and _sequence_, as found within the diff language.
**
** On the other hand, this very notion of _ordering_ and _sequence_ is essential to the
** meaning of "diff", as far as collections of "children" are involved. This leaves us
** with the decision, either to increase complexity of the diff language's definition
** and concept, or to accommodate this discrepancy within the binding implementation.
** Deliberately, the whole concept of a "diff language" builds onto the notion of
** _layered semantics,_ where the precise meaning of some terms remains a private
** extension within specific usage context. There is a lot of leeway within the
** language, and the _correct usage protocol_ is linked to the actual scope of
** usage. We need the diff language to be a connecting medium, to link some
** remote partners based on a locally shared common understanding of structure.
**
** And so we use the same approach when it comes to "attributes": We'll assume that
** the partners connected through diff messages are _structurally compatible_ -- thus
** any "change" message emitted at one side is assumed to basically make sense on the
** receiving side. Consequently, the binding of an "attribute" to an object or data field
** will either ignore or reject any specifics about field order. It will _reject_ an
** explicit demand to re-order a field, and it will silently pass down other notions
** related to ordering -- down to lower "onion layers" of the concrete binding. So
** it depends on the concrete setup of the data binding (TreeMutator), if some
** expression in diff language will be deemed incompatible -- which happens when
** in the end no "onion layer" of the concrete binding was able to absorb and
** comply to the MutationMessage.
**
** Another architectural consideration is relevant to the way attribute bindings are
** constructed: we rather construct a separate binding for each individual attribute,
** instead of building a collective binding for all attributes of a given object.
** This gives us the benefit of a simple and flexible solution plus it avoids the
** overhead of managing a _collection of attribute definitions_ (which would likely
** cause a heap allocation for storage). The downside is that we loose any coherence
** between attributes of "the same object", we loose possible consistency checks and
** we get a linear search for access to any attribute binding. Moreover, we cannot
** protect against creation of a nonsensical binding, e.g. a binding which ties
** the same attribute several times in contradictory fashion. The client code
** constructing the concrete TreeMutator needs to have adequate understanding
** regarding mode of operation and "mechanics" of such a binding.
**
** @remark for sake of completeness an alternative binding option should be mentioned:
** Attributes could be represented as a map of `(key,value)` pairs and then bound
** via the STL collection binding. This way, all the attributes of an "object"
** would be treated as coherent unit, within a single "onion layer". However,
** such a layout tends to run against the conventions and the protocol of the
** diff language and should be confined to cover some corner cases (e.g. to
** support an open ended collection of _custom properties_)
**
** @note the header tree-mutator-attribute-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_ATTRIBUTE_BINDING_H
#define LIB_DIFF_TREE_MUTATOR_ATTRIBUTE_BINDING_H
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/diff/tree-mutator.hpp"
#include "lib/format-string.hpp"
#include "lib/idi/entry-id.hpp"
#include <utility>
namespace lib {
namespace diff{
namespace { // Mutator-Builder decorator components...
/**
* Generic behaviour of any binding to object fields (attributes).
* Since object fields as such are part of the class definition, a diff
* will never be able to add, insert, delete or re-order fields. Thus we
* do not need to keep track of an "old" and "new" order; rather there
* is always one single fixed element present to work on.
* @note consequently, several diff operations are either implemented NOP,
* or passed to the parent (lower onion layers).
*/
template<class PAR>
class AttributeBindingBase
: public PAR
{
BareEntryID attribID_;
protected:
AttributeBindingBase (BareEntryID attribID, PAR&& chain)
: PAR(std::forward<PAR>(chain))
, attribID_(attribID)
{ }
/** hard wired "selector predicate" for this binding layer.
* We handle only mutation operations pertaining attributes
* which additionally match the key defined at binding time.
* Any other operations are passed down the chain.
* @param spec target specification given within the diff verb
* @return if this binding is in charge for handling the spec
*/
bool
isApplicable (GenNode const& spec)
{
return spec.isNamed()
and attribID_ == spec.idi;
}
void
__ifApplicable_refuse_to(Literal oper, GenNode const& spec)
{
if (this->isApplicable(spec))
throw error::Logic (_Fmt{"attempt to %s attribute '%s', "
"but this binding for '%s' is linked to a data field and "
"thus does not support any notion of 'order' or 'position', "
"inserting or deletion."}
% oper % spec.idi % this->attribID_);
}
/* ==== re-Implementation of the operation API ==== */
public:
/** ensure the given spec is deemed appropriate at that point.
* Due to the hard wired nature of an object field binding, this can
* only be verified passively: a spec targeted at an unknown attribute
* will be rejected. But since there is no notion of "ordering" for
* (object) data fields, we can not verify the diff's completeness. */
virtual bool
matchSrc (GenNode const& spec) override
{
return isApplicable (spec)
or PAR::matchSrc (spec);
}
// note: hasSrc() not overridden here --> delegate to parent layer
/** accept status quo, after verifying the spec from the diff verb */
virtual bool
acceptSrc (GenNode const& spec) override
{
return isApplicable (spec)
or PAR::acceptSrc (spec);
}
/** any reordering or deletion of object fields is prohibited
* @throw error::Logic when this binding layer becomes responsible for
* handling the given diff spec, because a proper diff must be arranged
* in a way not to ask this binding to "reorder" a field from an
* existing class definition.
*/
virtual void
skipSrc (GenNode const& refSpec) override
{
__ifApplicable_refuse_to ("skip or drop", refSpec);
PAR::skipSrc (refSpec);
}
virtual bool
findSrc (GenNode const& refSpec) override
{
__ifApplicable_refuse_to ("re-order", refSpec);
return PAR::findSrc (refSpec);
}
/** there is no real support for navigating to a 'position',
* since attribute / data field binding has no notion of ordering.
* An attempt to fast-forward to "the end" is tolerated though.
* @throw error::Logic when this binding becomes responsible and a
* request to navigate to some specific attribute is detected
* @note the diff spec `Ref::END` or `Ref::ATTRIBS` is tolerated
* and implemented as NOP (since there is no 'position'
* incorporated into the binding implementation.
*/
virtual bool
accept_until (GenNode const& spec) override
{
if (Ref::END == spec or Ref::ATTRIBS == spec)
return PAR::accept_until(spec);
else
{
__ifApplicable_refuse_to ("navigate to a position behind", spec);
return PAR::accept_until(spec);
}
}
};
template<class PAR, class CLO>
class ChangeOperation
: public AttributeBindingBase<PAR>
{
using CloArgs = typename lib::meta::_Fun<CLO>::Args;
using ValueType = typename lib::meta::Pick<CloArgs, 0>::Type;
using ID = idi::EntryID<ValueType>;
CLO setter_;
public:
ChangeOperation (Symbol attribKey, CLO clo, PAR&& chain)
: AttributeBindingBase<PAR>(ID{attribKey}, std::forward<PAR>(chain))
, setter_(clo)
{ }
/* ==== Implementation of value assignment operation ==== */
/** while, strictly speaking, one can not "insert" fields into a
* given class definition, this binding can tolerate an `INS` verb
* whenever this means to touch a field which is actually known and
* present in the class definition underlying this binding. In such
* a case, we just assign the given value. This implementation leeway
* is deliberate to support classes with optional / defaultable properties.
*/
virtual bool
injectNew (GenNode const& spec) override
{
if (not this->isApplicable(spec))
return PAR::injectNew(spec);
setter_(spec.data.get<ValueType>());
return true;
}
/** invoke the setter lambda, when this binding layer is in charge */
virtual bool
assignElm (GenNode const& spec) override
{
if (not this->isApplicable(spec))
return PAR::assignElm(spec);
setter_(spec.data.get<ValueType>());
return true;
}
};
template<class PAR, class MUT>
class MutationOperation
: public AttributeBindingBase<PAR>
{
ASSERT_VALID_SIGNATURE (MUT, void(TreeMutator::Handle));
MUT mutatorBuilder_;
public:
MutationOperation (BareEntryID const& attribID, MUT clo, PAR&& chain)
: AttributeBindingBase<PAR>(attribID, std::forward<PAR>(chain))
, mutatorBuilder_(clo)
{ }
/** if this binding layer is in charge, then invoke the closure,
* which is assumed to construct a nested TreeMutator into the provided Buffer,
* able to deal with the nested attribute object referred by this binding */
virtual bool
mutateChild (GenNode const& spec, TreeMutator::Handle targetBuff) override
{
if (not this->isApplicable(spec))
return PAR::mutateChild(spec, targetBuff);
mutatorBuilder_(targetBuff);
return true;
}
/** default setup: silently absorb insert.
* @remark this whole binding flavour (MutateOperation) deals with an
* odd corner case, namely an object valued attribute, which is deliberately
* treated as nested scope; a sub-scope accessed by name. Since attributes
* do not support any notion of "inserting" or "reordering" anyway, it is
* typical in such a situation for the implementation data structure to manage
* the underlying object automatically or default construct it anyway; moreover
* it would be pointless to define a setter, since the whole point is not to
* assign, but rather to enter and populate the nested target object. Yet,
* the diff language requires us to send an empty initial value at least
* once prior to mutation.
* @note if you really _need_ a dedicated custom setter, just define it
* _after_ the `mutateAttribute` spec; it's implementation will then
* be layered on top and shadow this default case.
*/
virtual bool
injectNew (GenNode const& spec) override
{
return this->isApplicable(spec)
or PAR::injectNew(spec);
}
};
/** Entry point for DSL builder */
template<class PAR>
template<typename CLO>
inline auto
Builder<PAR>::change (Symbol attributeID, CLO setterClosure)
{
return chainedBuilder<ChangeOperation<PAR,CLO>> (attributeID, setterClosure);
}
/** Entry point for DSL builder */
template<class PAR>
template<typename CLO>
inline auto
Builder<PAR>::mutateAttrib (Symbol attributeID, CLO mutatorBuilderClosure)
{
idi::EntryID<Rec> key{attributeID};
return chainedBuilder<MutationOperation<PAR,CLO>> (key, mutatorBuilderClosure);
}
/** Entry point for DSL builder; variation to handle an attribute-object with given ID */
template<class PAR>
template<typename CLO>
inline auto
Builder<PAR>::mutateAttrib (idi::BareEntryID const& rawID, CLO mutatorBuilderClosure)
{
return chainedBuilder<MutationOperation<PAR,CLO>> (rawID, mutatorBuilderClosure);
}
}//(END)Mutator-Builder decorator components...
}} // namespace lib::diff
#endif /*LIB_DIFF_TREE_MUTATOR_ATTRIBUTE_BINDING_H*/