/* TREE-DIFF-APPLICATION.hpp - language to describe differences in linearised form Copyright (C) Lumiera.org 2014, 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 tree-diff-application.hpp ** Concrete implementation(s) to apply structural changes to hierarchical ** data structures. Together with the generic #DiffApplicator, this allows ** to receive linearised structural diff descriptions and apply them to ** a given target data structure, to effect the corresponding changes. ** ** \par Design considerations ** While -- conceptually -- our tree diff handling can be seen as an extension ** and generalisation of list diffing, the decision was \em not to embody this ** extension into the implementation technically, for sake of clarity. More so, ** since the Record, which serves as foundation for our »External Tree Description«, ** was made to look and behave like a list-like entity, but built with two distinct ** scopes at implementation level: the attribute scope and the contents scope. This ** carries over to the fine points of the list diff language semantics, especially ** when it comes to fault tolerance and strictness vs fuzziness in diff application. ** The implementation is thus faced with having to deal with an internal focus and ** a switch from scope to scope, which adds a lot of complexity. So the list diff ** application strategy can be seen as blueprint and demonstration of principles. ** ** Another point in question is weather see the diff application as manipulating ** a target data structure, or rather building a reshaped copy. The fact that ** GenNode and Record are designed as immutable values seems to favour the latter, ** yet the very reason to engage into building this diff framework was how to ** handle partial updates within a expectedly very large UI model, reflecting ** the actual session model in Proc-Layer. So we end up working on a Mutator, ** which clearly signals we're going to reshape and re-rig the target data. ** ** @see diff-list-application-test.cpp ** @see VerbToken ** */ #ifndef LIB_DIFF_TREE_DIFF_APPLICATION_H #define LIB_DIFF_TREE_DIFF_APPLICATION_H #include "lib/diff/tree-diff.hpp" #include "lib/diff/gen-node.hpp" #include "lib/format-string.hpp" #include "lib/util.hpp" #include #include namespace lib { namespace diff{ using util::unConst; using util::_Fmt; using std::move; using std::swap; /** * Interpreter for the tree-diff-language to work on GenNode elements * A concrete strategy to apply a structural diff to a target data structure * made from #Record elements. This data structure is assumed to be * recursive, tree-like. But because Record elements are conceived as immutable * and value-like, the tree diff application actually works on a Rec::Mutator * wrapping the target record to be altered through consuming the diff. * @throws lumiera::error::State when diff application fails due to the * target sequence being different than assumed by the given diff. * @see #TreeDiffInterpreter explanation of the verbs */ template<> class DiffApplicationStrategy : public TreeDiffInterpreter { using Mutator = Rec::Mutator; using Content = Rec::ContentMutator; using Iter = Content::Iter; struct ScopeFrame { Mutator& target; Content content; ScopeFrame(Mutator& toModify) : target(toModify) , content() { } void init() { target.swapContent (content); } }; /** Storage: a stack of workspaces * used to handle nested child objects */ std::stack scopes_; Mutator& out() { return scopes_.top().target; } Content& src() { return scopes_.top().content; } Iter& srcPos() { return scopes_.top().content.pos; } bool endOfData() { return srcPos() == src().end(); } Rec& alteredRec() { return out(); } void __expect_in_target (GenNode const& elm, Literal oper) { if (endOfData()) throw error::State(_Fmt("Unable to %s element %s from target as demanded; " "no (further) elements in target sequence") % oper % elm , LUMIERA_ERROR_DIFF_CONFLICT); if (not srcPos()->matches(elm)) throw error::State(_Fmt("Unable to %s element %s from target as demanded; " "found element %s on current target position instead") % oper % elm % *srcPos() , LUMIERA_ERROR_DIFF_CONFLICT); } void __expect_further_elements (GenNode const& elm) { if (endOfData()) throw error::State(_Fmt("Premature end of target sequence, still expecting element %s; " "unable to apply diff further.") % elm , LUMIERA_ERROR_DIFF_CONFLICT); } void __expect_found (GenNode const& elm, Iter const& targetPos) { if (targetPos == src().end()) throw error::State(_Fmt("Premature end of sequence; unable to locate " "element %s in the remainder of the target.") % elm , LUMIERA_ERROR_DIFF_CONFLICT); } void __expect_successful_location (GenNode const& elm) { if (endOfData() and not ( elm.matches(Ref::END) // after(_END_) -> its OK we hit the end or (elm.matches(Ref::ATTRIBS) and src().children.empty()))) // after(_ATTRIBS_) -> if there are no children, it's OK to hit the end throw error::State(_Fmt("Unable locate position 'after(%s)'") % elm.idi , LUMIERA_ERROR_DIFF_CONFLICT); } void __expect_valid_parent_scope (GenNode::ID const& idi) { if (scopes_.empty()) throw error::State(_Fmt("Unbalanced child scope bracketing tokens in diff; " "When leaving scope %s, we fell out of root scope.") % idi.getSym() , LUMIERA_ERROR_DIFF_CONFLICT); if (alteredRec().empty()) throw error::State(_Fmt("Corrupted state. When leaving scope %s, " "we found an empty parent scope.") % idi.getSym() , LUMIERA_ERROR_DIFF_CONFLICT); } void __expect_end_of_scope (GenNode::ID const& idi) { if (not endOfData()) throw error::State(_Fmt("Incomplete diff: when about to leave scope %s, " "not all previously existing elements have been confirmed by the diff. " "At least one spurious element %s was left over") % idi.getSym() % *srcPos() , LUMIERA_ERROR_DIFF_CONFLICT); } Iter find_in_current_scope (GenNode const& elm) { Iter end_of_scope = src().currIsAttrib()? src().attribs.end() : src().children.end(); return std::find (srcPos(), end_of_scope, elm); } GenNode const& find_child (GenNode::ID const& idi) { for (auto & child : alteredRec()) if (child.idi == idi) return child; throw error::State(_Fmt("Attempt to mutate non existing child record; unable to locate child %s " "after applying the diff. Current scope: %s") % idi.getSym() % alteredRec() , LUMIERA_ERROR_DIFF_CONFLICT); } void move_into_new_sequence (Iter pos) { if (src().currIsAttrib()) out().appendAttrib (move(*pos)); else out().appendChild (move(*pos)); } /* == Implementation of the list diff application primitives == */ void ins (GenNode const& n) override { if (n.isNamed()) if (n.isTypeID()) out().setType (n.data.get()); else out().appendAttrib(n); else { out().appendChild(n); if (src().currIsAttrib()) src().jumpToChildScope(); } } void del (GenNode const& n) override { __expect_in_target(n, "remove"); ++src(); } void pick (GenNode const& n) override { __expect_in_target(n, "pick"); move_into_new_sequence (srcPos()); ++src(); } void skip (GenNode const& n) override { __expect_further_elements (n); ++src(); } // assume the actual content has been moved away by a previous find() void find (GenNode const& n) override { __expect_further_elements (n); Iter found = find_in_current_scope(n); __expect_found (n, found); move_into_new_sequence (found); } // consume and leave waste, expected to be cleaned-up by skip() later /* == Implementation of the tree diff application primitives == */ /** cue to a position behind the named node, * thereby picking (accepting) all traversed elements * into the reshaped new data structure as-is */ void after (GenNode const& n) override { if (n.matches(Ref::ATTRIBS)) while (not endOfData() and srcPos()->isNamed()) { move_into_new_sequence (srcPos()); ++src(); } else if (n.matches(Ref::END)) while (not endOfData()) { move_into_new_sequence (srcPos()); ++src(); } else while (not (endOfData() or srcPos()->matches(n))) { move_into_new_sequence (srcPos()); ++src(); } __expect_successful_location(n); if (not endOfData() and srcPos()->matches(n)) ++src(); // get /after/ an explicitly given position } /** open nested scope to apply diff to child object */ void mut (GenNode const& n) override { GenNode const& child = find_child (n.idi); Rec const& childRecord = child.data.get(); scopes_.emplace (mutateInPlace (unConst(childRecord))); } /** finish and leave child object scope, return to parent */ void emu (GenNode const& n) override { __expect_end_of_scope (n.idi); scopes_.pop(); __expect_valid_parent_scope (n.idi); } public: explicit DiffApplicationStrategy(Rec::Mutator& mutableTargetRecord) : scopes_() { scopes_.emplace(mutableTargetRecord); } void initDiffApplication() { REQUIRE (1 == scopes_.size()); scopes_.top().init(); } }; }} // namespace lib::diff #endif /*LIB_DIFF_TREE_DIFF_APPLICATION_H*/