2015-03-21 02:00:55 +01:00
|
|
|
/*
|
|
|
|
|
RECORD.hpp - collection to represent object-like data
|
|
|
|
|
|
|
|
|
|
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 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 \em core
|
|
|
|
|
** representation of the same entities relying on
|
|
|
|
|
** \link diff-language.hpp diff messages. \endlink
|
|
|
|
|
** 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.
|
|
|
|
|
**
|
2015-06-04 19:26:45 +02:00
|
|
|
** \par design decisions
|
|
|
|
|
** The Record type is shaped from its intended use: It serves to symbolically represent
|
|
|
|
|
** \em objects in the "external tree description". Here, \em objects means objects for
|
|
|
|
|
** real, i.e. with types, fields and an enclosed scope. But \em external means that we
|
|
|
|
|
** do not work on these objects right here, we only represent them, for later referral,
|
|
|
|
|
** \em symbolically.
|
|
|
|
|
**
|
|
|
|
|
** 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 \em realms within this symbolic representation are distinguished
|
|
|
|
|
** by convention solely
|
|
|
|
|
**
|
2015-08-17 22:13:36 +02:00
|
|
|
** * metadata is very limited and boils down to a type attribute known by name
|
2015-06-04 19:26:45 +02:00
|
|
|
** * children (scope contents) can be recognised by \em not bearing a name
|
|
|
|
|
**
|
|
|
|
|
** Record entities are meant to be immutable. The proper way to alter a Record is
|
2015-08-17 22:13:36 +02:00
|
|
|
** to apply a \link tree-diff.hpp diff \endlink. Yet for the \em implementation
|
|
|
|
|
** of this diff handling, a Record::Mutator is provided, to allow controlled
|
|
|
|
|
** partial re-building of a given data element.
|
2015-06-04 19:26:45 +02:00
|
|
|
**
|
|
|
|
|
** \par rationale
|
|
|
|
|
** The underlying theme of this design is negative, dialectical: we do not want to
|
2015-06-06 02:40:18 +02:00
|
|
|
** build yet another object system. The object model of C++ is deemed adequate.
|
|
|
|
|
**
|
|
|
|
|
** @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).
|
2015-08-17 22:13:36 +02:00
|
|
|
** - moreover, we assume that the value type allows for somehow embedding
|
2015-06-06 02:40:18 +02:00
|
|
|
** 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.
|
2015-06-04 19:26:45 +02:00
|
|
|
**
|
|
|
|
|
** @see GenericRecordRepresentation_test
|
2015-03-21 02:00:55 +01:00
|
|
|
**
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef LIB_DIFF_RECORD_H
|
|
|
|
|
#define LIB_DIFF_RECORD_H
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "lib/error.hpp"
|
2015-06-05 19:17:39 +02:00
|
|
|
#include "lib/iter-adapter.hpp"
|
|
|
|
|
#include "lib/iter-adapter-stl.hpp"
|
|
|
|
|
#include "lib/itertools.hpp"
|
2015-07-05 03:17:39 +02:00
|
|
|
#include "lib/util.hpp"
|
2015-06-05 19:17:39 +02:00
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
#include <boost/noncopyable.hpp>
|
2015-03-21 02:00:55 +01:00
|
|
|
|
2015-07-08 04:12:10 +02:00
|
|
|
#include <algorithm>
|
2015-06-05 19:17:39 +02:00
|
|
|
#include <utility>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <string>
|
2015-06-06 02:40:18 +02:00
|
|
|
|
2015-03-21 02:00:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
|
|
|
|
namespace diff{
|
|
|
|
|
|
|
|
|
|
namespace error = lumiera::error;
|
|
|
|
|
|
2015-08-17 06:17:00 +02:00
|
|
|
using util::isnil;
|
2015-06-05 19:17:39 +02:00
|
|
|
using std::string;
|
2015-03-21 02:00:55 +01:00
|
|
|
|
2015-08-17 02:40:57 +02:00
|
|
|
template<typename VAL>
|
|
|
|
|
struct RecordSetup;
|
2015-03-21 02:00:55 +01:00
|
|
|
|
|
|
|
|
|
2015-06-06 02:40:18 +02:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
2015-07-05 03:17:39 +02:00
|
|
|
* Record elements are meant to be immutable; they can be created from a
|
2015-06-06 02:40:18 +02:00
|
|
|
* defining collection. However, we provide a #Mutator mechanism to allow
|
|
|
|
|
* for rebuilding and mutating symbolic data structures based on Records
|
2015-07-05 03:17:39 +02:00
|
|
|
* and GenNode. Essentially, Lumiera's diff framework relies on this.
|
2015-06-06 02:40:18 +02:00
|
|
|
*/
|
2015-03-21 02:00:55 +01:00
|
|
|
template<typename VAL>
|
|
|
|
|
class Record
|
|
|
|
|
{
|
2015-08-17 02:40:57 +02:00
|
|
|
using Storage = typename RecordSetup<VAL>::Storage;
|
|
|
|
|
using ElmIter = typename RecordSetup<VAL>::ElmIter;
|
|
|
|
|
using Access = typename RecordSetup<VAL>::Access;
|
|
|
|
|
|
2015-06-05 19:17:39 +02:00
|
|
|
|
|
|
|
|
string type_;
|
2015-08-17 02:40:57 +02:00
|
|
|
Storage attribs_;
|
|
|
|
|
Storage children_;
|
2015-03-21 02:00:55 +01:00
|
|
|
|
|
|
|
|
public:
|
2015-08-17 06:17:00 +02:00
|
|
|
static const string TYPE_NIL;
|
|
|
|
|
|
2015-06-05 19:17:39 +02:00
|
|
|
Record()
|
2015-08-17 06:17:00 +02:00
|
|
|
: type_(TYPE_NIL)
|
2015-06-05 19:17:39 +02:00
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
template<typename A, typename C>
|
|
|
|
|
Record(Symbol typeID, A&& att, C&& chi)
|
2015-08-17 06:17:00 +02:00
|
|
|
: type_(isnil(typeID)? TYPE_NIL:string(typeID))
|
2015-06-05 19:17:39 +02:00
|
|
|
, 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)
|
2015-08-17 06:17:00 +02:00
|
|
|
: type_(isnil(typeID)? TYPE_NIL:string(typeID))
|
2015-06-05 19:17:39 +02:00
|
|
|
, attribs_(att)
|
|
|
|
|
, children_(chi)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
template<typename SEQ>
|
|
|
|
|
explicit
|
|
|
|
|
Record (SEQ const& con)
|
2015-08-17 06:17:00 +02:00
|
|
|
: type_(TYPE_NIL)
|
2015-06-05 19:17:39 +02:00
|
|
|
{
|
|
|
|
|
auto p = std::begin(con);
|
|
|
|
|
auto e = std::end(con);
|
|
|
|
|
for ( ; p!=e && isAttribute(*p); ++p)
|
2015-08-17 03:59:53 +02:00
|
|
|
if (isTypeID (*p))
|
|
|
|
|
type_ = extractTypeID(*p);
|
|
|
|
|
else
|
|
|
|
|
attribs_.push_back (*p);
|
2015-06-05 19:17:39 +02:00
|
|
|
for ( ; p!=e; ++p)
|
|
|
|
|
children_.push_back (*p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Record (std::initializer_list<VAL> const&& ili)
|
|
|
|
|
: Record(ili)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
// all default copy operations acceptable
|
|
|
|
|
|
|
|
|
|
|
2015-07-08 04:12:10 +02:00
|
|
|
/** for diagnostic purpose: include format-util.hpp */
|
2015-07-05 03:17:39 +02:00
|
|
|
operator std::string() const;
|
2015-06-05 19:17:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
empty() const
|
|
|
|
|
{
|
|
|
|
|
return attribs_.empty()
|
|
|
|
|
&& children_.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string
|
|
|
|
|
getType() const
|
|
|
|
|
{
|
|
|
|
|
return type_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
hasAttribute (string key) const
|
|
|
|
|
{
|
2015-07-08 04:12:10 +02:00
|
|
|
return attribs_.end() != findKey(key);
|
2015-06-05 19:17:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2015-07-05 03:17:39 +02:00
|
|
|
contains (VAL const& val) const
|
2015-06-05 19:17:39 +02:00
|
|
|
{
|
2015-07-05 03:17:39 +02:00
|
|
|
return util::contains (children_, val);
|
2015-06-05 19:17:39 +02:00
|
|
|
}
|
|
|
|
|
|
2015-08-17 02:40:57 +02:00
|
|
|
Access
|
2015-06-05 19:17:39 +02:00
|
|
|
get (string key) const
|
|
|
|
|
{
|
2015-07-08 04:12:10 +02:00
|
|
|
ElmIter found = findKey (key);
|
|
|
|
|
if (attribs_.end() == found)
|
|
|
|
|
throw error::Invalid ("Record has no attribute \""+key+"\"");
|
|
|
|
|
else
|
2015-08-17 01:22:01 +02:00
|
|
|
return extractVal (*found);
|
2015-06-05 19:17:39 +02:00
|
|
|
}
|
|
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
/**
|
|
|
|
|
* 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 sliced 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.
|
|
|
|
|
*/
|
|
|
|
|
Record (Mutator const& mut)
|
|
|
|
|
: Record((Record const&) mut)
|
|
|
|
|
{ }
|
|
|
|
|
Record (Mutator && mut)
|
|
|
|
|
: Record(std::move ((Record) mut))
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
friend class Mutator;
|
|
|
|
|
|
|
|
|
|
|
2015-06-05 19:17:39 +02:00
|
|
|
|
|
|
|
|
/* ==== Exposing scope and contents for iteration ====== */
|
|
|
|
|
|
2015-08-17 02:40:57 +02:00
|
|
|
using iterator = IterAdapter<ElmIter, const Record*>;
|
|
|
|
|
using scopeIter = typename iter_stl::_SeqT<const Storage>::Range;
|
2015-06-05 19:17:39 +02:00
|
|
|
using keyIter = TransformIter<scopeIter, string>;
|
2015-08-17 02:40:57 +02:00
|
|
|
using valIter = TransformIter<scopeIter, Access>;
|
2015-06-05 19:17:39 +02:00
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
/** default iteration exposes all data within this "object", starting with the attributes */
|
2015-08-17 06:17:00 +02:00
|
|
|
iterator begin () const { return iterator(this, attribs_.empty()? children_.begin() : attribs_.begin()); }
|
2015-06-06 01:17:42 +02:00
|
|
|
iterator end () const { return iterator(); }
|
2015-06-05 19:17:39 +02:00
|
|
|
|
|
|
|
|
scopeIter attribs() const { return iter_stl::eachElm(attribs_); }
|
2015-06-06 01:17:42 +02:00
|
|
|
scopeIter scope() const { return iter_stl::eachElm(children_); }
|
2015-06-05 19:17:39 +02:00
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
keyIter keys() const { return transformIterator(attribs(), extractKey); }
|
|
|
|
|
valIter vals() const { return transformIterator(attribs(), extractVal); }
|
2015-06-05 19:17:39 +02:00
|
|
|
|
|
|
|
|
protected: /* ==== API for the IterAdapter ==== */
|
|
|
|
|
|
|
|
|
|
/** Implementation of Iteration-logic: pull next element. */
|
|
|
|
|
friend void
|
2015-08-17 06:17:00 +02:00
|
|
|
iterNext (const Record* src, ElmIter& pos)
|
2015-06-05 19:17:39 +02:00
|
|
|
{
|
|
|
|
|
++pos;
|
|
|
|
|
checkPoint (src,pos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Implementation of Iteration-logic: detect iteration end.
|
|
|
|
|
* @remarks seamless continuation of the iteration when reaching
|
2015-08-17 01:22:01 +02:00
|
|
|
* the end of the attribute collection. In this implementation,
|
|
|
|
|
* we use the default constructed \c ITER() to mark iteration end.
|
2015-06-05 19:17:39 +02:00
|
|
|
*/
|
|
|
|
|
friend bool
|
2015-08-17 06:17:00 +02:00
|
|
|
checkPoint (const Record* src, ElmIter& pos)
|
2015-06-05 19:17:39 +02:00
|
|
|
{
|
|
|
|
|
REQUIRE (src);
|
2015-08-17 06:17:00 +02:00
|
|
|
static const ElmIter END;
|
|
|
|
|
if (pos != END && pos == src->attribs_.end() && !src->children_.empty())
|
2015-07-08 04:12:10 +02:00
|
|
|
{
|
|
|
|
|
pos = src->children_.begin();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2015-06-05 19:17:39 +02:00
|
|
|
else
|
2015-08-17 06:17:00 +02:00
|
|
|
if (pos != END && (pos != src->children_.end()))
|
2015-07-08 04:12:10 +02:00
|
|
|
return true;
|
2015-06-05 19:17:39 +02:00
|
|
|
else
|
|
|
|
|
{
|
2015-08-17 06:17:00 +02:00
|
|
|
pos = END;
|
2015-06-05 19:17:39 +02:00
|
|
|
return false;
|
|
|
|
|
} }
|
|
|
|
|
|
|
|
|
|
private:
|
2015-06-06 02:40:18 +02:00
|
|
|
/* === abstract attribute handling : needs specialisation === */
|
|
|
|
|
static bool isAttribute (VAL const& v);
|
|
|
|
|
static bool isTypeID (VAL const& v);
|
|
|
|
|
static string extractTypeID (VAL const& v);
|
2015-07-05 03:17:39 +02:00
|
|
|
static string renderAttribute (VAL const& a);
|
2015-06-06 02:40:18 +02:00
|
|
|
static string extractKey (VAL const& v);
|
2015-08-17 02:40:57 +02:00
|
|
|
static Access extractVal (VAL const& v);
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
template<typename X>
|
|
|
|
|
static VAL buildAttribute (string const& key, X&& payload);
|
2015-06-05 19:17:39 +02:00
|
|
|
|
|
|
|
|
|
2015-07-08 04:12:10 +02:00
|
|
|
ElmIter
|
|
|
|
|
findKey (string key) const
|
|
|
|
|
{
|
|
|
|
|
return std::find_if (attribs_.begin()
|
|
|
|
|
,attribs_.end()
|
|
|
|
|
,[=](VAL const& elm)
|
|
|
|
|
{
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
return key == extractKey(elm);
|
2015-07-08 04:12:10 +02:00
|
|
|
});
|
2015-08-17 01:22:01 +02:00
|
|
|
}
|
2015-07-08 04:12:10 +02:00
|
|
|
|
|
|
|
|
|
2015-06-05 19:17:39 +02:00
|
|
|
friend bool
|
|
|
|
|
operator== (Record const& r1, Record const& r2)
|
|
|
|
|
{
|
2015-07-08 04:12:10 +02:00
|
|
|
return r1.type_ == r2.type_
|
|
|
|
|
&& r1.attribs_ == r2.attribs_
|
|
|
|
|
&& r1.children_ == r2.children_;
|
2015-06-05 19:17:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
friend bool
|
|
|
|
|
operator!= (Record const& r1, Record const& r2)
|
|
|
|
|
{
|
|
|
|
|
return ! (r1 == r2);
|
|
|
|
|
}
|
2015-03-21 02:00:55 +01:00
|
|
|
};
|
|
|
|
|
|
2015-08-17 06:17:00 +02:00
|
|
|
template<typename VAL>
|
|
|
|
|
const string Record<VAL>::TYPE_NIL = "NIL";
|
|
|
|
|
|
2015-03-21 02:00:55 +01:00
|
|
|
|
|
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
template<typename VAL>
|
|
|
|
|
class Record<VAL>::Mutator
|
|
|
|
|
: boost::noncopyable
|
|
|
|
|
{
|
|
|
|
|
using Rec = Record<VAL>;
|
|
|
|
|
|
|
|
|
|
Rec record_;
|
|
|
|
|
|
|
|
|
|
public:
|
2015-07-04 14:55:36 +02:00
|
|
|
Mutator()
|
|
|
|
|
: record_()
|
|
|
|
|
{ }
|
|
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
explicit
|
|
|
|
|
Mutator (Rec const& startingPoint)
|
|
|
|
|
: record_(startingPoint)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
explicit
|
|
|
|
|
Mutator (Rec && startingPoint)
|
|
|
|
|
: record_(std::move (startingPoint))
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
operator Rec&()
|
|
|
|
|
{
|
|
|
|
|
return record_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
replace (Rec& existingInstance) noexcept
|
|
|
|
|
{
|
|
|
|
|
std::swap (existingInstance, record_);
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-04 03:39:53 +02:00
|
|
|
bool
|
|
|
|
|
empty() const
|
|
|
|
|
{
|
|
|
|
|
return record_.empty();
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
|
|
|
|
|
/* === functions to alter contents === */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
setType (string const& newTypeID)
|
|
|
|
|
{
|
|
|
|
|
record_.type_ = newTypeID;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-04 03:39:53 +02:00
|
|
|
Mutator&
|
|
|
|
|
type (string const& typeID)
|
|
|
|
|
{
|
|
|
|
|
setType (typeID);
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
template<typename X>
|
2015-07-04 03:39:53 +02:00
|
|
|
Mutator&
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
set (string const& key, X&& content)
|
2015-06-06 01:17:42 +02:00
|
|
|
{
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
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));
|
2015-07-04 03:39:53 +02:00
|
|
|
return *this;
|
2015-06-06 01:17:42 +02:00
|
|
|
}
|
|
|
|
|
|
2015-07-04 03:39:53 +02:00
|
|
|
Mutator&
|
2015-06-06 01:17:42 +02:00
|
|
|
appendChild (VAL const& newChild)
|
|
|
|
|
{
|
|
|
|
|
record_.children_.push_back (newChild);
|
2015-07-04 03:39:53 +02:00
|
|
|
return *this;
|
2015-06-06 01:17:42 +02:00
|
|
|
}
|
|
|
|
|
|
2015-07-04 03:39:53 +02:00
|
|
|
Mutator&
|
2015-06-06 01:17:42 +02:00
|
|
|
prependChild (VAL const& newChild)
|
|
|
|
|
{
|
|
|
|
|
record_.children_.insert (record_.children_.begin(), newChild);
|
2015-07-04 03:39:53 +02:00
|
|
|
return *this;
|
2015-06-06 01:17:42 +02:00
|
|
|
}
|
|
|
|
|
|
2015-07-04 03:39:53 +02:00
|
|
|
|
|
|
|
|
/* === extension point for building specific value types === */
|
|
|
|
|
/*
|
2015-08-17 02:40:57 +02:00
|
|
|
* the following builder functions need to be specialised
|
2015-07-04 03:39:53 +02:00
|
|
|
* to create a Record holding specific value types,
|
|
|
|
|
* especially for building a tree like structure
|
|
|
|
|
* with GenNode holding a Record<GenNode>
|
|
|
|
|
*/
|
|
|
|
|
|
2015-08-29 01:46:24 +02:00
|
|
|
VAL genNode();
|
|
|
|
|
VAL genNode(string const& symbolicID);
|
2015-07-04 03:39:53 +02:00
|
|
|
|
2015-07-04 15:22:04 +02:00
|
|
|
template<typename X, typename...ARGS>
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
Mutator& attrib (string const& key, X&& initialiser, ARGS&& ...args)
|
2015-07-04 15:22:04 +02:00
|
|
|
{
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
set (key, std::forward<X>(initialiser));
|
2015-07-04 15:22:04 +02:00
|
|
|
return attrib (std::forward<ARGS>(args)...);
|
|
|
|
|
}
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
Mutator& attrib () { return *this; } // argument recursion end
|
|
|
|
|
|
2015-07-04 03:39:53 +02:00
|
|
|
|
2015-07-04 15:22:04 +02:00
|
|
|
template<typename X, typename...ARGS>
|
|
|
|
|
Mutator& scope (X const& initialiser, ARGS&& ...args)
|
|
|
|
|
{
|
|
|
|
|
appendChild (VAL(initialiser));
|
|
|
|
|
return scope (std::forward<ARGS>(args)...);
|
|
|
|
|
}
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
Mutator& scope () { return *this; }
|
2015-07-04 03:39:53 +02:00
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2015-03-21 02:00:55 +01:00
|
|
|
|
2015-06-14 02:52:11 +02:00
|
|
|
/**
|
|
|
|
|
* wrapped record reference.
|
|
|
|
|
* A helper for lib::GenNode and the diff representation.
|
2015-08-17 22:13:36 +02:00
|
|
|
* RecordRef is copyable and movable, but like a reference
|
2015-06-14 02:52:11 +02:00
|
|
|
* can not be rebound. It can be used to refer to a subtree
|
|
|
|
|
* within the diff representation, without the need to copy.
|
2015-06-14 02:58:43 +02:00
|
|
|
* @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.
|
2015-06-14 02:52:11 +02:00
|
|
|
*/
|
|
|
|
|
template<typename VAL>
|
|
|
|
|
class RecordRef
|
|
|
|
|
{
|
|
|
|
|
using Target = Record<VAL>;
|
|
|
|
|
|
|
|
|
|
Target* record_;
|
2015-08-17 22:13:36 +02:00
|
|
|
|
2015-06-14 02:52:11 +02:00
|
|
|
public:
|
2015-06-14 02:58:43 +02:00
|
|
|
/** by default create an
|
|
|
|
|
* invalid ("bottom") reference */
|
|
|
|
|
RecordRef() noexcept
|
|
|
|
|
: record_(nullptr)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
/** create a reference bound to
|
|
|
|
|
* the given target; can not be rebound */
|
2015-06-14 02:52:11 +02:00
|
|
|
RecordRef(Target& o) noexcept
|
|
|
|
|
: record_(&o)
|
|
|
|
|
{ }
|
|
|
|
|
|
2015-06-14 02:58:43 +02:00
|
|
|
/** prevent moving into black hole */
|
2015-06-14 02:52:11 +02:00
|
|
|
RecordRef(Target&&) = delete;
|
|
|
|
|
|
2015-08-17 20:56:40 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2015-06-14 02:52:11 +02:00
|
|
|
|
|
|
|
|
|
2015-07-03 15:42:25 +02:00
|
|
|
explicit
|
|
|
|
|
operator bool() const
|
2015-07-04 20:50:18 +02:00
|
|
|
{
|
|
|
|
|
return empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
empty() const
|
2015-07-03 15:42:25 +02:00
|
|
|
{
|
|
|
|
|
return bool(record_);
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-14 02:58:43 +02:00
|
|
|
/** target is accessed by cast
|
|
|
|
|
* @throws error::Logic on bottom reference
|
|
|
|
|
*/
|
2015-06-14 02:52:11 +02:00
|
|
|
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_;
|
|
|
|
|
}
|
2015-08-29 21:27:33 +02:00
|
|
|
|
|
|
|
|
/** @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_;
|
|
|
|
|
}
|
2015-06-14 02:52:11 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-08-17 02:40:57 +02:00
|
|
|
/* === 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 = typename Storage::const_iterator;
|
|
|
|
|
using Access = string; ///< data access by value copy
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-14 02:52:11 +02:00
|
|
|
|
2015-08-17 02:40:57 +02:00
|
|
|
/* default handling defined for Record<string> */
|
2015-06-06 02:40:18 +02:00
|
|
|
|
|
|
|
|
template<>
|
2015-07-15 04:16:22 +02:00
|
|
|
inline string
|
|
|
|
|
Record<string>::extractKey (string const& v)
|
2015-06-06 02:40:18 +02:00
|
|
|
{
|
2015-07-15 04:16:22 +02:00
|
|
|
size_t pos = v.find('=');
|
|
|
|
|
if (string::npos == pos)
|
|
|
|
|
return "";
|
|
|
|
|
else
|
2015-08-17 01:22:01 +02:00
|
|
|
return util::trim (v.substr (0,pos));
|
2015-06-06 02:40:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
2015-07-15 04:16:22 +02:00
|
|
|
inline string
|
|
|
|
|
Record<string>::extractVal (string const& v)
|
2015-06-06 02:40:18 +02:00
|
|
|
{
|
2015-07-15 04:16:22 +02:00
|
|
|
size_t pos = v.find('=');
|
|
|
|
|
if (string::npos == pos)
|
|
|
|
|
return v;
|
|
|
|
|
else
|
2015-08-17 01:22:01 +02:00
|
|
|
return util::trim (v.substr (pos+1, v.length() - pos));
|
2015-06-06 02:40:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
2015-07-15 04:16:22 +02:00
|
|
|
inline bool
|
|
|
|
|
Record<string>::isAttribute (string const& v)
|
2015-06-06 02:40:18 +02:00
|
|
|
{
|
2015-07-15 04:16:22 +02:00
|
|
|
return string::npos != v.find('=');
|
2015-06-06 02:40:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
2015-07-15 04:16:22 +02:00
|
|
|
inline bool
|
|
|
|
|
Record<string>::isTypeID (string const& v)
|
2015-06-06 02:40:18 +02:00
|
|
|
{
|
2015-07-15 04:16:22 +02:00
|
|
|
return isAttribute(v)
|
|
|
|
|
&& "type" == extractKey(v);
|
2015-06-06 02:40:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
inline string
|
2015-07-15 04:16:22 +02:00
|
|
|
Record<string>::extractTypeID (string const& v)
|
2015-06-06 02:40:18 +02:00
|
|
|
{
|
2015-08-17 03:59:53 +02:00
|
|
|
return extractVal(v);
|
2015-06-06 02:40:18 +02:00
|
|
|
}
|
|
|
|
|
|
2015-07-05 03:17:39 +02:00
|
|
|
template<>
|
|
|
|
|
inline string
|
|
|
|
|
Record<string>::renderAttribute (string const& attrib)
|
|
|
|
|
{
|
2015-07-15 04:16:22 +02:00
|
|
|
return extractKey(attrib) + " = " + extractVal(attrib);
|
2015-07-05 03:17:39 +02:00
|
|
|
}
|
|
|
|
|
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
template<>
|
|
|
|
|
template<typename X>
|
|
|
|
|
inline string
|
|
|
|
|
Record<string>::buildAttribute (string const& key, X&& payload)
|
|
|
|
|
{
|
|
|
|
|
return string(key + " = " + extractVal(payload));
|
|
|
|
|
}
|
2015-08-17 22:13:36 +02:00
|
|
|
|
Generic Record: finish implementation of Mutator
especially setting (changing) attributes turned out to be tricky,
since in case of a GenNode this would mean to re-bind the hash ID;
we can not possibly do that properly without knowing the type of the payload,
and by design this payload type is opaque (erased).
As resort, I changed the semantics of the assign operation:
now it rather builds a new payload element, with a given initialiser.
In case of the strings, this ends up being the same operation,
while in case of GenNode, this is now something entirely different:
we can now build a new GenNode "in place" of the old one, and both
will have the same symbolic ID (attribute key). Incidentally,
our Variant implementation will reject such a re-building operatinon
when this means to change the (opaque) payload type.
in addition, I created a new API function on the Mutator,
allowing to move-in a complete attribute object. Actually this
new function became the working implementation. This way, it is
still possible to emplace a new attribute efficiently (consider
this to be a whole object graph!). But only, if the key (ID)
embedded in the attribute object is already what is the intended
key for this attribute. This way, we elegantly circumvent the
problem of having to re-bind a hash ID without knowing the type seed
2015-08-17 20:31:07 +02:00
|
|
|
|
2015-07-05 03:17:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* === Diagnostics === */
|
|
|
|
|
|
|
|
|
|
template<typename VAL>
|
|
|
|
|
Record<VAL>::operator std::string() const
|
|
|
|
|
{
|
|
|
|
|
#ifndef LIB_FORMAT_UTIL_H
|
|
|
|
|
return "Record(...)";
|
|
|
|
|
#else
|
|
|
|
|
using util::join;
|
|
|
|
|
using lib::transformIterator;
|
|
|
|
|
|
|
|
|
|
return "Rec("
|
2015-08-17 06:17:00 +02:00
|
|
|
+ (TYPE_NIL==type_? "" : type_+"| ")
|
2015-07-05 03:17:39 +02:00
|
|
|
+ join (transformIterator (this->attribs(), renderAttribute))
|
2015-08-17 03:59:53 +02:00
|
|
|
+ " |{"
|
2015-07-05 03:17:39 +02:00
|
|
|
+ join (this->scope())
|
|
|
|
|
+ "})"
|
|
|
|
|
;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-06 02:40:18 +02:00
|
|
|
|
|
|
|
|
|
2015-03-21 02:00:55 +01:00
|
|
|
}} // namespace lib::diff
|
|
|
|
|
#endif /*LIB_DIFF_GEN_NODE_H*/
|