Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file b_chain.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379(* bidirectional ordered chained lists (mutable, not thread-safe) *)(* San Vu Ngoc *)(* This file is part of Bogue but can be used independently *)(* A chain is morally just a list with a pointer indicating the current element
we're looking at. The implementation should provide an easy and fast way to
access the element before and the element after. The whole chain (that is,
the connected component of any of its elements) is called a stack. There is
no particular type for a stack. *)(* TODO we could use a phantom type when we want to ensure the chain is not
empty. See
https://blog.janestreet.com/howto-static-access-control-using-phantom-types/*)moduleUtils=B_utilsletdebug=!Utils.debugexceptionMax_inserttype'aelement={id:int;(* [id] identifies the stack (connected component) *)mutablevalue:'a;mutabledepth:int;(* [depth] (positive integer for non-empty Chain, zero for empty) is a
redundant information, in order to get faster comparison between
chains. The rule is that the .next element must have higher depth. A
consequence is that the number of elements cannot exceed max_int - 2
(here = 4611686018427387901) *)mutableprev:('aelement)option;mutablenext:('aelement)option}letnew_id=Utils.fresh_int()type'at='aelementoption(* None = empty chain *)(* The only non trivial (fun) part of the implementation is to decide what
"depth" should be attributed to an element when adding or inserting a new
element in a chain. In our implementation, the two directions "prev" and
"next" are not symmetric. In a symmetric implementation, each insertion would
cut the depth interval in two equal parts, hence, since max_int = 2^62, in
the (very) worst case, we can roughly only insert 62 elements to a chain
before we need to reattribute depths. Here we decide that "insert_after" is
more common than "insert_before" -- we will use the Chains for graphic
layers, and it's more usual to add a layer on top of the previous one rather
than inserting a layer "below" an existing one.
So, when appending a new element, we simply add a constant value to the
depth: sqrt(max_int). Therefore we may append sqrt(max_int) elements in a
row. *)letdx=ifdebugthen10elseint_of_float(sqrt(floatmax_int))(* Since max_int = 4611686018427387903 on a 64bits machine, dx = 2147483648 *)letsingletonvalue=Some{id=new_id();value;depth=dx;prev=None;next=None}letget_stack_id=function|None->invalid_arg"[Chain.get_stack_id] Empty Chain has no stack id."|Somea->a.id(* same as Option.iter f o *)letdo_optionof=matchowith|Somex->fx|None->()letnext=function|None->None|Somet->t.nextletprev=function|None->None|Somet->t.prevletvalue=function|None->invalid_arg"[Chain.value] Empty chain has no value."|Somea->a.valueletdepth=function|None->0|Somea->a.depthletrecfirst=function|None->None|Somea->matcha.prevwith|None->Somea|b->firstbletreclast=function|None->None|Somea->matcha.nextwith|None->Somea|b->lastbletsame_stackt1t2=matcht1,t2with|None,None|None,Some_|Some_,None->false|Somex1,Somex2->x1.id=x2.idletcomp(x:int)(y:int)=Stdlib.comparexyletcompare_elementsx1x2=ifx1.id<>x2.idthenfailwith"Cannot compare chains in different stacks"elsecompx1.depthx2.depthletcomparet1t2=matcht1,t2with|None,None->0|Some_,None->1|None,Some_->-1|Somex1,Somex2->compare_elementsx1x2let(==)t1t2=comparet1t2=0(* t1 >. t2 if [depth t1 > depth t2] (when in the same stack). So ">" means
"deeper than". *)let(>.)t1t2=comparet1t2>0letmint1t2=ift1>.t2thent2elset1(*(* We discard all empty chains when computing the minimum *)*)(* match t1, t2 with
* | None, None -> None
* | Some _, None -> t1
* | None, Some _ -> t2
* | Some x1, Some x2 -> if compare_elements x1 x2 > 0 then t2 else t1 *)letsizet=letrecloopti=matchtwith|None->i|Somet->loopt.next(i+1)inloop(firstt)0letis_emptyt=t=None(* redistribute depth values *)letevenizet=letdx=max_int/(sizet+2)inifdx=0thenfailwith"Chain too large"(* in principe this cannot happen *)elseletrecloopdt=matchtwith|None->()|Somea->a.depth<-d;loop(d+dx)a.nextinloopdx(firstt)(* the return value points to the inserted element *)letinsert_aftertvalue=letn=nexttinletid,depth=matchtwith|None->new_id(),dx|Somex->matchnwith|None->x.id,x.depth+dx|Somex'->letd=x'.depth-x.depthinifd<2thenraiseMax_insert(* TODO: en fait on peut encore décaler le suivant ! *)elsex.id,x.depth+d/2inlett'=Some{id;value;depth;prev=t;next=n}inUtils.(printddebug_memory"New layer created with depth: %u\n"depth);do_optiont(funx->x.next<-t');do_optionn(funx->x.prev<-t');t'letinsert_aftertvalue=tryinsert_aftertvaluewith|Max_insert->Utils.(printddebug_memory"Need to evenize chain...");evenizet;insert_aftertvalue|e->raiseeletinsert_beforetvalue=letp=prevtinletid,depth=matchtwith|None->new_id(),dx|Somex->letd'=matchpwith|None->0|Somex'->x'.depthinletd=x.depth-d'inifd<2thenraiseMax_insert(* TODO: en fait on peut encore décaler le suivant ! *)elsex.id,x.depth-d/2inlett'=Some{id;value;depth;prev=p;next=t}inUtils.(printddebug_memory"New layer created with depth: %u\n"depth);do_optiont(funx->x.prev<-t');do_optionp(funx->x.next<-t');t'letinsert_beforetvalue=tryinsert_beforetvaluewith|Max_insert->Utils.(printddebug_memory"Need to evenize chain...");evenizet;insert_beforetvalue|e->raiseeletreplacetvalue=matchtwith|None->invalid_arg"[Chain.replace] Cannot set value to empty Chain."|Somea->a.value<-value(* [remove t] removes the element pointed by t in the stack and returns the next
element. Then [t] becomes isolated and should be discarded. *)(* not used *)letremove=function|None->invalid_arg"Cannot remove element of empty Chain."|Somea->do_optiona.prev(funp->p.next<-a.next);do_optiona.next(funn->n.prev<-a.prev);a.prev<-None;lett=a.nextina.next<-None;t(* return the ordered list of values of the whole stack. *)letto_listt=letrecloopxlist=matchxwith|None->list|Somea->loopa.prev(a.value::list)inloop(lastt)[](* Create a chain from a list of values. The return value points to the last
element of the list *)letof_listlist:'at=letid=new_id()inlett,_=List.fold_left(fun(t,depth)value->lett'=Some{id;value;depth;next=None;prev=t}indo_optiont(funb->b.next<-t');(t',depth+dx))(None,dx)listint(* iter on values (not elements) starting from the given position *)letreciter_downf=function|None->()|Somea->fa.value;iter_downfa.next(* iter on values (not elements) of the whole chain *)letiterft=iter_downf(firstt)letreciter_upf=function|None->()|Somea->fa.value;iter_upfa.prevletiter_upft=iter_upf(lastt)(* iter on 'real elements' (no option) *)letreciter_elements_downf=function|None->()|Somea->fa;iter_elements_downfa.nextletiter_elementsft=iter_elements_downf(firstt)letfilltvalue=iter_elements(funt->t.value<-value)tletinsert_chain_before~dstt=iter(funv->ignore(insert_beforedstv))t(* of course this could be done more efficiently = in constant time if we didn't
have to compute depth, or if the depths of the subchain were strictly included
between t and (next t) *)letinsert_chain_after~dstt=iter_up(funv->ignore(insert_afterdstv))tletprint_depthst=iter_elements(funa->Printf.printf"depth=%d\n"a.depth)t(* [copy t] returns a copy of the chain in a new stack, still pointing to the
same (copied) element. *)(* not used *)letcopy=function|None->None|Somea0->letid=new_id()inleta0'={a0withid}in(* copy t=(Some a, Some b,...) into t'=(Some a', Some b', ...) *)letrecloop_downa'=function|None->assert(a'.next=None)|Someb->(* One could use [insert_after] but that function is a bit too general
since here we know we append at the end of the stack. *)letb'={bwithid;prev=Somea'}ina'.next<-Someb';loop_downb'b.nextinletrecloop_upa'=function|None->assert(a'.prev=None)|Someb->(* Same remark as above, for [insert_before]. *)letb'={bwithid;next=Somea'}ina'.prev<-Someb';loop_upb'b.previnloop_downa0'a0.next;loop_upa0'a0.prev;Somea0'(* [copy_into ~dst:t s] copy the element pointed by [s] into a different stack
[t] with keeping the depth. Its position is determined by its depth. Does
nothing if the depth is already occupied. Return the copied element. Does not
modify [s]. Does not copy the whole chain of [s]: see [copy] for this. [t]
may be empty (then a new stack is created).
This weird function can serve to extract a subchain from an existing chain,
in order to move it to another stack: this is why we use it in Bogue for
sending layouts to a different window. Another possibility would be to use
[copy] and then remove the unwanted elements. Another possibility would be to
get a list of elements we want to extract (removing doublons), sort it, and
then use [of_list]. This does not preserve exact depths, but preserve the
order. *)letcopy_into~dst:t=function|None->Utils.(printddebug_warning"Copying an empty Chain has no effect.");None|Someaass->ifsame_stacksttheninvalid_arg"[Chain.copy_into] Cannot copy a chain element into the same stack."elseletrecsearch_positiont0=function|None->t0,None|Somebast1->ifa.depth>b.depththensearch_positiont1b.nextelset0,t1in(* t0 < Some a <= t1 *)letid=matchtwithNone->new_id()|Someb->b.idinleta'={awithid}inmatchsearch_positionNone(firstt)with|None,None->a'.next<-None;a'.prev<-None;Somea'|Somea0ast0,None->a'.next<-None;a'.prev<-t0;a0.next<-Somea';Somea'|None,(Somea1ast1)->ifa1.depth<>a.depththenbegina'.prev<-None;a'.next<-t1;a1.prev<-Somea';Somea'endelset1|Somea0ast0,(Somea1ast1)->ifa1.depth<>a.depththenbeginlettt=Somea'ina'.prev<-t0;a'.next<-t1;a0.next<-tt;a1.prev<-tt;ttendelset1