Yet another chainsaw massacre. One of the most obnoxious annoyances with C++ metaprogramming is the need to insert `typename` and `template` qualifiers into most definitions, to help the compiler to cope with the syntax, which is not context-free. The recent standards adds several clarifications, so that most of these qualifiers are redundant now, at least at places where it is unambiguously clear that only a type can be given. GCC already supports most of these relaxing rules (Clang unfortunately lags way behind with support of newer language features...)
808 lines
25 KiB
C++
808 lines
25 KiB
C++
/*
|
||
RECORD.hpp - collection to represent object-like data
|
||
|
||
Copyright (C)
|
||
2015, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** 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. See the file COPYING for further details.
|
||
|
||
*/
|
||
|
||
|
||
/** @file record.hpp
|
||
** Special collection to represent object-like data.
|
||
** To be used in a context where introspection, open, extensible definitions
|
||
** and loose coupling of data representation matters. Typically, structures
|
||
** defined in terms of Record elements are linked to the _actual core_
|
||
** representation of the same entities relying on [diff messages](\ref diff-language.hpp).
|
||
** Record is one of the supported flavours within the DataCap of GenNode elements,
|
||
** which in turn serve as the standard handle to refer to other elements, entities,
|
||
** attributes or references within the "backbone" of the Lumiera GUI.
|
||
**
|
||
** A Record holds
|
||
** - (optionally) a type-ID string
|
||
** - a collection of _named attributes_ (key-value data)
|
||
** - a list of _enclosed children_ (contents within the "scope" of this Record)
|
||
**
|
||
** # design decisions
|
||
** The Record type is shaped from its intended use: It serves to symbolically represent
|
||
** *objects* in the "external tree description". Here, "objects" means objects _for real_,
|
||
** i.e. with types, fields and an enclosed scope. Yet the term *external* means that we do not
|
||
** work on these objects right here, we only represent them, for later referral, _symbolically_.
|
||
**
|
||
** \par rationale
|
||
** The underlying theme and motivation of this design is negative: we do not want
|
||
** to build _yet another object system_. The object model of C++ is deemed adequate.
|
||
**
|
||
** This leads to the following decisions
|
||
** - the Record entity is itself an object and thus has an inner side, privately.
|
||
** The entrails of the Record can be reworked and tuned for performance
|
||
** - yet the Record has an external appearance, which makes it look flat and passive.
|
||
** This is to say, a Record has no visible functionality.
|
||
** - the parts or _realms_ within this symbolic representation are distinguished
|
||
** by convention solely
|
||
**
|
||
** * metadata is very limited and boils down to a type attribute known by name
|
||
** * children (scope contents) can be recognised by _not_ bearing a name
|
||
**
|
||
** Record entities are meant to be immutable. The proper way to alter a Record is
|
||
** to apply a [diff](\ref tree-diff.hpp). Yet for the _implementation_ of this
|
||
** diff handling, a Record::Mutator is provided, to allow controlled partial
|
||
** re-building of a given data element. Moreover, especially for Record<GenNode>,
|
||
** this mutator can be used for a DSL-style compact notation of a complete
|
||
** "object tree" -- a feature intended for writing unit tests.
|
||
**
|
||
** @remarks
|
||
** - the implementation is focused on the intended primary use case,
|
||
** which is to exchange diff messages drawn against a symbolic representation
|
||
** of a typed object tree. Especially, we assume that there is only a small
|
||
** number of attributes (so linear search for access by key is adequate).
|
||
** - moreover, we assume that the value type allows for somehow embedding
|
||
** the key of each attribute; the implementation needs an explicit
|
||
** specialisation of the binding functions for each value type.
|
||
** - this header defines a specialisation for VAL = std::string --
|
||
** while the most relevant specialisation for GenNode is provided
|
||
** alongside with this special, monadic value type.
|
||
** - an alternative implementation approach would have been to use a
|
||
** dedicated helper type to represent the collection of attributes.
|
||
** This type might then be specialised, e.g. to utilise an index table
|
||
** for key-value lookup. However, in the light of the intended usage
|
||
** of Record entities as tree nodes within a GenNode monad, such a
|
||
** more elaborate approach was deemed unnecessary for the time being.
|
||
**
|
||
** @see GenericRecord_test
|
||
**
|
||
*/
|
||
|
||
|
||
#ifndef LIB_DIFF_RECORD_H
|
||
#define LIB_DIFF_RECORD_H
|
||
|
||
|
||
#include "lib/error.hpp"
|
||
#include "lib/nocopy.hpp"
|
||
#include "lib/iter-adapter.hpp"
|
||
#include "lib/iter-adapter-stl.hpp"
|
||
#include "lib/itertools.hpp"
|
||
#include "lib/format-util.hpp" ///////////////////////////////TICKET #973 : investigate the impact of this inclusion on code size
|
||
#include "lib/util.hpp"
|
||
|
||
|
||
#include <algorithm>
|
||
#include <utility>
|
||
#include <vector>
|
||
#include <string>
|
||
|
||
|
||
|
||
namespace lib {
|
||
|
||
template<class BA, class DEFAULT>
|
||
class PlantingHandle;
|
||
|
||
namespace idi {
|
||
class BareEntryID;
|
||
}
|
||
namespace diff{
|
||
|
||
namespace error = lumiera::error;
|
||
|
||
using util::isnil;
|
||
using std::string;
|
||
|
||
class TreeMutator;
|
||
|
||
template<typename VAL>
|
||
struct RecordSetup;
|
||
|
||
|
||
/**
|
||
* object-like record of data.
|
||
* For symbolic representation of "objects".
|
||
* A Record holds both \em attributes (key-value data)
|
||
* plus a list of \em enclosed children, which are conceived
|
||
* to be within the "scope" of this Record. Optionally, a \em typeID
|
||
* (metadata) may be defined. Otherwise, this typeID defaults to \c "NIL".
|
||
* The representation of attributes depends on the actual value type, which
|
||
* somehow need the ability to encode the keys within the value data.
|
||
* By default, a specialisation is given for string, using the \c "key = val"
|
||
* syntax. Yet the most relevant use case is \c Record<GenNode> -- using the
|
||
* embedded name-ID of the GenNode elements as key for attributes.
|
||
*
|
||
* Record elements are meant to be immutable; they can be created from a
|
||
* defining collection. However, we provide a #Mutator mechanism to allow
|
||
* for rebuilding and mutating symbolic data structures based on Records
|
||
* and GenNode. Essentially, Lumiera's diff framework relies on this.
|
||
*/
|
||
template<typename VAL>
|
||
class Record
|
||
{
|
||
using Storage = RecordSetup<VAL>::Storage;
|
||
using ElmIter = RecordSetup<VAL>::ElmIter;
|
||
using Access = RecordSetup<VAL>::Access;
|
||
|
||
|
||
string type_;
|
||
Storage attribs_;
|
||
Storage children_;
|
||
|
||
public:
|
||
static const string TYPE_NIL;
|
||
static const Symbol TYPE_NIL_SYM;
|
||
|
||
Record()
|
||
: type_(TYPE_NIL)
|
||
{ }
|
||
|
||
template<typename A, typename C>
|
||
Record(Symbol typeID, A&& att, C&& chi)
|
||
: type_(isnil(typeID)? TYPE_NIL:string(typeID))
|
||
, attribs_(std::forward<A> (att))
|
||
, children_(std::forward<C> (chi))
|
||
{ }
|
||
|
||
template<typename A, typename C>
|
||
Record(Symbol typeID, std::initializer_list<A> const&& att
|
||
, std::initializer_list<C> const&& chi)
|
||
: type_(isnil(typeID)? TYPE_NIL:string(typeID))
|
||
, attribs_(att)
|
||
, children_(chi)
|
||
{ }
|
||
|
||
template<typename SEQ>
|
||
explicit
|
||
Record (SEQ const& con)
|
||
: type_(TYPE_NIL)
|
||
{
|
||
auto p = std::begin(con);
|
||
auto e = std::end(con);
|
||
for ( ; p!=e and isAttribute(*p); ++p)
|
||
if (isTypeID (*p))
|
||
type_ = extractTypeID(*p);
|
||
else
|
||
attribs_.push_back (*p);
|
||
for ( ; p!=e; ++p)
|
||
children_.push_back (*p);
|
||
}
|
||
|
||
Record (std::initializer_list<VAL> const&& ili)
|
||
: Record(ili)
|
||
{ }
|
||
|
||
// all default copy operations acceptable
|
||
Record (Record &&) = default;
|
||
Record (Record const&) = default;
|
||
Record& operator= (Record &&) = default;
|
||
Record& operator= (Record const&) = default;
|
||
|
||
|
||
/** for diagnostic purpose */
|
||
operator std::string() const;
|
||
|
||
|
||
size_t
|
||
attribSize() const
|
||
{
|
||
return attribs_.size();
|
||
}
|
||
|
||
size_t
|
||
childSize() const
|
||
{
|
||
return children_.size();
|
||
}
|
||
|
||
bool
|
||
empty() const
|
||
{
|
||
return attribs_.empty()
|
||
and children_.empty();
|
||
}
|
||
|
||
|
||
string
|
||
getType() const
|
||
{
|
||
return type_;
|
||
}
|
||
|
||
bool
|
||
hasAttribute (string key) const
|
||
{
|
||
return attribs_.end() != findKey(key);
|
||
}
|
||
|
||
bool
|
||
contains (VAL const& val) const
|
||
{
|
||
return util::contains (children_, val);
|
||
}
|
||
|
||
Access
|
||
get (string key) const
|
||
{
|
||
ElmIter found = findKey (key);
|
||
if (attribs_.end() == found)
|
||
throw error::Invalid ("Record has no attribute \""+key+"\"");
|
||
else
|
||
return extractVal (*found);
|
||
}
|
||
|
||
Access
|
||
child (size_t idx) const
|
||
{
|
||
if (children_.size() <= idx)
|
||
throw error::Invalid ("Child index " +util::toString(idx)
|
||
+" out of bounds [0.."+util::toString(children_.size())
|
||
+"["
|
||
,error::LUMIERA_ERROR_INDEX_BOUNDS);
|
||
return children_[idx];
|
||
}
|
||
|
||
|
||
/**
|
||
* While otherwise immutable,
|
||
* a Record object can be remoulded
|
||
* with the help of a Mutator object...
|
||
* @remarks a Mutator basically wraps a \em copy
|
||
* of the original object. After performing
|
||
* the desired changes, the altered copy can either
|
||
* be copied out (by conversion), or moved overwriting
|
||
* an existing other Record instance (implemented as swap)
|
||
*/
|
||
class Mutator;
|
||
|
||
|
||
|
||
/**
|
||
* copy-initialise (or convert) from the given Mutator instance.
|
||
* @remarks need to code this explicitly, otherwise Record's
|
||
* build-from sequence templated ctor would kick in.
|
||
* @warning beware of initialiser lists. Record has a dedicated
|
||
* ctor to accept an initialiser list of GenNode elements,
|
||
* and GenNode's ctor is deliberately _not explicit,_ to ease
|
||
* building argument lists wrapped in GenNodes. When writing
|
||
* initialisation expressions inline, the compiler might pick
|
||
* the conversion path through initialiser list, which means
|
||
* the contents of the Mutator end up wrapped into a GenNode,
|
||
* which in turn becomes the only child of the new Record.
|
||
*/
|
||
Record (Mutator const& mut)
|
||
: Record(static_cast<Record const&> (mut))
|
||
{ }
|
||
explicit
|
||
Record (Mutator && mut)
|
||
: Record{}
|
||
{
|
||
mut.swap(*this);
|
||
}
|
||
|
||
friend class Mutator;
|
||
|
||
|
||
|
||
/* ==== Exposing scope and contents for iteration ====== */
|
||
|
||
using iterator = IterAdapter<ElmIter, const Record*>;
|
||
using scopeIter = iter_stl::_SeqT<const Storage>::Range;
|
||
using keyIter = TransformIter<scopeIter, string>;
|
||
using valIter = TransformIter<scopeIter, Access>;
|
||
|
||
/** default iteration exposes all data within this "object", starting with the attributes */
|
||
iterator begin () const { return iterator(this, attribs_.empty()? children_.begin() : attribs_.begin()); }
|
||
iterator end () const { return iterator(); }
|
||
|
||
scopeIter attribs() const { return iter_stl::eachElm(attribs_); }
|
||
scopeIter scope() const { return iter_stl::eachElm(children_); }
|
||
|
||
keyIter keys() const { return transformIterator(attribs(), extractKey); }
|
||
valIter vals() const { return transformIterator(attribs(), extractVal); }
|
||
|
||
protected: /* ==== API for the IterAdapter ==== */
|
||
|
||
/** Implementation of Iteration-logic: pull next element. */
|
||
friend void
|
||
iterNext (const Record*, ElmIter& pos)
|
||
{
|
||
++pos;
|
||
}
|
||
|
||
/** Implementation of Iteration-logic: detect iteration end.
|
||
* @remarks seamless continuation of the iteration when reaching
|
||
* the end of the attribute collection. In this implementation,
|
||
* we use the default constructed `ITER()` to mark iteration end.
|
||
*/
|
||
friend bool
|
||
checkPoint (const Record* src, ElmIter& pos)
|
||
{
|
||
REQUIRE (src);
|
||
static const ElmIter END;
|
||
if (pos != END and pos == src->attribs_.end() and not src->children_.empty())
|
||
{
|
||
pos = src->children_.begin();
|
||
return true;
|
||
}
|
||
else
|
||
if (pos != END and (pos != src->children_.end()))
|
||
return true;
|
||
else
|
||
{
|
||
pos = END;
|
||
return false;
|
||
} }
|
||
|
||
private:
|
||
/* === abstract attribute handling : needs specialisation === */
|
||
static bool isAttribute (VAL const& v);
|
||
static bool isTypeID (VAL const& v);
|
||
static string extractTypeID (VAL const& v);
|
||
static string renderAttribute (VAL const& a);
|
||
static string extractKey (VAL const& v);
|
||
static Access extractVal (VAL const& v);
|
||
template<typename X>
|
||
static VAL buildAttribute (string const& key, X&& payload);
|
||
|
||
|
||
ElmIter
|
||
findKey (string key) const
|
||
{
|
||
return std::find_if (attribs_.begin()
|
||
,attribs_.end()
|
||
,[=](VAL const& elm)
|
||
{
|
||
return key == extractKey(elm);
|
||
});
|
||
}
|
||
|
||
|
||
friend bool
|
||
operator== (Record const& r1, Record const& r2)
|
||
{
|
||
return r1.type_ == r2.type_
|
||
and r1.attribs_ == r2.attribs_
|
||
and r1.children_ == r2.children_;
|
||
}
|
||
|
||
friend bool
|
||
operator!= (Record const& r1, Record const& r2)
|
||
{
|
||
return not (r1 == r2);
|
||
}
|
||
};
|
||
|
||
template<typename VAL>
|
||
const Symbol Record<VAL>::TYPE_NIL_SYM = "NIL";
|
||
|
||
template<typename VAL>
|
||
const string Record<VAL>::TYPE_NIL = string(TYPE_NIL_SYM);
|
||
|
||
|
||
|
||
template<typename VAL>
|
||
class Record<VAL>::Mutator
|
||
: util::MoveOnly
|
||
{
|
||
using Rec = Record<VAL>;
|
||
|
||
Rec record_;
|
||
|
||
public:
|
||
Mutator()
|
||
: record_()
|
||
{ }
|
||
|
||
explicit
|
||
Mutator (Rec const& startingPoint)
|
||
: record_(startingPoint)
|
||
{ }
|
||
|
||
explicit
|
||
Mutator (Rec && startingPoint)
|
||
: record_(std::move (startingPoint))
|
||
{ }
|
||
|
||
/** Builder-terminal: return copy of mutated results when done */
|
||
operator Rec & () { return record_; }
|
||
operator Rec const&() const { return record_; }
|
||
|
||
void
|
||
swap (Rec& existingInstance) noexcept
|
||
{
|
||
using std::swap;
|
||
swap (existingInstance, record_);
|
||
}
|
||
|
||
bool
|
||
empty() const
|
||
{
|
||
return record_.empty();
|
||
}
|
||
|
||
|
||
/* === functions to alter contents === */
|
||
|
||
void
|
||
setType (string const& newTypeID)
|
||
{
|
||
record_.type_ = newTypeID;
|
||
}
|
||
|
||
Mutator&&
|
||
type (string const& typeID)
|
||
{
|
||
setType (typeID);
|
||
return move(*this);
|
||
}
|
||
|
||
template<typename X>
|
||
Mutator&&
|
||
set (string const& key, X&& content)
|
||
{
|
||
VAL attribute(Rec::buildAttribute (key, std::forward<X>(content)));
|
||
return set (std::move (attribute));
|
||
}
|
||
|
||
Mutator&&
|
||
set (VAL&& attribute)
|
||
{
|
||
string key = Rec::extractKey(attribute);
|
||
if (isnil (key))
|
||
throw error::Invalid ("Attempt to set an attribute with empty key");
|
||
|
||
Rec::Storage& as =record_.attribs_;
|
||
auto found = std::find_if (as.begin(),as.end()
|
||
,[=](VAL const& elm)
|
||
{
|
||
return key == extractKey(elm);
|
||
});
|
||
if (as.end() == found)
|
||
as.push_back (std::forward<VAL> (attribute));
|
||
else
|
||
(*found) = (std::forward<VAL> (attribute));
|
||
return move(*this);
|
||
}
|
||
|
||
Mutator&&
|
||
appendAttrib (VAL const& newAttrib)
|
||
{
|
||
REQUIRE (Rec::isAttribute(newAttrib));
|
||
record_.attribs_.push_back (newAttrib);
|
||
return move(*this);
|
||
}
|
||
|
||
Mutator&&
|
||
appendChild (VAL const& newChild)
|
||
{
|
||
record_.children_.push_back (newChild);
|
||
return move(*this);
|
||
}
|
||
|
||
Mutator&&
|
||
prependChild (VAL const& newChild)
|
||
{
|
||
record_.children_.insert (record_.children_.begin(), newChild);
|
||
return move(*this);
|
||
}
|
||
|
||
/* === low-level access (for diff application) === */
|
||
|
||
using BufferHandle = PlantingHandle<TreeMutator, TreeMutator>;
|
||
|
||
/** attachment point to receive and apply tree-diff changes.
|
||
* The actual implementation needs to be provided for concrete Record payload types;
|
||
* in case of Record<GenNode>, a default implementation for this feature is provided by the
|
||
* "diff framework", which offers a preconfigured binding to create a TreeMutator implementation,
|
||
* which can then be used for a DiffApplicator. This way, a Rec::Mutator can receive diff messages
|
||
* to reorder and reshape the contents.
|
||
* @param BufferHandle pointing to an (implementation provided) storage location, where this
|
||
* function is expected to construct a suitable TreeMutator, linked to the internals
|
||
* of this Record::Mutator.
|
||
* @see lib::diff::mutatorBinding()
|
||
* @see lib::diff::DiffApplicationStrategy
|
||
* @see tree-diff-application.hpp
|
||
* @see DiffTreeApplication_test usage example
|
||
*/
|
||
void buildMutator (BufferHandle);
|
||
|
||
auto
|
||
exposeToDiff()
|
||
{
|
||
return std::tie (record_.attribs_, record_.children_);
|
||
}
|
||
|
||
|
||
/** get the tail element.
|
||
* @return either the last child, or the last attribute, when children are empty.
|
||
* @note typically this might be used to get back at the element "just added",
|
||
* as when muting a child node in diff application. But there is a loophole:
|
||
* we might have added an attribute even when there are already children.
|
||
*/
|
||
VAL const&
|
||
accessLast()
|
||
{
|
||
if (record_.empty())
|
||
throw error::State("Record is empty, unable to access (last) element.");
|
||
|
||
if (record_.children_.empty())
|
||
return record_.attribs_.back();
|
||
else
|
||
return record_.children_.back();
|
||
}
|
||
|
||
|
||
/* === extension point for building specific value types === */
|
||
/*
|
||
* the following builder functions need to be specialised
|
||
* to create a Record holding specific value types,
|
||
* especially for building a tree like structure
|
||
* with GenNode holding a Record<GenNode>
|
||
*/
|
||
|
||
VAL genNode();
|
||
VAL genNode(idi::BareEntryID rawID);
|
||
VAL genNode(string const& symbolicID);
|
||
|
||
template<typename X, typename...ARGS>
|
||
Mutator&& attrib (string const& key, X&& initialiser, ARGS&& ...args)
|
||
{
|
||
set (key, std::forward<X>(initialiser));
|
||
return attrib (std::forward<ARGS>(args)...);
|
||
}
|
||
Mutator&& attrib () { return move(*this); } // argument recursion end
|
||
|
||
|
||
template<typename X, typename...ARGS>
|
||
Mutator&& scope (X const& initialiser, ARGS&& ...args)
|
||
{
|
||
appendChild (VAL(initialiser));
|
||
return scope (std::forward<ARGS>(args)...);
|
||
}
|
||
Mutator&& scope () { return move(*this); }
|
||
|
||
};
|
||
|
||
|
||
/** open an existing record for modification in-place.
|
||
* @warning this function undermines the immutability of Record
|
||
* @remarks exploits the specific and known memory layout of Rec::Mutator.
|
||
* This trickery is necessary to avoid copying a large and possibly
|
||
* nested object tree; this happens when applying a diff, when
|
||
* recursively descending into nested child objects.
|
||
* @todo do we have a design mismatch here?? /////////////////////////////////////////TICKET #970
|
||
* @see tree-diff-application.hpp
|
||
*/
|
||
template<typename VAL>
|
||
inline Record<VAL>::Mutator&
|
||
mutateInPlace (Record<VAL>& record_to_mutate)
|
||
{
|
||
return reinterpret_cast<Record<VAL>::Mutator &> (record_to_mutate);
|
||
}
|
||
|
||
|
||
/**
|
||
* wrapped record reference.
|
||
* A helper for lib::GenNode and the diff representation.
|
||
* RecordRef is copyable and movable, but like a reference
|
||
* can not be rebound. It can be used to refer to a subtree
|
||
* within the diff representation, without the need to copy.
|
||
* @remarks this is almost identical to std::ref, with the
|
||
* notable difference that it can be default-created
|
||
* into "bottom" state; this also implies to have
|
||
* a NULL check on dereferentiation.
|
||
*/
|
||
template<typename VAL>
|
||
class RecordRef
|
||
{
|
||
using Target = Record<VAL>;
|
||
|
||
Target* record_;
|
||
|
||
public:
|
||
/** by default create an
|
||
* invalid ("bottom") reference */
|
||
RecordRef() noexcept
|
||
: record_(nullptr)
|
||
{ }
|
||
|
||
/** create a reference bound to
|
||
* the given target; can not be rebound */
|
||
RecordRef(Target& o) noexcept
|
||
: record_(&o)
|
||
{ }
|
||
|
||
/** prevent moving into black hole */
|
||
RecordRef(Target&&) = delete;
|
||
|
||
RecordRef(RecordRef const&) = default;
|
||
RecordRef(RecordRef &&) = default;
|
||
|
||
/** references can not be reassigned */
|
||
RecordRef& operator= (RecordRef const&) = delete;
|
||
RecordRef& operator= (RecordRef &) = delete;
|
||
|
||
/** assignment is not allowed, but moving is */
|
||
RecordRef&
|
||
operator= (RecordRef &&o)
|
||
{
|
||
std::swap (record_, o.record_);
|
||
return *this;
|
||
}
|
||
|
||
|
||
explicit
|
||
operator bool() const
|
||
{
|
||
return bool(record_);
|
||
}
|
||
|
||
bool
|
||
empty() const
|
||
{
|
||
return not record_;
|
||
}
|
||
|
||
/** target is accessed by cast
|
||
* @throws error::Logic on bottom reference
|
||
*/
|
||
operator Target&() const
|
||
{
|
||
if (!record_)
|
||
throw error::Logic("attempt to dereference an unbound record reference"
|
||
,error::LUMIERA_ERROR_BOTTOM_VALUE);
|
||
return *record_;
|
||
}
|
||
|
||
Target*
|
||
get() const noexcept
|
||
{
|
||
return record_;
|
||
}
|
||
|
||
operator string() const
|
||
{
|
||
return "Ref->" + (empty()? util::BOTTOM_INDICATOR
|
||
: string(*record_));
|
||
}
|
||
|
||
/** @note equality of references (instance pointers), not targets */
|
||
friend bool
|
||
operator== (RecordRef const& r1, RecordRef const& r2)
|
||
{
|
||
return r1.record_ == r2.record_;
|
||
}
|
||
friend bool
|
||
operator!= (RecordRef const& r1, RecordRef const& r2)
|
||
{
|
||
return r1.record_ != r2.record_;
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
/* === Extension point: Specialisations for attribute handling === */
|
||
|
||
/**
|
||
* Type configuration (extension point).
|
||
* Data storage and access types.
|
||
*/
|
||
template<>
|
||
struct RecordSetup<string>
|
||
{
|
||
using Storage = std::vector<string>;
|
||
using ElmIter = Storage::const_iterator;
|
||
using Access = string; ///< data access by value copy
|
||
};
|
||
|
||
|
||
|
||
|
||
/* default handling defined for Record<string> */
|
||
|
||
template<>
|
||
inline string
|
||
Record<string>::extractKey (string const& v)
|
||
{
|
||
size_t pos = v.find('=');
|
||
if (string::npos == pos)
|
||
return "";
|
||
else
|
||
return util::trim (v.substr (0,pos));
|
||
}
|
||
|
||
template<>
|
||
inline string
|
||
Record<string>::extractVal (string const& v)
|
||
{
|
||
size_t pos = v.find('=');
|
||
if (string::npos == pos)
|
||
return v;
|
||
else
|
||
return util::trim (v.substr (pos+1, v.length() - pos));
|
||
}
|
||
|
||
template<>
|
||
inline bool
|
||
Record<string>::isAttribute (string const& v)
|
||
{
|
||
return string::npos != v.find('=');
|
||
}
|
||
|
||
template<>
|
||
inline bool
|
||
Record<string>::isTypeID (string const& v)
|
||
{
|
||
return isAttribute(v)
|
||
and "type" == extractKey(v);
|
||
}
|
||
|
||
template<>
|
||
inline string
|
||
Record<string>::extractTypeID (string const& v)
|
||
{
|
||
return extractVal(v);
|
||
}
|
||
|
||
template<>
|
||
inline string
|
||
Record<string>::renderAttribute (string const& attrib)
|
||
{
|
||
return extractKey(attrib) + " = " + extractVal(attrib);
|
||
}
|
||
|
||
template<>
|
||
template<typename X>
|
||
inline string
|
||
Record<string>::buildAttribute (string const& key, X&& payload)
|
||
{
|
||
return string(key + " = " + extractVal(payload));
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/* === Diagnostics === */
|
||
|
||
template<typename VAL>
|
||
Record<VAL>::operator std::string() const
|
||
{
|
||
using util::join;
|
||
using lib::transformIterator;
|
||
|
||
return "Rec("
|
||
+ (TYPE_NIL==type_? "" : type_)
|
||
+ (isnil(this->attribs())? "" : "| "+join (transformIterator (this->attribs(), renderAttribute))+" ")
|
||
+ (isnil(this->scope())? "" : "|{"+join (this->scope())+"}")
|
||
+ ")"
|
||
;
|
||
}
|
||
|
||
|
||
|
||
}} // namespace lib::diff
|
||
#endif /*LIB_DIFF_GEN_NODE_H*/
|