/* GEN-NODE.hpp - generic node element for tree like data representation 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 gen-node.hpp ** Generic building block for tree shaped (meta)data structures. ** A representation built from GenNode elements is intended to support ** (limited) introspection of data structures and exchange of mutations ** in the form of \link diff-language.hpp diff messages. \endlink ** ** Despite of the name, GenNode is \em not meant to be an universal ** data representation; rather it is limited to embody a fixed hard ** wired set of data types, able to stand-in for attributes ** and sub scope contents of the lumiera high-level data model. ** ** \par Anatomy of a GenNode ** ** GenNode is a polymorphic value with well defined identity and type. ** Each element is conceived to be »unique within context« -- as defined ** by the immediately visible scope within a tree like structure. ** Beyond this identity metadata, each GenNode carries a DataCap, which ** is an inline container and attachment point for payload data. Simple ** attribute values can be carried alongside, while more complex types ** or entities bound to a reference and registration system (e.g. Placement) ** will be referred by a suitable reference representation (PlacementID). ** The DataCap is what creates the polymorphic nature, where the common ** interface is mostly limited to managemental tasks (copying of values, ** external representation). ** ** To represent object-like structures and for building trees, a special ** kind of data type is placed into the DataCap. This type, Record ** is recursive and has the ability to hold both a a set of attributes ** addressable by-name and an (ordered) collection of elements treated ** as children within the scope of the given record. ** ** \par Requirements ** ** GenNode elements are to be used in the diff detection and implementation. ** This implies some requirements for the (opaque) elements used in diff: ** - they need to support the notion of equality ** - we need to derive a key type for usage in index tables ** - this implies the necessity to support std::less comparisons for tree-maps ** - and the necessity to support hash code generation for unordered (hash)maps ** - moreover, the elements need to be values, able to be copied and handled at will ** - it will be beneficial for these values to support move semantics explicitly ** - in addition, the tree diffing suggests a mechanism to re-gain the fully ** typed context, either based on some kind of embedded type tag, or ** alternatively by visitation and matching ** - finally, the handling of changes prompts us to support installation ** of a specifically typed change handling closure. ** ** \par monadic nature ** ** As suggested by the usage for representation of tree shaped data, we acknowledge ** that GenNode is a Monad. We support the basic operations \em construction ** and \em flatMap. To fit in with this generic processing pattern, the one element ** flavours of GenNode are considered the special case, while the collective flavours ** form the base case -- every GenNode can be iterated. The \em construction requirement ** suggests that GenNode may be created readily, just by wrapping any given and suitable ** element, thereby picking up the element's type. For sake of code organisation and ** dependency management, we solve this requirement with the help of a trait type, ** expecting the actual usage to supply the necessary specialisations on site. ** ** @todo the purpose and goal of the monadic approach is not clear yet (5/2015). ** To begin with, for the task of diff detection and application, it is sufficient ** to get the children as traversable collection ** ** @see GenNodeBasic_test ** @see diff-list-generation-test.cpp ** @see DiffDetector ** */ #ifndef LIB_DIFF_GEN_NODE_H #define LIB_DIFF_GEN_NODE_H #include "lib/error.hpp" #include "lib/idi/entry-id.hpp" #include "lib/time/timevalue.hpp" //#include "lib/util.hpp" //#include "lib/format-string.hpp" //#include "lib/format-util.hpp" #include "lib/diff/record.hpp" #include "lib/variant.hpp" #include "lib/util.hpp" //#include #include #include #include //#include namespace lib { namespace diff{ namespace error = lumiera::error; using std::string; struct GenNode; /** Define actual data storage and access types used */ template<> struct RecordSetup { using Storage = std::vector; using ElmIter = typename Storage::const_iterator; /** using const reference data access * relevant for handling large subtrees */ using Access = GenNode const&; }; using Rec = Record; using RecRef = RecordRef; using MakeRec = Rec::Mutator; using DataValues = meta::Types; class DataCap : public Variant { public: template DataCap(X&& x) : Variant(std::forward(x)) { } ////////////////////////TICKET #963 Forwarding shadows copy operations -- generic solution?? DataCap(DataCap const&) =default; DataCap(DataCap&&) =default; DataCap(DataCap& o) : DataCap((DataCap const&)o) { } DataCap& operator= (DataCap const&) =default; DataCap& operator= (DataCap&&) =default; bool matchData (DataCap const&) const; bool matchNum (int64_t) const; bool matchTxt (string const&) const; bool matchTime (time::TimeValue) const; bool matchBool (bool) const; bool matchDbl (double) const; bool matchLuid (hash::LuidH) const; bool matchRec (RecRef const&) const; bool matchRec (Rec const&) const; operator string() const; }; /** generic data element node within a tree */ struct GenNode { class ID : public idi::BareEntryID { friend struct GenNode; template ID (X*, string const& symbolicID) : idi::BareEntryID (symbolicID, idi::getTypeHash()) { } public: ID (GenNode const& node) : ID(node.idi) { } // standard copy operations acceptable operator string() const { return "ID(\""+getSym()+"\")"; } }; //------GenNode Data fields--- ID idi; DataCap data; template GenNode(X&& val) : idi(&val, buildChildID()) , data(std::forward(val)) { } template GenNode(string const& symbolicID, X&& val) : idi(&val, symbolicID) , data(std::forward(val)) { } GenNode(string const& symbolicID, const char* text) : GenNode(symbolicID, string(text)) { } GenNode(const char* text) : GenNode(string(text)) { } ////////////////////////TICKET #963 Forwarding shadows copy operations -- generic solution?? GenNode(GenNode const&) =default; GenNode(GenNode&&) =default; GenNode(GenNode& o) : GenNode((GenNode const&)o) { } GenNode& operator= (GenNode const&) =default; GenNode& operator= (GenNode&&) =default; /** @internal diagnostics helper. Need to include format-helper.cpp */ operator string() const { return "GenNode-"+string(idi)+"-"+string(data); } bool isNamed() const { return !util::startsWith (idi.getSym(), "_CHILD_"); } bool contains (GenNode const& elm) const { return contains (elm.idi); } bool contains (ID const&) const { UNIMPLEMENTED("containment check by ID"); } bool matches (GenNode const& o) const { return o == *this; } bool matches (ID const& id) const { return idi == id; } bool matches (int number) const { return data.matchNum(number);} bool matches (int64_t number) const { return data.matchNum(number);} bool matches (short number) const { return data.matchNum(number);} bool matches (char number) const { return data.matchNum(number);} bool matches (double number) const { return data.matchDbl(number);} bool matches (string text) const { return data.matchTxt(text);} bool matches (const char* text) const { return data.matchTxt(text);} bool matches (time::TimeValue t) const { return data.matchTime(t); } bool matches (bool b) const { return data.matchBool(b); } bool matches (hash::LuidH h) const { return data.matchLuid(h); } bool matches (RecRef const& ref) const { return data.matchRec(ref); } bool matches (Rec const& rec) const { return data.matchRec(rec); } class ScopeExplorer; friend string name (GenNode const& node) { return node.idi.getSym(); } friend bool operator== (GenNode const& n1, GenNode const& n2) { return n1.idi == n2.idi && n1.data.matchData(n2.data); } friend bool operator!= (GenNode const& n1, GenNode const& n2) { return ! (n1 == n2); } protected: /** @internal for dedicated builder subclasses */ GenNode (ID&& id, DataCap&& d) : idi(std::move(id)) , data(std::move(d)) { } template static GenNode::ID fabricateRefID (string const& symbolicID) { X* typeID(0); return ID(typeID, symbolicID); } private: template static string buildChildID() { return "_CHILD_" + idi::generateSymbolicID(); } }; /** * Monad-like depth-first expansion of a GenNode */ class GenNode::ScopeExplorer { struct Locator { const GenNode* node_; Rec::iterator scope_; Locator() : node_(nullptr) { } Locator(GenNode const& n) : node_(&n) { } Locator(Rec const& r) : node_(nullptr) , scope_(r.begin()) { } friend bool checkPoint (Locator const& loc) { return bool(node_) || bool(scope_); } friend GenNode const& yield (Locator const& loc) { return node_? *node_ : *scope_; } friend void iterNext (Locator & loc) { if (node_) node_ = nullptr; else ++scope_; } }; using ScopeIter = IterStateWrapper; std::deque scopes_; public: ScopeExplorer() { } ScopeExplorer(GenNode const& n) : scopes_({n}) { } /* === Iteration control API for IterStateWrapper == */ friend bool checkPoint (ScopeExplorer const& explorer) { return !explorer.scopes_.empty() && bool(explorer.scopes_.back()); } friend GenNode const& yield (ScopeExplorer const& explorer) { return * (explorer.scopes_.back()); } friend void iterNext (ScopeExplorer & explorer) { ScopeIter& current = explorer.scopes_.back(); explorer.scopes_.emplace_back(current->data.expand()); ++current; while (!explorer.scopes_.empty() && !explorer.scopes_.back()) explorer.scopes_.pop_back(); } }; /** * Constructor for a specially crafted 'ref GenNode'. * The identity record of the generated object will be prepared * such as to be identical to a regular GenNode with Record payload. * @note slicing in usage is intentional */ struct Ref : GenNode { /** create an empty ID stand-in. * @note the purpose is to create a symbolic reference by name */ explicit Ref(string const& symbolicID) : GenNode(fabricateRefID (symbolicID) , DataCap(RecRef())) // note: places NIL into the reference part { } /** build reference to a Record, using the original ID * @throw error::Logic when oNode does not hold a Record */ Ref(GenNode& oNode) : GenNode(ID(oNode) , DataCap(RecRef(oNode.data.get()))) { } static Ref END; ///< symbolic ID ref "_END_" static Ref THIS; ///< symbolic ID ref "_THIS_" static Ref CHILD; ///< symbolic ID ref "_CHILD_" static Ref ATTRIBS; ///< symbolic ID ref "_ATTRIBS_" }; /* === Specialisation to add fluent GenNode builder API to Record === */ template<> inline GenNode MakeRec::genNode() { return GenNode(std::move(record_)); } template<> inline GenNode MakeRec::genNode(string const& symbolicID) { return GenNode(symbolicID, std::move(record_)); } /* === Specialisation for handling of attributes in Record === */ template<> inline bool Rec::isAttribute (GenNode const& attrib) { return attrib.isNamed(); } template<> inline bool Rec::isTypeID (GenNode const& v) { return "type" == v.idi.getSym(); } template<> inline string Rec::extractTypeID (GenNode const& v) { return isTypeID(v)? v.data.get() : Rec::TYPE_NIL; } template<> inline string Rec::extractKey (GenNode const& v) { return isAttribute(v)? v.idi.getSym() : ""; } template<> inline GenNode const& Rec::extractVal (GenNode const& v) { return v; } template<> inline string Rec::renderAttribute (GenNode const& a) { return a.idi.getSym() +" = "+ string(a.data); } template<> template inline GenNode Rec::buildAttribute (string const& key, X&& payload) { return GenNode(key, payload); } }} // namespace lib::diff #endif /*LIB_DIFF_GEN_NODE_H*/