2016-07-24 15:16:06 +02:00
/*
TreeDiffMutatorBinding - implementation of diff application to opaque data
Copyright ( C ) Lumiera . org
2016 , 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 0213 9 , USA .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** @file tree-diff-mutator-binding.cpp
* * Implementation of diff application to unspecific private data structures .
* * This binding is the link between a generic interpreter for our
* * » tree diff language « and a concrete TreeMutator implementation ,
* * as provided by the target data structure . We do not require much
* * additional knowledge regarding the opaque target structure , beyond
* * the ability to construct such a customised TreeMutator . For this reason ,
* * the implementation is mostly generic and thus can be emitted here within
* * the library module - - with the exception of the ctor , which indeed picks
* * up some specifics of the concrete usage situation and thus needs to be
* * generated in usage context .
* *
* * @ see tree - diff . cpp
* * @ see tree - diff - mutator - binding . cpp
* * @ see DiffVirtualisedApplication_test
* *
*/
# include "lib/error.hpp"
# include "lib/diff/tree-diff-mutator-binding.hpp"
namespace lib {
namespace diff {
2016-07-29 01:46:11 +02:00
ScopeManager : : ~ ScopeManager ( ) { } ; ///< emit VTable here...
2016-07-24 15:16:06 +02:00
/* ======= Implementation of Tree Diff Application via TreeMutator ======= */
using util : : unConst ;
using util : : cStr ;
using util : : _Fmt ;
using std : : move ;
using std : : swap ;
# if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
2016-07-25 15:21:30 +02:00
using Mutator = Rec : : Mutator ;
using Content = Rec : : ContentMutator ;
using Iter = Content : : Iter ;
2016-07-24 15:16:06 +02:00
template < >
2016-07-28 01:17:50 +02:00
struct TreeDiffMutatorBinding : : ScopeFrame
2016-07-24 15:16:06 +02:00
{
2016-07-25 15:21:30 +02:00
Mutator & target ;
Content content ;
ScopeFrame ( Mutator & toModify )
: target ( toModify )
, content ( )
{ }
2016-07-24 15:16:06 +02:00
2016-07-25 15:21:30 +02:00
void init ( )
2016-07-24 15:16:06 +02:00
{
2016-07-25 15:21:30 +02:00
target . swapContent ( content ) ;
content . resetPos ( ) ;
}
} ;
2016-07-24 15:16:06 +02:00
/** 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 ( ) ; } /////TODO split into an actual scope end check and an non-null check
Rec & alteredRec ( ) { return out ( ) ; }
2016-07-25 15:21:30 +02:00
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_in_target ( GenNode const & elm , Literal oper )
2016-07-25 15:21:30 +02:00
{
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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_further_elements ( GenNode const & elm )
2016-07-25 15:21:30 +02:00
{
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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_found ( GenNode const & elm , Iter const & targetPos )
2016-07-25 15:21:30 +02:00
{
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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_successful_location ( GenNode const & elm )
2016-07-25 15:21:30 +02:00
{
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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_valid_parent_scope ( GenNode : : ID const & idi )
2016-07-25 15:21:30 +02:00
{
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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_end_of_scope ( GenNode : : ID const & idi )
2016-07-25 15:21:30 +02:00
{
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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : find_in_current_scope ( GenNode const & elm )
2016-07-25 15:21:30 +02:00
{
Iter end_of_scope = src ( ) . currIsAttrib ( ) ? src ( ) . attribs . end ( )
: src ( ) . children . end ( ) ;
return std : : find_if ( srcPos ( )
, end_of_scope
, [ & ] ( auto & entry )
{
return entry . matches ( elm ) ;
} ) ;
}
GenNode const &
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : find_child ( GenNode : : ID const & idi )
2016-07-25 15:21:30 +02:00
{
if ( alteredRec ( ) . empty ( ) )
throw error : : State ( _Fmt ( " Attempt to mutate element %s, but current target data scope is empty. "
" Sender and receiver out of sync? " ) % idi . getSym ( )
, LUMIERA_ERROR_DIFF_CONFLICT ) ;
// Short-cut-mutation: look at the last element.
// this should be the one just added. BUT NOTE: this fails
// when adding an attribute after entering the child scope.
// Since attributes are typically values and not mutated,
// this inaccuracy was deemed acceptable
auto & current = out ( ) . accessLast ( ) ;
if ( Ref : : THIS . matches ( idi ) or current . matches ( idi ) )
return current ;
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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : move_into_new_sequence ( Iter pos )
2016-07-25 15:21:30 +02:00
{
if ( src ( ) . currIsAttrib ( ) )
out ( ) . appendAttrib ( move ( * pos ) ) ; //////////////TICKET #969 was it a good idea to allow adding attributes "after the fact"?
else
out ( ) . appendChild ( move ( * pos ) ) ;
}
2016-07-24 15:16:06 +02:00
# endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
2016-07-25 15:21:30 +02:00
/* == Forwarding: error handling == */
2016-08-10 03:29:32 +02:00
2016-07-25 15:21:30 +02:00
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_in_target ( GenNode const & elm , Literal oper )
2016-07-25 15:21:30 +02:00
{
2016-08-10 03:29:32 +02:00
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 ) ;
// /////////////////////////////////////////////////////////////TODO: is there any chance to uphold this special syntactic construct?? otherwise drop it from the language!
// if (elm.matches(Ref::CHILD) and not srcPos()->isNamed())
// return; // allow for anonymous pick or delete of children
if ( not treeMutator_ - > matchSrc ( 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 ( ) /////////////////////TODO still relevant?
, LUMIERA_ERROR_DIFF_CONFLICT ) ;
2016-07-25 15:21:30 +02:00
}
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_further_elements ( GenNode const & elm )
2016-07-25 15:21:30 +02:00
{
2016-08-10 03:29:32 +02:00
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 ) ;
2016-07-25 15:21:30 +02:00
}
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __fail_not_found ( GenNode const & elm )
2016-07-25 15:21:30 +02:00
{
2016-08-10 03:29:32 +02:00
throw error : : State ( _Fmt ( " Premature end of sequence; unable to locate "
" element %s in the remainder of the target. " ) % elm
, LUMIERA_ERROR_DIFF_CONFLICT ) ;
2016-07-25 15:21:30 +02:00
}
2016-07-31 00:32:03 +02:00
void
TreeDiffMutatorBinding : : __failMismatch ( GenNode const & spec , Literal oper )
{
2016-08-25 18:42:51 +02:00
throw error : : State ( _Fmt ( " Unable to %s element %s. Current shape of target "
" data does not match expectations " ) % oper % spec
2016-07-31 00:32:03 +02:00
, LUMIERA_ERROR_DIFF_CONFLICT ) ;
}
2016-07-25 15:21:30 +02:00
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_end_of_scope ( GenNode : : ID const & idi )
2016-07-25 15:21:30 +02:00
{
2016-08-26 02:42:19 +02:00
if ( not treeMutator_ - > completeScope ( ) )
throw error : : State ( _Fmt ( " Diff application floundered in nested scope %s; "
" unexpected extra elements found when diff "
" should have settled everything. " ) % idi . getSym ( )
, LUMIERA_ERROR_DIFF_CONFLICT ) ;
2016-07-25 15:21:30 +02:00
}
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : __expect_valid_parent_scope ( GenNode : : ID const & idi )
2016-07-25 15:21:30 +02:00
{
2016-08-25 18:42:51 +02:00
if ( 0 = = scopeManger_ - > depth ( ) )
throw error : : Fatal ( _Fmt ( " Diff application floundered after leaving scope %s; "
" unbalanced nested scopes, diff attempts to pop root. " ) % idi . getSym ( )
, LUMIERA_ERROR_DIFF_CONFLICT ) ;
2016-07-25 15:21:30 +02:00
}
/* == Implementation of the list diff application primitives == */
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : ins ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
investigate and confirm the logic underlying the matchSrc, skipSrc and acceptSrc primitives
In Theory, acceptSrc and skipSrc are to operate symmetrically,
with the sole difference that skipSrc does not move anything
into the new content.
BUT, since skipSrc is also used to implement the `skip` verb,
which serves to discard garbage left back by a preceeding `find`,
we cannot touch the data found in the src position without risk
of SEGFAULT. For this reason, there is a dedicated matchSrc operation,
which shall be used to generate the verification step to properly
implement the `del` verb.
I've spent quite some time to verify the logic of predicate evaluation.
It seems to be OK: whenever the SELECTOR applies, then we'll perform
the local match, and then also we'll perform the skipSrc. Otherwise,
we'll delegate both operations likewise to the next lower layer,
without touching anything here.
2016-08-09 23:42:42 +02:00
bool success = treeMutator_ - > injectNew ( n ) ;
2016-07-31 00:32:03 +02:00
if ( not success )
__failMismatch ( n , " insert " ) ;
2016-07-25 15:21:30 +02:00
}
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : del ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
__expect_in_target ( n , " remove " ) ;
2016-08-10 03:29:32 +02:00
treeMutator_ - > skipSrc ( n ) ;
2016-07-25 15:21:30 +02:00
}
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : pick ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
2016-08-10 03:29:32 +02:00
// __expect_in_target(n, "pick"); ////////////////////////TODO TOD-o ?
bool success = treeMutator_ - > acceptSrc ( n ) ;
if ( not success )
__failMismatch ( n , " pick " ) ;
2016-07-25 15:21:30 +02:00
}
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : skip ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
__expect_further_elements ( n ) ;
2016-08-10 03:29:32 +02:00
treeMutator_ - > skipSrc ( n ) ;
2016-07-25 15:21:30 +02:00
} // assume the actual content has been moved away by a previous find()
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : find ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
__expect_further_elements ( n ) ;
// consume and leave waste, expected to be cleaned-up by skip() later
2016-08-10 03:29:32 +02:00
if ( not treeMutator_ - > findSrc ( n ) )
2016-07-25 15:21:30 +02:00
__fail_not_found ( n ) ;
}
/* == 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
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : after ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
2016-08-25 17:48:40 +02:00
if ( not treeMutator_ - > accept_until ( n ) )
2016-07-25 15:21:30 +02:00
__fail_not_found ( n ) ;
}
/** assignment of changed value in one step */
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : set ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
2016-08-25 18:42:51 +02:00
if ( not treeMutator_ - > assignElm ( n ) )
__failMismatch ( n , " assign " ) ;
2016-07-25 15:21:30 +02:00
}
/** open nested scope to apply diff to child object */
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : mut ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
2016-08-26 02:42:19 +02:00
TreeMutator : : Handle buffHandle = scopeManger_ - > openScope ( ) ;
if ( not treeMutator_ - > mutateChild ( n , buffHandle ) )
__failMismatch ( n , " enter nested scope " ) ;
2016-07-25 15:21:30 +02:00
2016-08-26 02:42:19 +02:00
TRACE ( diff , " tree-diff: ENTER scope %s " , cStr ( n . idi ) ) ;
treeMutator_ = buffHandle . get ( ) ;
2016-07-25 15:21:30 +02:00
}
/** finish and leave child object scope, return to parent */
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : emu ( GenNode const & n )
2016-07-25 15:21:30 +02:00
{
2016-08-26 02:42:19 +02:00
TRACE ( diff , " tree-diff: LEAVE scope %s " , cStr ( n . idi ) ) ;
2016-07-25 15:21:30 +02:00
__expect_end_of_scope ( n . idi ) ;
2016-08-26 02:42:19 +02:00
treeMutator_ = & scopeManger_ - > closeScope ( ) ;
2016-07-25 15:21:30 +02:00
__expect_valid_parent_scope ( n . idi ) ;
}
void
2016-07-28 01:17:50 +02:00
TreeDiffMutatorBinding : : initDiffApplication ( )
2016-07-25 15:21:30 +02:00
{
2016-07-30 18:23:45 +02:00
REQUIRE ( scopeManger_ ) ;
REQUIRE ( treeMutator_ ) ;
2016-07-25 15:21:30 +02:00
}
2016-07-24 15:16:06 +02:00
} } // namespace lib::diff