123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238(* This file is part of BOGUE, by San Vu Ngoc *)(* Flow data structure
A flow is a simple FIFO queue like Queue.t which can be rewinded to its
initial state. As a consequence, an iteration of a flow can be stopped and
resumed later without destroying the queue. *)(* This file is part of BOGUE but can be used independently. *)exceptionEnd_reachedtype'acell_cons={content:'a;mutablenext:'acell}and'acell=|Nil|Consof'acell_constype'at={mutablecurrent:'acell;mutablefirst:'acell;mutablelast:'acell}letcreate()={current=Nil;first=Nil;last=Nil}letis_emptyq=q.first=Nil(* q.last = Nil would work too *)letend_reachedq=q.current=Nilletclearq=q.current<-Nil;q.first<-Nil;q.last<-Nilletrewindq=q.current<-q.first(* forget the past of the flow. *)letforgetq=q.first<-q.current(* Add [x] to the queue. If the current pointer is [Nil] (end reached) then it
will point to the added element. *)letaddxq=letcell=Cons{content=x;next=Nil}inmatchq.lastwith|Nil->(* q is empty *)assert(is_emptyq);q.current<-cell;q.first<-cell;q.last<-cell|Conslast->last.next<-cell;q.last<-cell;ifq.current=Nilthenq.current<-cell(* Read and advance in the flow, but do not remove from the queue. *)letreadq=matchq.currentwith|Nil->raiseEnd_reached|Cons{content;next}->q.current<-next;contentletread_optq=matchq.currentwith|Nil->None|Cons{content;next}->q.current<-next;Somecontent(* TODO use read_opt? *)letiter=letreciterqf=function|Nil->()|Cons{content;next}->q.current<-next;fcontent;iterqfnextinfunfq->iterqfq.current;q.current<-Nil(* Stops when the result of [f] evaluated on the queue element is true. *)letiter_untilfq=letrecloopf=function|Nil->q.current<-Nil|Cons{content;next}->q.current<-next;ifnot(fcontent)thenloopfnextinloopfq.current(* Check if [q] contains an element [x] for which [f x] returns true (starting
from current position) *)letexistsfq=letrecloop=function|Nil->false|Cons{content;next}->fcontent||loopnextinloopq.current(* Could be optimized if necessary *)letof_listl=letq=create()inList.iter(funx->addxq)l;q(* Removing an arbitrary element is not very adapted to this data structure,
except of course popping out the first element. The following functions are
for occasional use. *)(* The following functions use physical equality [==] on union types, which
seems to be implementation dependent according to the manual. Hence we
redefine [==] to have a better control over it. But it seems to work with the
original [Stdlib.==] as well. Note that [Cons x == Cons x] is FALSE for
[Stdlib.(==)] but TRUE for our redefined equality. However we were careful
not to encounter this case anyway. *)let(==)cell1cell2=matchcell1,cell2with|Nil,Nil->true|Consc1,Consc2->Stdlib.(==)c1c2(* this one is well-defined because of
the mutable field "next". *)|_->false(* [remove_first_match_after f q] removes the first q element (starting from
*next* to current) for which f evals to true. *)letremove_first_match_after=letrecloopfqprevious_cell=matchprevious_cellwith|Nil->raiseEnd_reached|Consprevious->matchprevious.nextwith|Nil->raiseNot_found|Conscurr->iffcurr.contentthenbeginifq.last==previous.next(* we want to remove the last element *)thenq.last<-previous_cell;previous.next<-curr.next;endelseloopfqprevious.nextinfunfq->loopfqq.currentletremove_currentq=matchq.currentwith|Nil->raiseEnd_reached|Conscurr->ifq.current==q.first(* easy case *)thenbeginifq.last==q.currentthenq.last<-Nil;q.first<-curr.next;q.current<-curr.next;endelse(* bad case, we have to start from top *)letrecloopprevious=matchprevious.nextwith|Nil->failwith"Should not happen"|Consc->ifprevious.next==q.currentthenbeginprevious.next<-c.next;q.current<-c.nextendelseloopcinmatchq.firstwith|Nil->failwith"Empty flow head: should not happen"|Consp->loopp(* Remove the first element (starting from the top of the flow) for which f
evals true. In case the removed element was current, the next one becomes
current. *)letremove_first_matchfq=matchq.currentwith|Cons{content;next=_}->iffcontentthenremove_currentqelseremove_first_match_afterfq|Nil->raiseEnd_reached(* Slow. only for debugging *)letlength_fromcell=letrecloopicell=matchcellwith|Nil->i|Cons{content=_;next}->loop(i+1)nextinloop0celllettotal_lengthq=length_fromq.firstletlengthq=length_fromq.currentlettest()=letq=of_list[1;2;3]inassert(not(is_emptyq));assert(not(end_reachedq));assert(readq=1);assert(readq=2);assert(readq=3);assert(end_reachedq);assert(not(is_emptyq));add4q;assert(readq=4);rewindq;iter_until(funx->x>=2)q;assert(readq=3);assert(readq=4);assert(end_reachedq);rewindq;iter(fun_->())q;assert(end_reachedq);rewindq;iter_until(funx->x>=20)q;assert(end_reachedq);clearq;assert(is_emptyq);assert(read_optq=None);add1q;remove_currentq;assert(end_reachedq);add1q;add2q;remove_currentq;assert(exists(funx->x=2)q);assert(readq=2);assert(not(exists(funx->x=2)q))