lumiera_documentation/src/lib/diff/tree-diff-application.hpp
Ichthyostega daa13ab6dc implement anonymous pick or delete of children
...while I must admit that I'm a bit doubtful about that
language feature, but it does come in handy when manually
writing diff messages. The reason is the automatic naming
of child objects, which makes it often hard to refer to
a child after the fact, since the name can not be
reconstructed systematically.

Obviously the downside of this "anonymous pick / delete"
is that we allow to pick (accept) or even delete just
any child, which happens to sit there, without being
able to detect a synchronisation mismatch between
sender and receiver.
2015-11-01 02:33:35 +01:00

349 lines
12 KiB
C++

/*
TREE-DIFF-APPLICATION.hpp - language to describe differences in linearised form
Copyright (C) Lumiera.org
2014, 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 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 <utility>
#include <stack>
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<GenNode> 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<Rec::Mutator>
: 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<ScopeFrame> 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 (elm.matches(Ref::CHILD) and not srcPos()->isNamed())
return; // allow for anonymous pick or delete of children
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<string>());
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<Rec>();
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*/