123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695openCore_kernelopenImportopenKindmoduleInternal_observer=Types.Internal_observermoduleNode=Types.NodeopenNodetype'at='aTypes.Node.t={(* [id] is a unique id for the node. *)id:Node_id.t;state:(Types.State.t[@sexp.opaque]);(* The fields from [recomputed_at] to [created_in] are grouped together and are in the
same order as they are used by [State.recompute] This has a positive performance
impact due to cache effects. Don't change the order of these nodes without
performance testing. *)(* [recomputed_at] is the last stabilization when [t]'s value was recomputed, even if
it was cut off. *)mutablerecomputed_at:Stabilization_num.t;(* [value_opt] starts as [none], and the first time [t] is computed it is set to
[some], and remains [some] thereafter, until [t] is invalidated, if ever. *)mutablevalue_opt:'aUopt.t;(* [kind] is the kind of DAG node [t] is. [kind] is mutable both for initialization
and because it can change, e.g. if [t] is invalidated. *)mutablekind:'aKind.t;mutablecutoff:'aCutoff.t;(* [changed_at] is the last stabilization when this node was computed and not cut off.
It is used to detect when [t]'s parents are stale and (because all parents are
necessary) need to be recomputed. *)mutablechanged_at:Stabilization_num.t;(* [num_on_update_handlers] is [List.length t.on_update_handlers] plus the number of
on-update handlers summed over all observers in [t.observers]. It is used to
quickly decide whether [t] needs to be added to [state.handle_after_stabilization]
when [t] changes. [num_on_update_handlers] will decrease when an observer is
removed from [t.observers], if the observer has on-update handlers. *)mutablenum_on_update_handlers:int;(* The parents of [t] are the nodes that depend on it, and should be computed when [t]
changes, once all of their other children are up to date. [num_parents] is the
number of parents. If [num_parents >= 1], then [parent0] is the first parent.
[parent1_and_beyond] holds the remaining parents. The order of the parents doesn't
matter. One node may occur multiple times as a parent of another (e.g. consider
[map2 n1 n1 ~f]).
This representation is optimized for the overwhelmingly common case that a node has
only one parent. *)mutablenum_parents:int;mutableparent1_and_beyond:Packed.tUopt.tarray;mutableparent0:Packed.tUopt.t;(* [created_in] is initially the scope that the node is created in. If a node is
later "rescoped", then created_in will be adjusted to the new scope that the node
is part of. *)mutablecreated_in:Scope.t;(* [next_node_in_same_scope] singly links all nodes created in [t.created_in]. *)mutablenext_node_in_same_scope:Packed.tUopt.t;(* [height] is used to visit nodes in topological order. If [is_necessary t], then
[height > c.height] for all children [c] of [t], and [height > Scope.height
t.created_in]. If [not (is_necessary t)], then [height = -1]. *)mutableheight:int;(* [height_in_recompute_heap] is the height at which [t] is stored in the recompute
heap, and is non-negative iff [t] is in the recompute heap. If [t] is the
recompute heap, then typically [t.height = t.height_in_recompute_heap]; however,
while height is being adjusted, one can temporarily have [t.height >
t.height_in_recompute_heap]. When height adjustment finishes, equality is restored
by increasing [t.height_in_recompute_heap] to [t.height] and shifting [t]'s
position in the recompute heap. *)mutableheight_in_recompute_heap:int;(* [prev_in_recompute_heap] and [next_in_recompute_heap] doubly link all nodes of the
same height in the recompute heap. *)mutableprev_in_recompute_heap:Packed.tUopt.t;mutablenext_in_recompute_heap:Packed.tUopt.t;(* [height_in_adjust_heights_heap] is used only during height adjustment, and is
non-negative iff [t] is in the adjust-heights heap. It holds the pre-adjusted
height of [t]. *)mutableheight_in_adjust_heights_heap:int;(* [next_in_adjust_heights_heap] singly links all nodes of the same height in the
adjust-heights heap. *)mutablenext_in_adjust_heights_heap:Packed.tUopt.t;(* [old_value_opt] is used only during stabilization, and only if
[t.num_on_update_handlers > 0]. It holds the pre-stabilization value of [t]. It
is cleared when running [t]'s on-update handlers, and so is always [Uopt.none]
between stabilizations. *)mutableold_value_opt:'aUopt.t;(* [observers] is the head of the doubly-linked list of observers of [t], or
[Uopt.none] if there are no observers. *)mutableobservers:('aInternal_observer.t[@sexp.opaque])Uopt.t;(* [is_in_handle_after_stabilization] is used to avoid pushing the same node multiple
times onto [state.handle_after_stabilization]. *)mutableis_in_handle_after_stabilization:bool;(* [on_update_handlers] is the functions supplied to [Incremental.on_update] to be run
as described in the module [On_update_handler]. [on_update_handlers] does not
contain the on-update handlers in [t.observers]. [on_update_handlers] only ever
gets longer; there is no way to remove elements. *)mutableon_update_handlers:'aOn_update_handler.tlist;mutablemy_parent_index_in_child_at_index:intarray;mutablemy_child_index_in_parent_at_index:intarray;mutableforce_necessary:bool;mutableuser_info:Info.toption;creation_backtrace:Backtrace.toption}[@@derivingfields,sexp_of]letsame(t1:_t)(t2:_t)=phys_samet1t2letpacked_same(Packed.Tt1)(Packed.Tt2)=samet1t2letset_user_infotuser_info=t.user_info<-user_infoletis_necessary=Node.is_necessaryletinitial_num_childrent=Kind.initial_num_childrent.kindletiteri_childrent~f=Kind.iteri_childrent.kind~fletis_valid=Node.is_validlettype_equal_if_phys_same=type_equal_if_phys_sameletedge_is_stale~child~parent=Stabilization_num.comparechild.changed_atparent.recomputed_at>0;;letis_stale_with_respect_to_a_childt=letis_stale=reffalseiniteri_childrent~f:(fun_(Tchild)->ifedge_is_stale~child~parent:tthenis_stale:=true);!is_stale;;letis_stale:typea.at->bool=fun(t:at)->matcht.kindwith|Uninitialized->assertfalse(* A const node is stale only at initialization. *)|Const_->Stabilization_num.is_nonet.recomputed_at(* Time-based nodes are considered stale when [t.recomputed_at] is none, which happens
at initialization and when the alarm mechanism makes a node stale (it sets the
[t.recomputed_at] to [Stabilization_num.none]). *)|At_->Stabilization_num.is_nonet.recomputed_at|At_intervals_->Stabilization_num.is_nonet.recomputed_at|Snapshot_->Stabilization_num.is_nonet.recomputed_at(* We never consider an invalidated node to be stale -- when we invalidate a node, we
immediately propagate invalidity to its ancestors. *)|Invalid->false(* A [Var] node is stale if it was set since it was recomputed. *)|Var{set_at;_}->Stabilization_num.compareset_att.recomputed_at>0(* Nodes that have children. *)|Bind_lhs_change_->Stabilization_num.is_nonet.recomputed_at||is_stale_with_respect_to_a_childt|If_test_change_->Stabilization_num.is_nonet.recomputed_at||is_stale_with_respect_to_a_childt|Join_lhs_change_->Stabilization_num.is_nonet.recomputed_at||is_stale_with_respect_to_a_childt|Array_fold_|Bind_main_|Freeze_|If_then_else_|Join_main_|Map_|Map2_|Map3_|Map4_|Map5_|Map6_|Map7_|Map8_|Map9_|Map10_|Map11_|Map12_|Map13_|Map14_|Map15_|Step_function_|Unordered_array_fold_->Stabilization_num.is_nonet.recomputed_at||is_stale_with_respect_to_a_childt|Expert{force_stale;_}->force_stale||Stabilization_num.is_nonet.recomputed_at||is_stale_with_respect_to_a_childt;;letneeds_to_be_computedt=is_necessaryt&&is_staletletis_in_recompute_heapt=t.height_in_recompute_heap>=0letis_in_adjust_heights_heapt=t.height_in_adjust_heights_heap>=0letget_parentt~index=Uopt.value_exn(ifindex=0thent.parent0elset.parent1_and_beyond.(index-1));;letiteri_parentst~f=ift.num_parents>0then(f0(Uopt.value_exnt.parent0);forindex=1tot.num_parents-1dofindex(Uopt.value_exnt.parent1_and_beyond.(index-1))done);;lethas_childt~child=lethas=reffalseiniteri_childrent~f:(fun_(Tchild')->has:=!has||samechildchild');!has;;lethas_invalid_childt=lethas=reffalseiniteri_childrent~f:(fun_(Tchild)->has:=!has||not(is_validchild));!has;;lethas_parent(t:_t)~parent=lethas=reffalseiniteri_parentst~f:(fun_(Tparent')->has:=!has||sameparentparent');!has;;letshould_be_invalidated:typea.at->bool=funt->matcht.kindwith(* nodes with no children *)|Uninitialized->assertfalse|At_->false|At_intervals_->false|Const_|Snapshot_|Var_->false|Invalid->false(* Nodes with a fixed set of children are invalid if any child is invalid. *)|Array_fold_|Freeze_|Map_|Map2_|Map3_|Map4_|Map5_|Map6_|Map7_|Map8_|Map9_|Map10_|Map11_|Map12_|Map13_|Map14_|Map15_|Step_function_|Unordered_array_fold_->has_invalid_childt(* A *_change node is invalid if the node it is watching for changes is invalid (same
reason as above). This is equivalent to [has_invalid_child t]. *)|Bind_lhs_change{lhs;_}->not(is_validlhs)|If_test_change{test;_}->not(is_validtest)|Join_lhs_change{lhs;_}->not(is_validlhs)(* [Bind_main], [If_then_else], and [Join_main] are invalid if their *_change child is,
but not necessarily if their other children are -- the graph may be restructured to
avoid the invalidity of those. *)|Bind_main{lhs_change;_}->not(is_validlhs_change)|If_then_else{test_change;_}->not(is_validtest_change)|Join_main{lhs_change;_}->not(is_validlhs_change)|Expert_->(* This is similar to what we do for bind above, except that any invalid child can be
removed, so we can only tell if an expert node becomes invalid when all its
dependencies have fired (which in practice means when we are about to run it). *)false;;letfold_observers(t:_t)~init~f=letr=reft.observersinletac=refinitinwhileUopt.is_some!rdoletobserver=Uopt.value_exn!rinr:=observer.next_in_observing;ac:=f!acobserverdone;!ac;;letiter_observerst~f=fold_observerst~init:()~f:(fun()observer->fobserver)letinvariant(typea)(invariant_a:a->unit)(t:at)=Invariant.invariant[%here]t[%sexp_of:_t](fun()->[%test_eq:bool](needs_to_be_computedt)(is_in_recompute_heapt);ifis_necessarytthen(assert(t.height>Scope.heightt.created_in);iteri_childrent~f:(fun_(Tchild)->assert(t.height>child.height);assert(has_parentchild~parent:t));assert(not(should_be_invalidatedt)));iteri_parentst~f:(fun_(Tparent)->assert(has_childparent~child:t);assert(is_necessaryparent);assert(t.height<parent.height));letcheckf=Invariant.check_fieldtfinFields.iter~id:(checkNode_id.invariant)~state:ignore~recomputed_at:(checkStabilization_num.invariant)~value_opt:(check(funvalue_opt->ifis_validt&¬(is_stalet)thenassert(Uopt.is_somevalue_opt);Uopt.invariantinvariant_avalue_opt))~kind:(check(funkind->Kind.invariantinvariant_akind;matchkindwith|Experte->Expert.invariant_about_num_invalid_childrene~is_necessary:(is_necessaryt)|_->()))~cutoff:(check(Cutoff.invariantinvariant_a))~changed_at:(check(funchanged_at->Stabilization_num.invariantchanged_at;ifStabilization_num.is_somet.recomputed_atthenassert(Stabilization_num.comparechanged_att.recomputed_at<=0)))~num_on_update_handlers:(check([%test_result:int]~expect:(List.lengtht.on_update_handlers+fold_observerst~init:0~f:(funn{on_update_handlers;_}->n+List.lengthon_update_handlers))))~num_parents:(check(funnum_parents->assert(num_parents>=0);assert(num_parents<=1+Array.lengtht.parent1_and_beyond)))~parent1_and_beyond:(check(funparent1_and_beyond->forparent_index=1toArray.lengthparent1_and_beyonddo[%test_eq:bool](parent_index<t.num_parents)(Uopt.is_someparent1_and_beyond.(parent_index-1))done))~parent0:(check(funparent0->[%test_eq:bool](t.num_parents>0)(Uopt.is_someparent0)))~created_in:(checkScope.invariant)~next_node_in_same_scope:(check(funnext_node_in_same_scope->ifScope.is_topt.created_in||not(is_validt)thenassert(Uopt.is_nonenext_node_in_same_scope)))~height:(check(funheight->ifis_necessarytthenassert(height>=0)elseassert(height=-1)))~height_in_recompute_heap:(check(funheight_in_recompute_heap->assert(height_in_recompute_heap>=-1);assert(height_in_recompute_heap<=t.height)))~prev_in_recompute_heap:(check(fun(prev_in_recompute_heap:Packed.tUopt.t)->ifnot(is_in_recompute_heapt)thenassert(Uopt.is_noneprev_in_recompute_heap);ifUopt.is_someprev_in_recompute_heapthen(let(Tprev)=Uopt.value_exnprev_in_recompute_heapinassert(packed_same(Tt)(Uopt.value_exnprev.next_in_recompute_heap));assert(t.height_in_recompute_heap=prev.height_in_recompute_heap))))~next_in_recompute_heap:(check(fun(next_in_recompute_heap:Packed.tUopt.t)->ifnot(is_in_recompute_heapt)thenassert(Uopt.is_nonenext_in_recompute_heap);ifUopt.is_somenext_in_recompute_heapthen(let(Tnext)=Uopt.value_exnnext_in_recompute_heapinassert(packed_same(Tt)(Uopt.value_exnnext.prev_in_recompute_heap));assert(t.height_in_recompute_heap=next.height_in_recompute_heap))))~height_in_adjust_heights_heap:(check(funheight_in_adjust_heights_heap->ifheight_in_adjust_heights_heap>=0thenassert(height_in_adjust_heights_heap<t.height)))~next_in_adjust_heights_heap:(check(fun(next_in_adjust_heights_heap:Packed.tUopt.t)->ifnot(is_in_adjust_heights_heapt)thenassert(Uopt.is_nonenext_in_adjust_heights_heap)elseifUopt.is_somenext_in_adjust_heights_heapthen(let(Tnext)=Uopt.value_exnnext_in_adjust_heights_heapinassert(is_in_adjust_heights_heapnext);assert(t.height_in_adjust_heights_heap=next.height_in_adjust_heights_heap))))~old_value_opt:(check(Uopt.invariantinvariant_a))~observers:(check(fun_->iter_observerst~f:(fun{state;observing;_}->assert(phys_equaltobserving);matchstatewith|In_use|Disallowed->()|Created|Unlinked->assertfalse)))~is_in_handle_after_stabilization:ignore~on_update_handlers:ignore~user_info:ignore~my_parent_index_in_child_at_index:(check(funmy_parent_index_in_child_at_index->(matcht.kindwith|Expert_->()|_->[%test_result:int](Array.lengthmy_parent_index_in_child_at_index)~expect:(initial_num_childrent));ifis_necessaryttheniteri_childrent~f:(funchild_index(Tchild)->assert(packed_same(Tt)(get_parentchild~index:my_parent_index_in_child_at_index.(child_index))))))~my_child_index_in_parent_at_index:(check(funmy_child_index_in_parent_at_index->[%test_result:int](Array.lengthmy_child_index_in_parent_at_index)~expect:(Array.lengtht.parent1_and_beyond+1);iteri_parentst~f:(funparent_index(Tparent)->assert(packed_same(Tt)(Kind.slow_get_childparent.kind~index:my_child_index_in_parent_at_index.(parent_index))))))~force_necessary:ignore~creation_backtrace:ignore);;letunsafe_valuet=Uopt.unsafe_valuet.value_optletvalue_exnt=ifUopt.is_somet.value_optthenUopt.unsafe_valuet.value_optelsefailwiths~here:[%here]"attempt to get value of an invalid node"t[%sexp_of:_t];;letget_cutofft=t.cutoffletset_cutofftcutoff=t.cutoff<-cutoffletis_constt=matcht.kindwith|Const_->true|_->false;;leton_updateton_update_handler=t.on_update_handlers<-on_update_handler::t.on_update_handlers;t.num_on_update_handlers<-t.num_on_update_handlers+1;;letrun_on_update_handlerstnode_update~now=letr=reft.on_update_handlersinwhilenot(List.is_empty!r)domatch!rwith|[]->assertfalse|on_update_handler::rest->r:=rest;On_update_handler.runon_update_handlernode_update~nowdone;letr=reft.observersinwhileUopt.is_some!rdoletobserver=Uopt.value_exn!rinr:=observer.next_in_observing;letr=refobserver.on_update_handlersinwhilenot(List.is_empty!r)domatch!rwith|[]->assertfalse|on_update_handler::rest->r:=rest;(* We have to test [state] before each on-update handler, because an on-update
handler might disable its own observer, which should prevent other on-update
handlers in the same observer from running. *)(matchobserver.statewith|Created|Unlinked->assertfalse|Disallowed->()|In_use->On_update_handler.runon_update_handlernode_update~now)donedone;;letset_kindtkind=t.kind<-kind;t.my_parent_index_in_child_at_index<-Array.create~len:(Kind.initial_num_childrenkind)(-1);;letcreatestatecreated_inkind=lett={id=Node_id.next();state;recomputed_at=Stabilization_num.none;value_opt=Uopt.none;kind;cutoff=Cutoff.phys_equal;changed_at=Stabilization_num.none;num_on_update_handlers=0;num_parents=0;parent1_and_beyond=[||];parent0=Uopt.none;created_in;next_node_in_same_scope=Uopt.none;height=-1;height_in_recompute_heap=-1;prev_in_recompute_heap=Uopt.none;next_in_recompute_heap=Uopt.none;height_in_adjust_heights_heap=-1;next_in_adjust_heights_heap=Uopt.none;old_value_opt=Uopt.none;observers=Uopt.none;is_in_handle_after_stabilization=false;on_update_handlers=[];my_parent_index_in_child_at_index=Array.create~len:(Kind.initial_num_childrenkind)(-1)(* [my_child_index_in_parent_at_index] has one element because it may need to hold
the child index of [parent0]. *);my_child_index_in_parent_at_index=[|-1|];force_necessary=false;user_info=None;creation_backtrace=(ifstate.keep_node_creation_backtracethenSome(Backtrace.get())elseNone)}inScope.add_nodecreated_int;(* [invariant] does not yet hold here because many uses of [Node.create] use [kind =
Uninitialized], and then mutate [t.kind] later. *)t;;letmax_num_parentst=1+Array.lengtht.parent1_and_beyondletmake_space_for_parent_if_necessaryt=ift.num_parents=max_num_parentstthen(letnew_max_num_parents=2*max_num_parentstint.parent1_and_beyond<-Array.realloct.parent1_and_beyond~len:(new_max_num_parents-1)Uopt.none;t.my_child_index_in_parent_at_index<-Array.realloct.my_child_index_in_parent_at_index~len:new_max_num_parents(-1));ifdebugthenassert(t.num_parents<max_num_parentst);;letmake_space_for_child_if_necessaryt~child_index=letmax_num_children=Array.lengtht.my_parent_index_in_child_at_indexinifchild_index>=max_num_childrenthen(ifdebugthenassert(child_index=max_num_children);letnew_max_num_children=Int.max2(2*max_num_children)int.my_parent_index_in_child_at_index<-Array.realloct.my_parent_index_in_child_at_index~len:new_max_num_children(-1));ifdebugthenassert(child_index<Array.lengtht.my_parent_index_in_child_at_index);;letset_parent:typea.child:at->parent:Packed.tUopt.t->parent_index:int->unit=fun~child~parent~parent_index->ifparent_index=0thenchild.parent0<-parentelsechild.parent1_and_beyond.(parent_index-1)<-parent;;letlink:typeab.child:at->child_index:int->parent:bt->parent_index:int->unit=fun~child~child_index~parent~parent_index->set_parent~child~parent:(Uopt.some(Packed.Tparent))~parent_index;child.my_child_index_in_parent_at_index.(parent_index)<-child_index;parent.my_parent_index_in_child_at_index.(child_index)<-parent_index;;letunlink:typeab.child:at->child_index:int->parent:bt->parent_index:int->unit=fun~child~child_index~parent~parent_index->set_parent~child~parent:Uopt.none~parent_index;ifdebugthen(child.my_child_index_in_parent_at_index.(parent_index)<--1;parent.my_parent_index_in_child_at_index.(child_index)<--1);;letadd_parent:typeab.child:at->parent:bt->child_index:int->unit=fun~child~parent~child_index->make_space_for_parent_if_necessarychild;make_space_for_child_if_necessaryparent~child_index;link~child~child_index~parent~parent_index:child.num_parents;child.num_parents<-child.num_parents+1;;letremove_parent:typeab.child:at->parent:bt->child_index:int->unit=fun~child~parent~child_index->ifdebugthenassert(child.num_parents>=1);letparent_index=parent.my_parent_index_in_child_at_index.(child_index)inifdebugthenassert(packed_same(Tparent)(get_parentchild~index:parent_index));letlast_parent_index=child.num_parents-1inifparent_index<last_parent_indexthen(let(Tparent)=Uopt.value_exnchild.parent1_and_beyond.(last_parent_index-1)inlink~child~child_index:child.my_child_index_in_parent_at_index.(last_parent_index)~parent~parent_index);unlink~child~child_index~parent~parent_index:last_parent_index;child.num_parents<-child.num_parents-1;;letswap_children_except_in_kindparent~child1~child_index1~child2~child_index2=ifdebugthen(assert(packed_same(Tchild1)(Kind.slow_get_childparent.kind~index:child_index1));assert(packed_same(Tchild2)(Kind.slow_get_childparent.kind~index:child_index2)));letindex_of_parent_in_child1=parent.my_parent_index_in_child_at_index.(child_index1)inletindex_of_parent_in_child2=parent.my_parent_index_in_child_at_index.(child_index2)inifdebugthen(assert(child1.my_child_index_in_parent_at_index.(index_of_parent_in_child1)=child_index1);assert(child2.my_child_index_in_parent_at_index.(index_of_parent_in_child2)=child_index2));(* now start swapping *)child1.my_child_index_in_parent_at_index.(index_of_parent_in_child1)<-child_index2;child2.my_child_index_in_parent_at_index.(index_of_parent_in_child2)<-child_index1;parent.my_parent_index_in_child_at_index.(child_index1)<-index_of_parent_in_child2;parent.my_parent_index_in_child_at_index.(child_index2)<-index_of_parent_in_child1;;modulePacked=structtypet=Packed.t=T:_Types.Node.t->t[@@unboxed]letsexp_of_t(Tt)=t|>[%sexp_of:_t]letinvariant(Tt)=invariantignoretmoduleAs_list(M:sigvalnext:Packed.t->Packed.tUopt.tend)=structtypet=Packed.tUopt.tletfoldt~init~f=letac=refinitinletr=reftinwhileUopt.is_some!rdoletpacked_node=Uopt.unsafe_value!rinr:=M.nextpacked_node;ac:=f!acpacked_nodedone;!ac;;letitert~f=foldt~init:()~f:(fun()n->fn)letinvariantt=itert~f:invariantletlengtht=foldt~init:0~f:(funn_->n+1)letto_listt=List.rev(foldt~init:[]~f:(funacn->n::ac))letsexp_of_tt=to_listt|>[%sexp_of:Packed.tlist]endletiter_descendants_internalts~f=letseen=Node_id.Hash_set.create()inletreciter_descendants(Tt)=ifnot(Hash_set.memseent.id)then(Hash_set.addseent.id;f(Tt);iteri_childrent~f:(fun_t->iter_descendantst))inList.iterts~f:iter_descendants;seen;;letiter_descendantsts~f=ignore(iter_descendants_internalts~f:_Hash_set.t)letsave_dotfilets=Out_channel.with_filefile~f:(funout->letnode_namenode="n"^Node_id.to_stringnode.idinfprintfout"digraph G {\n";fprintfout" rankdir = BT\n";letbind_edges=ref[]inletseen=iter_descendants_internalts~f:(fun(Tt)->letname=node_nametinfprintfout" %s [label=\"%s %s\\nheight = %d\"]\n"namename(Kind.namet.kind)t.height;iteri_childrent~f:(fun_(Tfrom_)->fprintfout" %s -> %s\n"(node_namefrom_)name);matcht.kindwith|Bind_lhs_changebind->Bind.iter_nodes_created_on_rhsbind~f:(funto_->bind_edges:=(Tt,to_)::!bind_edges)|_->())inList.iter!bind_edges~f:(fun(Tfrom,Tto_)->ifHash_set.memseento_.idthenfprintfout" %s -> %s [style=dashed]\n"(node_namefrom)(node_nameto_));fprintfout"}\n%!");;end