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
|
2018-11-09 20:19:45 +01:00
|
|
|
** defined in terms of Record elements are linked to the _actual core_
|
2017-08-13 07:09:06 +02:00
|
|
|
** representation of the same entities relying on [diff messages](\ref diff-language.hpp).
|
2015-03-21 02:00:55 +01:00
|
|
|
** 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.
|
|
|
|
|
**
|
2018-11-09 20:19:45 +01:00
|
|
|
** 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)
|
|
|
|
|
**
|
2021-01-23 16:45:04 +01:00
|
|
|
** # design decisions
|
2015-06-04 19:26:45 +02:00
|
|
|
** The Record type is shaped from its intended use: It serves to symbolically represent
|
2018-11-09 20:19:45 +01:00
|
|
|
** *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_.
|
2017-10-27 05:12:28 +02:00
|
|
|
**
|
|
|
|
|
** \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.
|
2015-06-04 19:26:45 +02:00
|
|
|
**
|
|
|
|
|
** 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.
|
2017-10-27 05:12:28 +02:00
|
|
|
** - the parts or _realms_ within this symbolic representation are distinguished
|
2015-06-04 19:26:45 +02:00
|
|
|
** 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
|
2017-10-27 05:12:28 +02:00
|
|
|
** * children (scope contents) can be recognised by _not_ bearing a name
|
2015-06-04 19:26:45 +02:00
|
|
|
**
|
|
|
|
|
** Record entities are meant to be immutable. The proper way to alter a Record is
|
2017-10-27 05:12:28 +02:00
|
|
|
** 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.
|
2015-06-06 02:40:18 +02:00
|
|
|
**
|
|
|
|
|
** @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
|
|
|
**
|
2019-12-12 23:41:26 +01:00
|
|
|
** @see GenericRecord_test
|
2015-03-21 02:00:55 +01:00
|
|
|
**
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef LIB_DIFF_RECORD_H
|
|
|
|
|
#define LIB_DIFF_RECORD_H
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "lib/error.hpp"
|
2018-03-24 05:35:13 +01:00
|
|
|
#include "lib/nocopy.hpp"
|
2015-06-05 19:17:39 +02:00
|
|
|
#include "lib/iter-adapter.hpp"
|
|
|
|
|
#include "lib/iter-adapter-stl.hpp"
|
|
|
|
|
#include "lib/itertools.hpp"
|
2016-05-28 01:49:03 +02:00
|
|
|
#include "lib/format-util.hpp" ///////////////////////////////TICKET #973 : investigate the impact of this inclusion on code size
|
2015-07-05 03:17:39 +02:00
|
|
|
#include "lib/util.hpp"
|
2015-06-05 19:17:39 +02:00
|
|
|
|
2015-10-23 20:55:02 +02: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 {
|
2016-09-05 02:25:07 +02:00
|
|
|
|
2021-05-02 19:40:11 +02:00
|
|
|
template<class BA, class DEFAULT>
|
2016-09-05 02:25:07 +02:00
|
|
|
class PlantingHandle;
|
|
|
|
|
|
2018-11-09 22:30:35 +01:00
|
|
|
namespace idi {
|
|
|
|
|
class BareEntryID;
|
|
|
|
|
}
|
2015-03-21 02:00:55 +01:00
|
|
|
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
|
|
|
|
2016-09-05 02:25:07 +02:00
|
|
|
class TreeMutator;
|
|
|
|
|
|
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;
|
2016-02-09 01:35:32 +01:00
|
|
|
static const Symbol TYPE_NIL_SYM;
|
2015-08-17 06:17:00 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2016-02-05 15:55:22 +01:00
|
|
|
/** for diagnostic purpose */
|
2015-07-05 03:17:39 +02:00
|
|
|
operator std::string() const;
|
2015-06-05 19:17:39 +02:00
|
|
|
|
|
|
|
|
|
2016-02-05 15:55:22 +01:00
|
|
|
size_t
|
|
|
|
|
attribSize() const
|
|
|
|
|
{
|
|
|
|
|
return attribs_.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t
|
|
|
|
|
childSize() const
|
|
|
|
|
{
|
|
|
|
|
return children_.size();
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-05 19:17:39 +02:00
|
|
|
bool
|
|
|
|
|
empty() const
|
|
|
|
|
{
|
|
|
|
|
return attribs_.empty()
|
2016-06-10 02:48:22 +02:00
|
|
|
and children_.empty();
|
2015-06-05 19:17:39 +02:00
|
|
|
}
|
|
|
|
|
|
2016-02-05 15:55:22 +01:00
|
|
|
|
2015-06-05 19:17:39 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2016-01-23 17:10:44 +01:00
|
|
|
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())
|
2016-01-28 15:19:09 +01:00
|
|
|
+"["
|
|
|
|
|
,error::LUMIERA_ERROR_INDEX_BOUNDS);
|
2016-01-23 17:10:44 +01:00
|
|
|
return children_[idx];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2015-10-23 20:55:02 +02:00
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
2017-10-27 05:12:28 +02:00
|
|
|
* @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.
|
2015-06-06 01:17:42 +02:00
|
|
|
*/
|
|
|
|
|
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
|
2017-10-01 03:25:33 +02:00
|
|
|
iterNext (const Record*, ElmIter& pos)
|
2015-06-05 19:17:39 +02:00
|
|
|
{
|
|
|
|
|
++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_
|
2015-09-25 02:38:59 +02:00
|
|
|
and r1.attribs_ == r2.attribs_
|
|
|
|
|
and r1.children_ == r2.children_;
|
2015-06-05 19:17:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
friend bool
|
|
|
|
|
operator!= (Record const& r1, Record const& r2)
|
|
|
|
|
{
|
2015-09-25 02:38:59 +02:00
|
|
|
return not (r1 == r2);
|
2015-06-05 19:17:39 +02:00
|
|
|
}
|
2015-03-21 02:00:55 +01:00
|
|
|
};
|
|
|
|
|
|
2015-08-17 06:17:00 +02:00
|
|
|
template<typename VAL>
|
2016-02-09 01:35:32 +01:00
|
|
|
const Symbol Record<VAL>::TYPE_NIL_SYM = "NIL";
|
|
|
|
|
|
|
|
|
|
template<typename VAL>
|
|
|
|
|
const string Record<VAL>::TYPE_NIL = string(TYPE_NIL_SYM);
|
2015-08-17 06:17:00 +02:00
|
|
|
|
2015-03-21 02:00:55 +01:00
|
|
|
|
|
|
|
|
|
2015-06-06 01:17:42 +02:00
|
|
|
template<typename VAL>
|
|
|
|
|
class Record<VAL>::Mutator
|
2018-03-24 05:35:13 +01:00
|
|
|
: util::NonCopyable
|
2015-06-06 01:17:42 +02:00
|
|
|
{
|
|
|
|
|
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
|
2016-03-03 22:58:33 +01:00
|
|
|
swap (Rec& existingInstance) noexcept
|
2015-06-06 01:17:42 +02:00
|
|
|
{
|
|
|
|
|
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-10-24 03:15:35 +02:00
|
|
|
Mutator&
|
|
|
|
|
appendAttrib (VAL const& newAttrib)
|
|
|
|
|
{
|
|
|
|
|
REQUIRE (Rec::isAttribute(newAttrib));
|
|
|
|
|
record_.attribs_.push_back (newAttrib);
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2016-09-05 02:25:07 +02:00
|
|
|
/* === low-level access (for diff application) === */
|
|
|
|
|
|
2021-05-02 19:40:11 +02:00
|
|
|
using BufferHandle = PlantingHandle<TreeMutator, TreeMutator>;
|
2016-09-05 02:25:07 +02:00
|
|
|
|
|
|
|
|
/** 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);
|
2015-10-23 00:40:02 +02:00
|
|
|
|
2016-08-31 18:40:09 +02:00
|
|
|
auto
|
|
|
|
|
exposeToDiff()
|
|
|
|
|
{
|
|
|
|
|
return std::tie (record_.attribs_, record_.children_);
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-04 03:39:53 +02:00
|
|
|
|
2015-11-01 03:29:35 +01:00
|
|
|
/** 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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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();
|
2018-11-09 22:30:35 +01:00
|
|
|
VAL genNode(idi::BareEntryID rawID);
|
2015-08-29 01:46:24 +02:00
|
|
|
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-10-30 04:45:22 +01:00
|
|
|
/** 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.
|
2015-10-30 21:44:43 +01:00
|
|
|
* @todo do we have a design mismatch here?? /////////////////////////////////////////TICKET #970
|
2015-10-30 04:45:22 +01:00
|
|
|
* @see tree-diff-application.hpp
|
|
|
|
|
*/
|
|
|
|
|
template<typename VAL>
|
|
|
|
|
inline typename Record<VAL>::Mutator&
|
|
|
|
|
mutateInPlace (Record<VAL>& record_to_mutate)
|
|
|
|
|
{
|
|
|
|
|
return reinterpret_cast<typename Record<VAL>::Mutator &> (record_to_mutate);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
{
|
2015-09-17 19:39:34 +02:00
|
|
|
return bool(record_);
|
2015-07-04 20:50:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
empty() const
|
2015-07-03 15:42:25 +02:00
|
|
|
{
|
2015-09-25 02:38:59 +02:00
|
|
|
return not record_;
|
2015-07-03 15:42:25 +02:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
using util::join;
|
|
|
|
|
using lib::transformIterator;
|
|
|
|
|
|
|
|
|
|
return "Rec("
|
2015-12-05 03:57:11 +01:00
|
|
|
+ (TYPE_NIL==type_? "" : type_)
|
|
|
|
|
+ (isnil(this->attribs())? "" : "| "+join (transformIterator (this->attribs(), renderAttribute))+" ")
|
|
|
|
|
+ (isnil(this->scope())? "" : "|{"+join (this->scope())+"}")
|
|
|
|
|
+ ")"
|
2015-07-05 03:17:39 +02:00
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
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*/
|