/* RECORD.hpp - collection to represent object-like data Copyright (C) Lumiera.org 2015, Hermann Vosseler 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. ** ** \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 ** ** * metadata is very limited and boils down to magic attributes known by name ** * 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 ** to apply a \link tree-diff.hpp diff \endlink ** ** \par rationale ** The underlying theme of this design is negative, dialectical: we do not want to ** 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). ** - moreover, we assume that the value type allows to somehow to embed ** the key of each attribute; the implementation needs an explicit ** specialisation of the binding functions for each value type. ** - this header defines a specialisation for VAL = std::string -- ** while the most relevant specialisation for GenNode is provided ** alongside with this special, monadic value type. ** - an alternative implementation approach would have been to use a ** dedicated helper type to represent the collection of attributes. ** This type might then be specialised, e.g. to utilise an index table ** for key-value lookup. However, in the light of the intended usage ** of Record entities as tree nodes within a GenNode monad, such a ** more elaborate approach was deemed unnecessary for the time being. ** ** @see GenericRecordRepresentation_test ** */ #ifndef LIB_DIFF_RECORD_H #define LIB_DIFF_RECORD_H #include "lib/error.hpp" #include "lib/iter-adapter.hpp" #include "lib/iter-adapter-stl.hpp" #include "lib/itertools.hpp" #include "lib/util.hpp" #include #include #include #include #include namespace lib { namespace diff{ namespace error = lumiera::error; using std::string; /** * 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 -- using the * embedded name-ID of the GenNode elements as key for attributes. * * Record elements are meant to be immutable; they can be created from a * defining collection. However, we provide a #Mutator mechanism to allow * for rebuilding and mutating symbolic data structures based on Records * and GenNode. Essentially, Lumiera's diff framework relies on this. */ template class Record { using _Vec = std::vector; using Attribs = _Vec; using Children = _Vec; using ElmIter = typename _Vec::const_iterator; string type_; Attribs attribs_; Children children_; public: Record() : type_("NIL") { } template Record(Symbol typeID, A&& att, C&& chi) : type_(typeID) , attribs_(std::forward (att)) , children_(std::forward (chi)) { } template Record(Symbol typeID, std::initializer_list const&& att , std::initializer_list const&& chi) : type_(typeID) , attribs_(att) , children_(chi) { } template explicit Record (SEQ const& con) : type_("NIL") { auto p = std::begin(con); auto e = std::end(con); if (p!=e && isTypeID (*p)) type_ = extractTypeID(*(p++)); for ( ; p!=e && isAttribute(*p); ++p) attribs_.push_back (*p); for ( ; p!=e; ++p) children_.push_back (*p); } Record (std::initializer_list const&& ili) : Record(ili) { } // all default copy operations acceptable /** for diagnostic purpose: include format-util.hpp */ operator std::string() const; bool empty() const { return attribs_.empty() && children_.empty(); } string getType() const { return type_; } bool hasAttribute (string key) const { return attribs_.end() != findKey(key); } bool contains (VAL const& val) const { return util::contains (children_, val); } VAL const& get (string key) const { ElmIter found = findKey (key); if (attribs_.end() == found) throw error::Invalid ("Record has no attribute \""+key+"\""); else return extractVal (*found); } /** * 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; /* ==== Exposing scope and contents for iteration ====== */ using iterator = IterAdapter; using scopeIter = typename iter_stl::_SeqT::Range; using keyIter = TransformIter; using valIter = TransformIter; /** default iteration exposes all data within this "object", starting with the attributes */ iterator begin () const { return iterator(this, attribs_.begin()); } iterator end () const { return iterator(); } scopeIter attribs() const { return iter_stl::eachElm(attribs_); } scopeIter scope() const { return iter_stl::eachElm(children_); } keyIter keys() const { return transformIterator(attribs(), extractKey); } valIter vals() const { return transformIterator(attribs(), extractVal); } protected: /* ==== API for the IterAdapter ==== */ /** Implementation of Iteration-logic: pull next element. */ template friend void iterNext (const Record* src, ITER& pos) { ++pos; checkPoint (src,pos); } /** Implementation of Iteration-logic: detect iteration end. * @remarks seamless continuation of the iteration when reaching * the end of the attribute collection. In this implementation, * we use the default constructed \c ITER() to mark iteration end. */ template friend bool checkPoint (const Record* src, ITER& pos) { REQUIRE (src); if ((pos != ITER()) && (pos == src->attribs_.end()) && !src->children_.empty()) { pos = src->children_.begin(); return true; } else if (pos != ITER() && (pos != src->children_.end())) return true; else { pos = ITER(); return false; } } private: /* === abstract attribute handling : needs specialisation === */ static bool isAttribute (VAL const& v); static bool isTypeID (VAL const& v); static string extractTypeID (VAL const& v); static VAL buildTypeAttribute (string const& typeID); static string renderAttribute (VAL const& a); static string extractKey (VAL const& v); static VAL extractVal (VAL const& v); ElmIter findKey (string key) const { return std::find_if (attribs_.begin() ,attribs_.end() ,[=](VAL const& elm) { return key == extractKey(elm); }); } friend bool operator== (Record const& r1, Record const& r2) { return r1.type_ == r2.type_ && r1.attribs_ == r2.attribs_ && r1.children_ == r2.children_; } friend bool operator!= (Record const& r1, Record const& r2) { return ! (r1 == r2); } }; template class Record::Mutator : boost::noncopyable { using Rec = Record; Rec record_; public: Mutator() : record_() { } 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_); } bool empty() const { return record_.empty(); } /* === functions to alter contents === */ void setType (string const& newTypeID) { set ("type", Rec::buildTypeAttribute (newTypeID)); record_.type_ = newTypeID; } Mutator& type (string const& typeID) { setType (typeID); return *this; } Mutator& set (string const& key, VAL const& newValue) { ///////////TODO; return *this; } Mutator& appendChild (VAL const& newChild) { record_.children_.push_back (newChild); return *this; } Mutator& prependChild (VAL const& newChild) { record_.children_.insert (record_.children_.begin(), newChild); return *this; } /* === extension point for building specific value types === */ /* * the following builder functions are to be specialised * to create a Record holding specific value types, * especially for building a tree like structure * with GenNode holding a Record */ VAL&& genNode(); VAL&& genNode(string const& symbolicID); template Mutator& attrib (string const& key, X const& initialiser, ARGS&& ...args) { set (key, VAL(initialiser)); return attrib (std::forward(args)...); } template Mutator& attrib (string const& key, X const& initialiser) { set (key, VAL(initialiser)); return *this; } template Mutator& scope (X const& initialiser, ARGS&& ...args) { appendChild (VAL(initialiser)); return scope (std::forward(args)...); } template Mutator& scope (X const& initialiser) { appendChild (VAL(initialiser)); return *this; } }; /** * wrapped record reference. * A helper for lib::GenNode and the diff representation. * RecordRef is copyable and assignable, but like a reference * can not be rebound. It can be used to refer to a subtree * within the diff representation, without the need to copy. * @remarks this is almost identical to std::ref, with the * notable difference that it can be default-created * into "bottom" state; this also implies to have * a NULL check on dereferentiation. */ template class RecordRef { using Target = Record; Target* record_; public: /** by default create an * invalid ("bottom") reference */ RecordRef() noexcept : record_(nullptr) { } /** create a reference bound to * the given target; can not be rebound */ RecordRef(Target& o) noexcept : record_(&o) { } /** prevent moving into black hole */ RecordRef(Target&&) = delete; // standard copy operations acceptable explicit operator bool() const { return empty(); } bool empty() const { return bool(record_); } /** target is accessed by cast * @throws error::Logic on bottom reference */ operator Target&() const { if (!record_) throw error::Logic("attempt to dereference an unbound record reference" ,error::LUMIERA_ERROR_BOTTOM_VALUE); return *record_; } Target* get() const noexcept { return record_; } }; /* === Specialisations to define the handling of attributes === */ template<> inline string Record::extractKey (string const& v) { size_t pos = v.find('='); if (string::npos == pos) return ""; else return util::trim (v.substr (0,pos)); } template<> inline string Record::extractVal (string const& v) { size_t pos = v.find('='); if (string::npos == pos) return v; else return util::trim (v.substr (pos+1, v.length() - pos)); } template<> inline bool Record::isAttribute (string const& v) { return string::npos != v.find('='); } template<> inline bool Record::isTypeID (string const& v) { return isAttribute(v) && "type" == extractKey(v); } template<> inline string Record::extractTypeID (string const& v) { return "todo"; //////////////////////////////////////////TODO do we really need this function? } template<> inline string Record::buildTypeAttribute (string const& typeID) { return "type = "+typeID; } template<> inline string Record::renderAttribute (string const& attrib) { return extractKey(attrib) + " = " + extractVal(attrib); } /* === Diagnostics === */ template Record::operator std::string() const { #ifndef LIB_FORMAT_UTIL_H return "Record(...)"; #else using util::join; using lib::transformIterator; return "Rec(" + join (transformIterator (this->attribs(), renderAttribute)) + "|{" + join (this->scope()) + "})" ; #endif } }} // namespace lib::diff #endif /*LIB_DIFF_GEN_NODE_H*/