123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465(*
* Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)letsrc=Logs.Src.create"functoria"~doc:"functoria library"moduleLog=(valLogs.src_logsrc:Logs.LOG)type'at=|If:{cond:'tKey.value;branches:('t*'at)list;default:'at;}->'at|Dev:{dev:'adevice;args:('a,'v)tl;deps:abstractlist}->'vt|App:{f:'at;args:('a,'v)tl}->'vtandabstract=Abstract:_t->abstractand('a,'b)tl=|Nil:('a,'a)tl|Cons:'at*('b,'c)tl->('a->'b,'c)tland'adevice=('a,abstract)Device.t(** Constructors *)letabstractt=Abstracttletrecapp_has_no_arguments:typea.at->bool=function|App{f=_;args=Cons_}->false|App{f;args=Nil}->app_has_no_argumentsf|Dev{args=Nil;deps=[];dev}->(* special hack for Job.noop *)ifnot(String.equal(Device.module_namedev)"Unit")thenmatchDevice.runtime_argsdevwith[]->true|_->falseelsefalse|Dev_->false|If{cond=_;branches;default}->app_has_no_argumentsdefault||List.exists(fun(_,branch)->app_has_no_argumentsbranch)branches(* Devices *)letmk_dev~args~depsdev=Dev{dev;args;deps}letof_devicedev=mk_dev~args:Nil~deps:(Device.extra_depsdev)devletv?packages?packages_v?runtime_args?keys?extra_deps?connect?dune?configure?filesmodule_namemodule_type=of_device@@Device.v?packages?packages_v?runtime_args?keys?extra_deps?connect?dune?configure?filesmodule_namemodule_typeletmain?pos?packages?packages_v?runtime_args?keys?extra_depsmodule_namety=letconnect_=Device.start?posinv?packages?packages_v?runtime_args?keys?extra_deps~connectmodule_namety(* If *)letmk_switch~cond~branches~default=If{cond;branches;default}letif_condthen_else_=mk_switch~cond~branches:[(true,then_);(false,else_)]~default:then_letmatch_cond~defaultbranches=mk_switch~cond~branches~default(* App *)letrecconcat_tl:typeabc.(a,b)tl->(b,c)tl->(a,c)tl=funt1t2->matcht1withNil->t2|Cons(h,t)->Cons(h,concat_tltt2)letrecmk_app:typeav.f:at->args:(a,v)tl->vt=fun~f~args:args1->matchfwith|Dev{dev;args=args2;deps}->mk_dev~args:(concat_tlargs2args1)~depsdev|App{f;args=args2}->mk_app~f~args:(concat_tlargs2args1)|_->App{f;args=args1}let($)fx=mk_app~f~args:(Cons(x,Nil))(** Utilities *)letrecpp:typea.atFmt.t=funppf->function|Dev{dev;args;deps=_}->Fmt.pfppf"@[<v>@[Dev %a@]@,@[<v2>args=[%a]@]@]"(Device.pppp_abstract)devpp_tlargs|App{f;args}->Fmt.pfppf"App %a(%a)"ppfpp_tlargs|If{cond=_;branches;default}->Fmt.pfppf"Switch (_,%a,%a)"(Fmt.listpp)(List.mapsndbranches)ppdefaultandpp_tl:typeab.(a,b)tlFmt.t=funppf->function|Nil->()|Cons(h,t)->Fmt.pfppf"%a,@ %a"pphpp_tltandpp_abstractppf(Abstracti)=ppppfi(** Tables and traversals *)(* **** WARNING ******
The [impl] type forms a DAG, implemented as terms with sharing.
It is *essential* to preserve sharing while walking the terms.
Otherwise
- We risk double initialization of devices
- The DOT graph is a mess
- We might collect information twice
As such, the equality, hashing, and tables must be tuned to share
[impl]s appropriately and the various traversals must use appropriate tables.
*)letrechash:typea.at->int=function|Dev{dev;args;deps}->Hashtbl.hash(`Dev,Device.hashdev,hash_tlargs,List.maphash_abstractdeps)|App{f;args}->Hashtbl.hash(`App,hashf,hash_tlargs)|If{cond;branches;default}->Hashtbl.hash(`If,cond,List.map(fun(p,t)->Hashtbl.hash(p,hasht))branches,hashdefault)andhash_abstract(Abstractx)=hashxandhash_tl:typeav.(a,v)tl->int=funx->matchxwith|Nil->Hashtbl.hash`Nil|Cons(h,t)->Hashtbl.hash(`Cons,hashh,hash_tlt)typeex=Ex:'a->exletequal_listpl1l2=List.lengthl1=List.lengthl2&&List.for_all2pl1l2letrecequal:typet1t2.t1t->t2t->(t1,t2)Typeid.witness=funxy->match(x,y)with|Devc,Devc'->(match(equal_listequal_abstractc.depsc'.deps,equal_tlc.argsc'.args(Device.witnessc.devc'.dev))with|true,Eq->Eq|_->NotEq)|Appa,Appb->(matchequal_tla.argsb.args(equala.fb.f)with|Eq->Eq|NotEq->NotEq)|Ifx1,Ifx2->(match(equalx1.defaultx2.default,Obj.reprx1.cond==Obj.reprx2.cond,equal_list(fun(p1,t1)(p2,t2)->Exp1=Exp2&&equal_abstract(abstractt1)(abstractt2))x1.branchesx2.branches)with|Eq,true,true->Eq|_->NotEq)|_->NotEqandequal_abstract(Abstractx)(Abstracty)=Typeid.to_bool@@equalxyandequal_tl:typet1t2v1v2.(t1,v1)tl->(t2,v2)tl->(t1,t2)Typeid.witness->(v1,v2)Typeid.witness=funxyeq->match(x,y,eq)with|Nil,Nil,Eq->Eq|Cons(h1,t1),Cons(h2,t2),Eq->(match(equalh1h2,equal_tlt1t2Eq)withEq,Eq->Eq|_->NotEq)|_->NotEqmoduleTbl=Hashtbl.Make(structtypet=abstractlethash=hash_abstractletequal=equal_abstractend)moduleHashcons:sigtypetblvalcreate:unit->tblvaladd:tbl->'at->'at->unitvalget:tbl->'at->'atoptionend=structtypetbl=abstractTbl.tletcreate()=Tbl.create50letaddtblab=Tbl.addtbl(abstracta)(abstractb)letget(typea)tbl(oldv:at):atoption=ifTbl.memtbl@@abstractoldvthenlet(Abstractnewv)=Tbl.findtbl(abstractoldv)inmatchequaloldvnewvwithEq->Somenewv|NotEq->NoneelseNoneendletsimplify~full~context(Abstractt)=lettbl=Hashcons.create()inletrecaux:typea.at->at=funimpl->matchHashcons.gettblimplwith|Someimpl'->impl'|None->letacc=matchimplwith|If{cond;branches;default}->(* Either
- A key is present in the context
- We are in full mode, and we use its default value
*)iffull||Key.memcontextcondthenletpath=Key.evalcontextcondinlett=tryList.assocpathbrancheswithNot_found->defaultinauxtelseletbranches=List.map(fun(p,t)->(p,auxt))branchesinmk_switch~cond~branches~default|Dev{dev;args;deps}->letargs=aux_tlargsinletdeps=List.mapaux_abstractdepsinmk_dev~args~depsdev|App{f;args}->letf=auxfinletargs=aux_tlargsinmk_app~f~argsinHashcons.addtblimplacc;accandaux_abstract(Abstracta)=Abstract(auxa)andaux_tl:typeav.(a,v)tl->(a,v)tl=function|Nil->Nil|Cons(h,t)->Cons(auxh,aux_tlt)inAbstract(auxt)leteval~context(Abstractt)=letnew_id=letr=ref0infun()->incrr;!rinlettbl=Tbl.create50inletrecaux:typea.at->Device.Graph.t=funimpl->ifTbl.memtbl@@abstractimplthenTbl.findtbl(abstractimpl)elseletacc=matchimplwith|Dev{dev;args;deps}->letargs=aux_tlargsinletdeps=List.mapaux_abstractdepsinDevice.Graph.D{dev;args;deps;id=new_id()}|App{f;args=extra_args}->let(D{dev;args;deps;id=_})=auxfinletextra_args=aux_tlextra_argsinD{dev;args=args@extra_args;deps;id=new_id()}|If{cond;branches;default}->letpath=Key.evalcontextcondinlett=tryList.assocpathbrancheswithNot_found->defaultinauxtinTbl.addtbl(abstractimpl)acc;accandaux_abstract(Abstracta)=auxaandaux_tl:typeav.(a,v)tl->_=function|Nil->[]|Cons(h,t)->leta=auxhina::aux_tltinauxttype'bf_dev={f:'a.('a,abstract)Device.t->'b}letwith_left_most_devicectxt(f:_f_dev)=letrecaux:typea.at->_=function|Devd->f.fd.dev|Appa->auxa.f|If{cond;branches;default}->letpath=Key.evalctxcondinlett=tryList.assocpathbrancheswithNot_found->defaultinauxtinauxttype'bf_dev_full={f:'a'v.args:'blist->deps:'blist->'adevice->'b;}type'af_switch={if_:'r.cond:'rKey.value->branches:('r*'a)list->default:'a->'a;}type'af_app=f:'a->args:'alist->'aletmap(typer)~(mk_switch:_f_switch)~(mk_app:_f_app)~(mk_dev:_f_dev_full)t=lettbl=Tbl.create50inletrecaux:typea.at->r=funimpl->ifTbl.memtbl@@abstractimplthenTbl.findtbl(abstractimpl)elseletacc=matchimplwith|Dev{dev;args;deps}->letdeps=List.fold_right(fun(Abstractx)l->auxx::l)deps[]inletargs=aux_tlargsinmk_dev.f~args~depsdev|App{f;args}->letf=auxfinletargs=aux_tlargsinmk_app~f~args|If{cond;branches;default}->letbranches=List.map(fun(p,t)->(p,auxt))branchesinletdefault=auxdefaultinmk_switch.if_~cond~branches~defaultinTbl.addtbl(abstractimpl)acc;accandaux_tl:typeav.(a,v)tl->rlist=function|Nil->[]|Cons(h,t)->auxh::aux_tltinauxttypelabel=If:_Key.value->label|Dev:_Device.t->label|Appletcollect:typety.(moduleMisc.Monoidwithtypet=ty)->(label->ty)->abstract->ty=fun(moduleM)op(Abstractt)->letr=refM.emptyinletaddx=r:=M.union(opx)!rinletmk_switch={if_=(fun~cond~branches:_~default:_->add@@Ifcond)}andmk_app~f:_~args:_=addAppandmk_dev={f=(fun~args:_~deps:_dev->add@@Devdev)}inlet()=map~mk_switch~mk_app~mk_devtin!r(* {2 Dot output} *)moduleDot=structtypeedge_label=|Functor|Argument|Dependency|Branchof{default:bool}letas_dot_graph(Abstractt)=letr=ref0inletnew_id()=incrr;!rinletvertices=ref[]inletedges=ref[]inletaddrx=r:=x::!rinletmk_switch={if_=(fun~cond~branches~default->letid=new_id()inaddvertices(id,Ifcond);List.iter(fun(_,id')->addedges(id,id',Branch{default=false}))branches;addedges(id,default,Branch{default=true});id);}andmk_app~f~args=letid=new_id()inaddvertices(id,App);addedges(id,f,Functor);List.iter(funid'->addedges(id,id',Argument))args;idandmk_dev={f=(fun~args~depsdev->letid=new_id()inaddvertices(id,Devdev);List.iter(funid'->addedges(id,id',Argument))args;List.iter(funid'->addedges(id,id',Dependency))deps;id);}inlet_=map~mk_switch~mk_app~mk_devtin(List.rev!vertices,List.rev!edges)letpp_verticeppf(id,label)=letattrs=matchlabelwith|App->[("label","$");("shape","diamond")]|Ifcond->[("label",Fmt.str"If\n%a"Key.pp_depscond)]|Devdev->letname=Fmt.str"%s__%i"(Device.nice_namedev)idinletlabel=Fmt.str"%s\n%s\n%a"name(Device.module_namedev)Fmt.(list~sep:(any", ")Key.pp)(Device.keysdev)in[("label",label);("shape","box")]inletpp_attrppf(field,v)=Fmt.pfppf"%s=%S"fieldvinFmt.pfppf"%d [%a];"id(Fmt.list~sep:(Fmt.any", ")pp_attr)attrsletpp_edgesppf(id,id',label)=letattrs=matchlabelwith|Functor->[("style","bold");("tailport","sw")]|Argument->[]|Dependency->[("style","dashed")]|Branch{default}->letl=[("style","dotted");("headport","n")]inifdefaultthen("style","bold")::lelselinletpp_attrppf(field,v)=Fmt.pfppf"%s=%S"fieldvinFmt.pfppf"%d -> %d [%a];"idid'(Fmt.list~sep:(Fmt.any", ")pp_attr)attrsletppppft=letvertices,edges=as_dot_graphtinFmt.pfppf{|@[<v2>digraph G {@,ordering=out;@,%a@,@,%a@,}@]|}(Fmt.list~sep:Fmt.cutpp_vertice)vertices(Fmt.list~sep:Fmt.cutpp_edges)edgesendletpp_dot=Dot.pp