123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523openImport(* Mini clone of Dune_lang.Decoder. Main advantage is that it forbids all the
crazy stuff and is automatically bi-directional *)(* TODO error handling is complete crap for now.
This should be unified with [Dune_lang.Decoder] eventually. *)typeerror=|Parse_errorof{message:string;payload:(string*Sexp.t)list}|Version_errorof{since:int*int;until:(int*int)option;message:string;payload:(string*Sexp.t)list}letdyn_of_error=letopenDyninfunction|Version_error{message;payload;until;since}->record["message",stringmessage;"payload",list(pairstringSexp.to_dyn)payload;"until",option(pairintint)until;"since",(pairintint)since]|Parse_error{message;payload}->record["message",stringmessage;"payload",list(pairstringSexp.to_dyn)payload];;exceptionOf_sexpoferrorletraise_of_sexp?(payload=[])message=raise(Of_sexp(Parse_error{message;payload}));;letraise_version_error?until?(payload=[])~sincemessage=raise(Of_sexp(Version_error{since;until;message;payload}));;let()=Printexc.register_printer(function|Of_sexp(Parse_error{message;payload})->Some(message^" "^Sexp.to_string(Sexp.recordpayload))|_->None);;moduleFields=structtypet=UnparsedofSexp.tString.Map.tletcheck_empty(Unparseds)=ifnot(String.Map.is_emptys)then(letpayload=[("unparsed",Sexp.List(String.Map.to_lists|>List.map~f:(fun(k,v)->Sexp.List[Sexp.Atomk;v])))]inraise_of_sexp~payload"unexpected fields");;letempty=UnparsedString.Map.emptyletmerge(Unparseda)(Unparsedb)=Unparsed(String.Map.unionab~f:(fun___->(* field names are guaranteed to be different at construction time in
[Both] *)assertfalse));;letof_fieldnamesexp=Unparsed(String.Map.singletonnamesexp)letof_sexp(x:Sexp.t)=matchxwith|Atom_->raise_of_sexp"Unexpected atom"|Listx->(matchString.Map.of_list_mapx~f:(function|List[Atoms;v]->s,v|_->raise_of_sexp"unable to read field")with|Error(s,_,_)->raise_of_sexp"duplicate fields"~payload:["field",Atoms]|Oks->Unparseds);;letoptional(Unparsedt)name=matchString.Map.findtnamewith|None->None,Unparsedt|Somev->Somev,Unparsed(String.Map.removetname);;letrequiredtname=letr,t=optionaltnameinmatchrwith|Somes->s,t|None->raise_of_sexp"missing required field"~payload:["name",Atomname];;letto_sexp(Unparsedt):Sexp.t=List(String.Map.to_listt|>List.map~f:(fun(k,v)->Sexp.List[Atomk;v]));;endtypevalues=Sexp.ttypefields=Fields.ttypeversion={since:int*int;until:(int*int)option}type('a,'kind)t=|String:(string,values)t|Int:(int,values)t|Float:(float,values)t|Unit:(unit,values)t|Char:(char,values)t|Iso:('a,'kind)t*('a->'b)*('b->'a)->('b,'kind)t|Iso_result:('a,'kind)t*('a->('b,exn)result)*('b->'a)->('b,'kind)t|Version:('a,'kind)t*version->('a,'kind)t|Both:(* Invariant: field names must be different *)('a,fields)t*('b,fields)t->('a*'b,fields)t|Sexp:(Sexp.t,values)t|List:('a,values)t->('alist,values)t|Field:string*'afield->('a,fields)t|Enum:(string*'a)list->('a,values)t|Sum:'aeconstrlist*('a->case)->('a,values)t|Pair:('a,values)t*('b,values)t->('a*'b,values)t|Triple:('a,values)t*('b,values)t*('c,values)t->('a*'b*'c,values)t|Fdecl:int*('a,'k)tFdecl.t->('a,'k)t|Either:(* Invariant: field names must be different *)('a,fields)t*('b,fields)t->(('a,'b)Either.t,fields)t|Record:('a,fields)t->('a,values)tand('a,'arg)constr={(* TODO allow constructors without an argument *)name:string;arg:('arg,values)t;inj:'arg->'a}and'aeconstr=Constr:('a,'arg)constr->'aeconstrandcase=Case:'arg*('a,'arg)constr->caseand'afield=|Required:('a,values)t->'afield|Optional:('a,values)t->'aoptionfieldand'kret=|Values:valuesret|Fields:Fields.t->fieldsrettype'avalue=('a,values)tletcaseac=Case(a,c)letconstrnamearginj={name;arg;inj}leteconstrc=Constrcletbothxy=Both(x,y)letlistx=Listxletsumxy=Sum(x,y)letpairxy=Pair(x,y)lettriplexyz=Triple(x,y,z)letdiscard_values((a,x):_*valuesret)=match(x:valuesret)with|Values->a;;letstring=Stringletint=Intletfloat=Floatletunit=Unitletoptionx=letnone=constr"None"unit(fun()->None)inletsome=constr"Some"x(funx->Somex)insum[econstrnone;econstrsome](function|None->case()none|Somes->casessome);;letchar=Charletsexp_for_digestt=letreciter:typeab.intlist->(a,b)t->Sexp.t=funids->function|String->Atom"String"|Int->Atom"Int"|Float->Atom"Float"|Unit->Atom"Unit"|Char->Atom"Char"|Iso(t,_,_)->List[Atom"Iso";iteridst]|Iso_result(t,_,_)->List[Atom"Iso_result";iteridst]|Version(t,{since=a,b;until})->letitems:Sexp.tlist=[Atom"Version";iteridst;List[Atom"since";Atom(Int.to_stringa);Atom(Int.to_stringb)]]inletitems=matchuntilwith|None->items|Some(a,b)->items@[List[Atom"until";Atom(Int.to_stringa);Atom(Int.to_stringb)]]inListitems|Both(a,b)->List[Atom"Both";iteridsa;iteridsb]|Sexp->Atom"Sexp"|Listt->List[Atom"List";iteridst]|Field(name,field)->letfield:Sexp.t=matchfieldwith|Requiredt->List[Atom"Required";iteridst]|Optionalt->List[Atom"Optional";iteridst]inList[Atom"Field";Atomname;field]|Enumcases->List(Atom"Enum"::List.mapcases~f:(fun(name,_):Sexp.t->Atomname))|Sum(constrs,_)->List(Atom"Sum"::List.mapconstrs~f:(fun(Constr{name;arg;inj=_}):Sexp.t->List[Atomname;iteridsarg]))|Pair(a,b)->List[Atom"Pair";iteridsa;iteridsb]|Triple(a,b,c)->List[Atom"Triple";iteridsa;iteridsb;iteridsc]|Fdecl(id,fdecl)->(* Although the id is represented as an auto-incrementing integer, we
find De Bruijn indices to put in the digest so that equivalent
structures produce the same digest. *)(matchList.findiids~f:(Int.equalid)with|Some(_,index)->List[Atom"Recurse";Atom(Int.to_stringindex)]|None->List[Atom"Fixpoint";iter(id::ids)(Fdecl.getfdecl)])|Either(a,b)->List[Atom"Either";iteridsa;iteridsb]|Recordt->List[Atom"Record";iteridst]initer[]t;;letto_sexp:'a.('a,values)t->'a->Sexp.t=funta->letrecloop:typeak.(a,k)t->a->k=funta->matchtwith|String->Atoma|Int->Atom(Int.to_stringa)|Float->Atom(Float.to_stringa)|Unit->List[]|Char->Atom(String.make1a)|Sexp->a|Version(t,_)->loopta|Fdecl(_,t)->loop(Fdecl.gett)a|Listt->List(List.mapa~f:(loopt))|Pair(x,y)->leta,b=ainList[loopxa;loopyb]|Triple(x,y,z)->leta,b,c=ainList[loopxa;loopyb;loopzc]|Recordr->letfields=looprainFields.to_sexpfields|Field(name,spec)->(matchspecwith|Requiredt->Fields.of_fieldname(loopta)|Optionalt->(matchawith|None->Fields.empty|Somea->Fields.of_fieldname(loopta)))|Iso_result(t,_,from)->loopt(froma)|Iso(t,_,from)->loopt(froma)|Both(x,y)->letx=loopx(fsta)inlety=loopy(snda)inFields.mergexy|Either(x,y)->(matchawith|Lefta->loopxa|Righta->loopya)|Sum(_,constr)->let(Case(a,constr))=constrainletarg=loopconstr.argainSexp.List[Atomconstr.name;arg]|Enumchoices->(matchList.find_mapchoices~f:(fun(s,a')->ifPoly.equalaa'thenSomeselseNone)with|Somev->Atomv|None->letopenDyninCode_error.raise"enum does not include this value"["valid values",list(fun(x,_)->stringx)choices])inloopta;;letcheck_version~version~since~until_ctx=ifversion<since||matchuntilwith|None->false|Someuntil->version>untilthenraise_version_error?until~since"invalid version";;letof_sexp:'a.('a,values)t->version:int*int->Sexp.t->'a=funt~versionsexp->letrecloop:typeak.(a,k)t->k->a*kret=fun(typeak)(t:(a,k)t)(ctx:k):(a*kret)->matchtwith|String->(matchctxwith|Atoms->s,Values|List_aslist->raise_of_sexp~payload:["list",list]"string: expected atom. received list")|Int->(matchctxwith|List_aslist->raise_of_sexp~payload:["list",list]"int: expected atom. received list"|Atoms->(matchInt.of_stringswith|None->raise_of_sexp"unable to read int"|Somei->i,Values))|Float->(matchctxwith|List_aslist->raise_of_sexp~payload:["list",list]"float: expected atom. received list"|Atoms->(matchFloat.of_string_optswith|None->raise_of_sexp"unable to read float"|Somei->i,Values))|Unit->(matchctxwith|List[]->(),Values|_->raise_of_sexp"expected empty list")|Char->(matchctxwith|Atoms->ifString.lengths=1thens.[0],Valueselseraise_of_sexp"expected only a single character"|List_->raise_of_sexp"expected a string of length 1")|Sexp->ctx,Values|Version(t,{since;until})->check_version~version~since~untilctx;looptctx|Fdecl(_,t)->loop(Fdecl.gett)ctx|Listt->(matchctxwith|Listxs->List.mapxs~f:(funx->discard_values(looptx)),Values|Atom_->raise_of_sexp"expected list")|Pair(x,y)->(matchctxwith|List[a;b]->leta,Values=loopxainletb,Values=loopybin(a,b),Values|_->raise_of_sexp"expected field entry")|Triple(x,y,z)->(matchctxwith|List[a;b;c]->leta,Values=loopxainletb,Values=loopybinletc,Values=loopzcin(a,b,c),Values|_->raise_of_sexp"expected field entry")|Record(r:(a,fields)t)->let(fields:Fields.t)=Fields.of_sexpctxinleta,Fieldsf=looprfieldsinFields.check_emptyf;a,Values|Field(name,spec)->(matchspecwith|Requiredv->letfield,rest=Fields.requiredctxnameinlett,Values=loopvfieldint,Fieldsrest|Optionalv->letfield,rest=Fields.optionalctxnameinlett=matchfieldwith|None->None|Somef->leta,Values=loopvfinSomeaint,Fieldsrest)|Either(x,y)->(try(* TODO share computation somehow *)leta,x=loopxctxinLefta,xwith|Of_sexp_->leta,y=loopyctxinRighta,y)|Iso(t,f,_)->leta,k=looptctxinfa,k|Iso_result(t,f,_)->leta,k=looptctxin(matchfawith|Errorexn->raiseexn|Oka->a,k)|Both(x,y)->leta,Fieldsk=loopxctxinletb,k=loopykin(a,b),k|Sum(constrs,_)->(matchctxwith|List[Atomhead;args]->(matchList.find_mapconstrs~f:(fun(Constrc)->ifhead=c.namethenSome(leta,k=loopc.argargsinc.inja,k)elseNone)with|None->raise_of_sexp"invalid constructor name"|Somep->p)|_->raise_of_sexp"expected constructor")|Enumchoices->(matchctxwith|List_->raise_of_sexp"expected list"|Atoma->(matchList.assocchoicesawith|None->raise_of_sexp"unable to read enum"|Somes->s,Values))indiscard_values(looptsexp);;letof_sexpconv~versionsexp=matchof_sexpconv~versionsexpwith|s->Oks|exceptionOf_sexpe->Errore;;letrecordr=Recordrleteitherxy=Either(x,y)letisoatf=Iso(a,t,f)letiso_resultatf=Iso_result(a,t,f)letversion?untilt~since=Version(t,{until;since})letfieldnamespec=Field(name,spec)letenumchoices=Enumchoicesletthreeabc=iso(Both(a,Both(b,c)))(fun(x,(y,z))->x,y,z)(fun(x,y,z)->x,(y,z));;letfourabcd=iso(both(bothab)(bothcd))(fun((w,x),(y,z))->w,x,y,z)(fun(w,x,y,z)->(w,x),(y,z));;letfiveabcde=iso(both(bothab)(threecde))(fun((a,b),(c,d,e))->a,b,c,d,e)(fun(a,b,c,d,e)->(a,b),(c,d,e));;letsixabcdef=iso(both(threeabc)(threedef))(fun((a,b,c),(d,e,f))->a,b,c,d,e,f)(fun(a,b,c,d,e,f)->(a,b,c),(d,e,f));;letsevenabcdefg=iso(both(threeabc)(fourdefg))(fun((a,b,c),(d,e,f,g))->a,b,c,d,e,f,g)(fun(a,b,c,d,e,f,g)->(a,b,c),(d,e,f,g));;leteightabcdefgh=iso(both(fourabcd)(fourefgh))(fun((a,b,c,d),(e,f,g,h))->a,b,c,d,e,f,g,h)(fun(a,b,c,d,e,f,g,h)->(a,b,c,d),(e,f,g,h));;letsexp=Sexpletrequiredx=Requiredxletoptionalx=Optionalxletfdecl_id=ref0letfixpointf=letfdecl=Fdecl.createDyn.opaqueinletid=!fdecl_idinincrfdecl_id;letresult=Fdecl(id,fdecl)inFdecl.setfdecl(fresult);result;;leterrore=raise(Of_sexpe)