123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208(**************************************************************************)(* *)(* OCaml *)(* *)(* Florian Angeletti, projet Cambium, Inria Paris *)(* *)(* Copyright 2021 Institut National de Recherche en Informatique et *)(* en Automatique. *)(* *)(* All rights reserved. This file is distributed under the terms of *)(* the GNU Lesser General Public License version 2.1, with the *)(* special exception on linking described in the file LICENSE. *)(* *)(**************************************************************************)type'awith_pos={pos:int;data:'a}letwith_posl=List.mapi(funndata->{pos=n+1;data})l(** Composite change and mismatches *)type('l,'r,'diff)mismatch=|Nameof{pos:int;got:string;expected:string;types_match:bool}|Typeof{pos:int;got:'l;expected:'r;reason:'diff}type('l,'r,'diff)change=|Changeof('l,'r,'diff)mismatch|Swapof{pos:int*int;first:string;last:string}|Moveof{name:string;got:int;expected:int}|Insertof{pos:int;insert:'r}|Deleteof{pos:int;delete:'l}letprefixppfx=letkind=matchxwith|Change_|Swap_|Move_->Diffing.Modification|Insert_->Diffing.Insertion|Delete_->Diffing.Deletioninletstylekppfinner=letsty=Diffing.stylekinFormat.pp_open_stagppf(Misc.Color.Stylesty);Format.kfprintf(funppf->Format.pp_close_stagppf())ppfinnerinmatchxwith|Change(Name{pos;_}|Type{pos;_})|Insert{pos;_}|Delete{pos;_}->stylekindppf"%i. "pos|Swap{pos=left,right;_}->stylekindppf"%i<->%i. "leftright|Move{got;expected;_}->stylekindppf"%i->%i. "expectedgot(** To detect [move] and [swaps], we are using the fact that
there are 2-cycles in the graph of name renaming.
- [Change (x,y,_) is then an edge from
[key_left x] to [key_right y].
- [Insert x] is an edge between the special node epsilon and
[key_left x]
- [Delete x] is an edge between [key_right] and the epsilon node
Since for 2-cycle, knowing one edge is enough to identify the cycle
it might belong to, we are using maps of partial 2-cycles.
*)moduleTwo_cycle:sigtypet=private(string*string)valcreate:string->string->tend=structtypet=string*stringletcreatekxky=ifkx<=kythenkx,kyelseky,kxendmoduleSwap=Map.Make(structtypet=Two_cycle.tletcompare:t->t->int=Stdlib.compareend)moduleMove=Misc.String.MapmoduleDefine(D:Diffing.Defswithtypeeq:=unit)=structmoduleInternal_defs=structtypeleft=D.leftwith_postyperight=D.rightwith_postypediff=(D.left,D.right,D.diff)mismatchtypeeq=unittypestate=D.stateendmoduleDiff=Diffing.Define(Internal_defs)typeleft=Internal_defs.lefttyperight=Internal_defs.righttypediff=(D.left,D.right,D.diff)mismatchtypecomposite_change=(D.left,D.right,D.diff)changetypenonrecchange=(left,right,unit,diff)Diffing.changetypepatch=composite_changelistmoduletypeParameters=sigincludeDiff.Parameterswithtypeupdate_result:=D.statevalkey_left:D.left->stringvalkey_right:D.right->stringendmoduleSimple(Impl:Parameters)=structopenImpl(** Partial 2-cycles *)type('l,'r)partial_cycle=|Leftofint*D.state*'l|Rightofint*D.state*'r|BothofD.state*'l*'r(** Compute the partial cycle and edge associated to an edge *)letedgestate(x:left)(y:right)=letkx,ky=key_leftx.data,key_righty.datainletedge=ifkx<=kythenLeft(x.pos,state,(x,y))elseRight(x.pos,state,(x,y))inTwo_cycle.createkxky,edgeletmerge_edgeexey=matchex,eywith|ex,None->Someex|Left(lpos,lstate,l),SomeRight(rpos,rstate,r)|Right(rpos,rstate,r),SomeLeft(lpos,lstate,l)->letstate=iflpos<rposthenrstateelselstateinSome(Both(state,l,r))|Both_asb,_|_,Some(Both_asb)->Someb|l,_->Somellettwo_cyclesstatechanges=letadd(state,(swaps,moves))(d:change)=updatedstate,matchdwith|Change(x,y,_)->letk,edge=edgestatexyinSwap.updatek(merge_edgeedge)swaps,moves|Insertnx->letk=key_rightnx.datainletedge=Right(nx.pos,state,nx)inswaps,Move.updatek(merge_edgeedge)moves|Deletenx->letk,edge=key_leftnx.data,Left(nx.pos,state,nx)inswaps,Move.updatek(merge_edgeedge)moves|_->swaps,movesinList.fold_leftadd(state,(Swap.empty,Move.empty))changes(** Check if an edge belongs to a known 2-cycle *)letswapswapsxy=letkx,ky=key_leftx.data,key_righty.datainletkey=Two_cycle.createkxkyinmatchSwap.find_optkeyswapswith|None|Some(Left_|Right_)->None|SomeBoth(state,(ll,lr),(rl,rr))->matchteststatellrr,teststaterllrwith|Ok_,Ok_->Some({pos=ll.pos;data=kx},{pos=rl.pos;data=ky})|Error_,_|_,Error_->Noneletmovemovesx=letname=matchxwith|Either.Leftx->key_leftx.data|Either.Rightx->key_rightx.datainmatchMove.find_optnamemoveswith|None|Some(Left_|Right_)->None|SomeBoth(state,got,expected)->matchteststategotexpectedwith|Ok_->Some(Move{name;got=got.pos;expected=expected.pos})|Error_->Noneletrefinestatepatch=let_,(swaps,moves)=two_cyclesstatepatchinletfilter:change->composite_changeoption=function|Keep_->None|Insertx->beginmatchmovemoves(Either.Rightx)with|Some_asmove->move|None->Some(Insert{pos=x.pos;insert=x.data})end|Deletex->beginmatchmovemoves(Either.Leftx)with|Some_->None|None->Some(Delete{pos=x.pos;delete=x.data})end|Change(x,y,reason)->matchswapswapsxywith|Some({pos=pos1;data=first},{pos=pos2;data=last})->ifx.pos=pos1thenSome(Swap{pos=pos1,pos2;first;last})elseNone|None->Some(Changereason)inList.filter_mapfilterpatchletdiffstateleftright=letleft=with_posleftinletright=with_posrightinletmoduleRaw=Diff.Simple(Impl)inletraw=Raw.diffstate(Array.of_listleft)(Array.of_listright)inrefinestaterawendend