123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)typeannot=stringlisttype('l,'p)node=|Intof'l*Z.t|Stringof'l*string|Bytesof'l*Bytes.t|Primof'l*'p*('l,'p)nodelist*annot|Seqof'l*('l,'p)nodelisttypecanonical_location=inttype'pcanonical=Canonicalof(canonical_location,'p)nodeletcanonical_location_encoding=letopenData_encodingindef"micheline.location"~title:"Canonical location in a Micheline expression"~description:"The location of a node in a Micheline expression tree in prefix order, \
with zero being the root and adding one for every basic node, sequence \
and primitive application."@@int31letlocation=function|Int(loc,_)->loc|String(loc,_)->loc|Bytes(loc,_)->loc|Seq(loc,_)->loc|Prim(loc,_,_,_)->locletannotations=function|Int(_,_)->[]|String(_,_)->[]|Bytes(_,_)->[]|Seq(_,_)->[]|Prim(_,_,_,annots)->annotsletroot(Canonicalexpr)=expr(* We use a defunctionalized CPS implementation. The type below corresponds to that of
continuations. *)type('l,'p,'la,'pa)cont=|Seq_contof'la*('l,'p,'la,'pa)list_cont|Prim_contof'la*'pa*annot*('l,'p,'la,'pa)list_contand('l,'p,'la,'pa)list_cont=|List_contof('l,'p)nodelist*('la,'pa)nodelist*('l,'p,'la,'pa)cont|Returnletstrip_locations(typeab)(root:(a,b)node):bcanonical=letid=letid=ref(-1)infun()->incrid;!idinletrecstrip_locationslk=letid=id()inmatchlwith|Int(_,v)->(apply[@tailcall])k(Int(id,v))|String(_,v)->(apply[@tailcall])k(String(id,v))|Bytes(_,v)->(apply[@tailcall])k(Bytes(id,v))|Seq(_,seq)->(strip_locations_list[@tailcall])seq[](Seq_cont(id,k))|Prim(_,name,seq,annots)->(strip_locations_list[@tailcall])seq[](Prim_cont(id,name,annots,k))andstrip_locations_listlsacck=matchlswith|[]->(apply_list[@tailcall])k(List.revacc)|x::tl->(strip_locations[@tailcall])x(List_cont(tl,acc,k))andapplyknode=matchkwith|List_cont(tl,acc,k)->(strip_locations_list[@tailcall])tl(node::acc)k|Return->nodeandapply_listknode_list=matchkwith|Seq_cont(id,k)->(apply[@tailcall])k(Seq(id,node_list))|Prim_cont(id,name,annots,k)->(apply[@tailcall])k(Prim(id,name,node_list,annots))inCanonical(strip_locationsrootReturn)letextract_locations:typelp.(l,p)node->pcanonical*(canonical_location*l)list=funroot->letid=letid=ref(-1)infun()->incrid;!idinletloc_table=ref[]inletrecstrip_locationslk=letid=id()inmatchlwith|Int(loc,v)->loc_table:=(id,loc)::!loc_table;(apply[@tailcall])k(Int(id,v))|String(loc,v)->loc_table:=(id,loc)::!loc_table;(apply[@tailcall])k(String(id,v))|Bytes(loc,v)->loc_table:=(id,loc)::!loc_table;(apply[@tailcall])k(Bytes(id,v))|Seq(loc,seq)->loc_table:=(id,loc)::!loc_table;(strip_locations_list[@tailcall])seq[](Seq_cont(id,k))|Prim(loc,name,seq,annots)->loc_table:=(id,loc)::!loc_table;(strip_locations_list[@tailcall])seq[](Prim_cont(id,name,annots,k))andstrip_locations_listlsacck=matchlswith|[]->(apply_list[@tailcall])k(List.revacc)|x::tl->(strip_locations[@tailcall])x(List_cont(tl,acc,k))andapplyknode=matchkwith|List_cont(tl,acc,k)->(strip_locations_list[@tailcall])tl(node::acc)k|Return->nodeandapply_listknode_list=matchkwith|Seq_cont(id,k)->(apply[@tailcall])k(Seq(id,node_list))|Prim_cont(id,name,annots,k)->(apply[@tailcall])k(Prim(id,name,node_list,annots))inletstripped=strip_locationsrootReturnin(Canonicalstripped,List.rev!loc_table)letinject_locations:typelp.(canonical_location->l)->pcanonical->(l,p)node=funlookup(Canonicalroot)->letrecinject_locationslk=matchlwith|Int(loc,v)->(apply[@tailcall])k(Int(lookuploc,v))|String(loc,v)->(apply[@tailcall])k(String(lookuploc,v))|Bytes(loc,v)->(apply[@tailcall])k(Bytes(lookuploc,v))|Seq(loc,seq)->(inject_locations_list[@tailcall])seq[](Seq_cont(lookuploc,k))|Prim(loc,name,seq,annots)->(inject_locations_list[@tailcall])seq[](Prim_cont(lookuploc,name,annots,k))andinject_locations_listlsacck=matchlswith|[]->(apply_list[@tailcall])k(List.revacc)|x::tl->(inject_locations[@tailcall])x(List_cont(tl,acc,k))andapplyknode=matchkwith|List_cont(tl,acc,k)->(inject_locations_list[@tailcall])tl(node::acc)k|Return->nodeandapply_listknode_list=matchkwith|Seq_cont(id,k)->(apply[@tailcall])k(Seq(id,node_list))|Prim_cont(id,name,annots,k)->(apply[@tailcall])k(Prim(id,name,node_list,annots))ininject_locationsrootReturnletmap:typeab.(a->b)->acanonical->bcanonical=funf(Canonicalexpr)->letrecmap_nodelk=matchlwith|(Int_|String_|Bytes_)asnode->(apply[@tailcall])knode|Seq(loc,seq)->(map_list[@tailcall])seq[](Seq_cont(loc,k))|Prim(loc,name,seq,annots)->(map_list[@tailcall])seq[](Prim_cont(loc,fname,annots,k))andmap_listlsacck=matchlswith|[]->(apply_list[@tailcall])k(List.revacc)|x::tl->(map_node[@tailcall])x(List_cont(tl,acc,k))andapplyknode=matchkwith|List_cont(tl,acc,k)->(map_list[@tailcall])tl(node::acc)k|Return->nodeandapply_listknode_list=matchkwith|Seq_cont(id,k)->(apply[@tailcall])k(Seq(id,node_list))|Prim_cont(id,name,annots,k)->(apply[@tailcall])k(Prim(id,name,node_list,annots))inCanonical(map_nodeexprReturn)letmap_node:typelalbpapb.(la->lb)->(pa->pb)->(la,pa)node->(lb,pb)node=funflfpnode->letrecmap_nodeflfpnodek=matchnodewith|Int(loc,v)->(apply[@tailcall])flfpk(Int(flloc,v))|String(loc,v)->(apply[@tailcall])flfpk(String(flloc,v))|Bytes(loc,v)->(apply[@tailcall])flfpk(Bytes(flloc,v))|Seq(loc,seq)->(map_node_list[@tailcall])flfpseq[](Seq_cont(flloc,k))|Prim(loc,name,seq,annots)->(map_node_list[@tailcall])flfpseq[](Prim_cont(flloc,fpname,annots,k))andmap_node_listflfplsacck=matchlswith|[]->(apply_list[@tailcall])flfpk(List.revacc)|x::tl->(map_node[@tailcall])flfpx(List_cont(tl,acc,k))andapplyflfpknode=matchkwith|List_cont(tl,acc,k)->(map_node_list[@tailcall])flfptl(node::acc)k|Return->nodeandapply_listflfpknode_list=matchkwith|Seq_cont(id,k)->(apply[@tailcall])flfpk(Seq(id,node_list))|Prim_cont(id,name,annots,k)->(apply[@tailcall])flfpk(Prim(id,name,node_list,annots))in(map_node[@tailcall])flfpnodeReturntypesemantics=V0|V1letinternal_canonical_encoding~semantics~variantprim_encoding=letopenData_encodinginletint_encoding=obj1(req"int"z)inletstring_encoding=obj1(req"string"string)inletbytes_encoding=obj1(req"bytes"bytes)inletint_encodingtag=casetagint_encoding~title:"Int"(functionInt(_,v)->Somev|_->None)(funv->Int(0,v))inletstring_encodingtag=casetagstring_encoding~title:"String"(functionString(_,v)->Somev|_->None)(funv->String(0,v))inletbytes_encodingtag=casetagbytes_encoding~title:"Bytes"(functionBytes(_,v)->Somev|_->None)(funv->Bytes(0,v))inletseq_encodingtagexpr_encoding=casetag(listexpr_encoding)~title:"Sequence"(functionSeq(_,v)->Somev|_->None)(funargs->Seq(0,args))inletannots_encoding=letsplits=ifs=""&&semantics<>V0then[]elseletannots=String.split_on_char' 'sinList.iter(funa->ifString.lengtha>255thenfailwith"Oversized annotation")annots;ifString.concat" "annots<>sthenfailwith"Invalid annotation string, must be a sequence of valid \
annotations with spaces";annotsinsplitted~json:(list(Bounded.string255))~binary:(conv(String.concat" ")splitstring)inletapplication_encodingtagexpr_encoding=casetag~title:"Generic prim (any number of args with or without annot)"(obj3(req"prim"prim_encoding)(dft"args"(listexpr_encoding)[])(dft"annots"annots_encoding[]))(function|Prim(_,prim,args,annots)->Some(prim,args,annots)|_->None)(fun(prim,args,annots)->Prim(0,prim,args,annots))inletnode_encoding=mu("micheline."^variant^".expression")(funexpr_encoding->splitted~json:(union~tag_size:`Uint8[int_encodingJson_only;string_encodingJson_only;bytes_encodingJson_only;seq_encodingJson_onlyexpr_encoding;application_encodingJson_onlyexpr_encoding;])~binary:(union~tag_size:`Uint8[int_encoding(Tag0);string_encoding(Tag1);seq_encoding(Tag2)expr_encoding;(* No args, no annot *)case(Tag3)~title:"Prim (no args, annot)"(obj1(req"prim"prim_encoding))(functionPrim(_,v,[],[])->Somev|_->None)(funv->Prim(0,v,[],[]));(* No args, with annots *)case(Tag4)~title:"Prim (no args + annot)"(obj2(req"prim"prim_encoding)(req"annots"annots_encoding))(function|Prim(_,v,[],annots)->Some(v,annots)|_->None)(function(prim,annots)->Prim(0,prim,[],annots));(* Single arg, no annot *)case(Tag5)~title:"Prim (1 arg, no annot)"(obj2(req"prim"prim_encoding)(req"arg"expr_encoding))(function|Prim(_,v,[arg],[])->Some(v,arg)|_->None)(function(prim,arg)->Prim(0,prim,[arg],[]));(* Single arg, with annot *)case(Tag6)~title:"Prim (1 arg + annot)"(obj3(req"prim"prim_encoding)(req"arg"expr_encoding)(req"annots"annots_encoding))(function|Prim(_,prim,[arg],annots)->Some(prim,arg,annots)|_->None)(fun(prim,arg,annots)->Prim(0,prim,[arg],annots));(* Two args, no annot *)case(Tag7)~title:"Prim (2 args, no annot)"(obj3(req"prim"prim_encoding)(req"arg1"expr_encoding)(req"arg2"expr_encoding))(function|Prim(_,prim,[arg1;arg2],[])->Some(prim,arg1,arg2)|_->None)(fun(prim,arg1,arg2)->Prim(0,prim,[arg1;arg2],[]));(* Two args, with annots *)case(Tag8)~title:"Prim (2 args + annot)"(obj4(req"prim"prim_encoding)(req"arg1"expr_encoding)(req"arg2"expr_encoding)(req"annots"annots_encoding))(function|Prim(_,prim,[arg1;arg2],annots)->Some(prim,arg1,arg2,annots)|_->None)(fun(prim,arg1,arg2,annots)->Prim(0,prim,[arg1;arg2],annots));(* General case *)application_encoding(Tag9)expr_encoding;bytes_encoding(Tag10);]))inconv(functionCanonicalnode->node)(funnode->strip_locationsnode)node_encodingletcanonical_encoding~variantprim_encoding=internal_canonical_encoding~semantics:V1~variantprim_encodingletcanonical_encoding_v1~variantprim_encoding=internal_canonical_encoding~semantics:V1~variantprim_encodingletcanonical_encoding_v0~variantprim_encoding=internal_canonical_encoding~semantics:V0~variantprim_encodinglettable_encoding~variantlocation_encodingprim_encoding=letopenData_encodinginconv(funnode->let(canon,assoc)=extract_locationsnodeinlet(_,table)=List.splitassocin(canon,table))(fun(canon,table)->lettable=Array.of_listtableininject_locations(funi->table.(i))canon)(obj2(req"expression"(canonical_encoding~variantprim_encoding))(req"locations"(listlocation_encoding)))leterased_encoding~variantdefault_locationprim_encoding=letopenData_encodinginconv(funnode->strip_locationsnode)(funcanon->inject_locations(fun_->default_location)canon)(canonical_encoding~variantprim_encoding)(** Testing
-------
Component: Micheline
Invocation: dune build @src/lib_micheline/runtest
Subject: Test preservation of semantics wrt original implementation
*)let%test_module"semantics_preservation"=(modulestructmoduleOriginal=structletstrip_locationsroot=letid=letid=ref(-1)infun()->incrid;!idinletrecstrip_locationsl=letid=id()inmatchlwith|Int(_,v)->Int(id,v)|String(_,v)->String(id,v)|Bytes(_,v)->Bytes(id,v)|Seq(_,seq)->Seq(id,List.mapstrip_locationsseq)|Prim(_,name,seq,annots)->Prim(id,name,List.mapstrip_locationsseq,annots)inCanonical(strip_locationsroot)letextract_locationsroot=letid=letid=ref(-1)infun()->incrid;!idinletloc_table=ref[]inletrecstrip_locationsl=letid=id()inmatchlwith|Int(loc,v)->loc_table:=(id,loc)::!loc_table;Int(id,v)|String(loc,v)->loc_table:=(id,loc)::!loc_table;String(id,v)|Bytes(loc,v)->loc_table:=(id,loc)::!loc_table;Bytes(id,v)|Seq(loc,seq)->loc_table:=(id,loc)::!loc_table;Seq(id,List.mapstrip_locationsseq)|Prim(loc,name,seq,annots)->loc_table:=(id,loc)::!loc_table;Prim(id,name,List.mapstrip_locationsseq,annots)inletstripped=strip_locationsrootin(Canonicalstripped,List.rev!loc_table)letinject_locationslookup(Canonicalroot)=letrecinject_locationsl=matchlwith|Int(loc,v)->Int(lookuploc,v)|String(loc,v)->String(lookuploc,v)|Bytes(loc,v)->Bytes(lookuploc,v)|Seq(loc,seq)->Seq(lookuploc,List.mapinject_locationsseq)|Prim(loc,name,seq,annots)->Prim(lookuploc,name,List.mapinject_locationsseq,annots)ininject_locationsrootletmapf(Canonicalexpr)=letrecmap_nodef=function|(Int_|String_|Bytes_)asnode->node|Seq(loc,seq)->Seq(loc,List.map(map_nodef)seq)|Prim(loc,name,seq,annots)->Prim(loc,fname,List.map(map_nodef)seq,annots)inCanonical(map_nodefexpr)letrecmap_nodeflfp=function|Int(loc,v)->Int(flloc,v)|String(loc,v)->String(flloc,v)|Bytes(loc,v)->Bytes(flloc,v)|Seq(loc,seq)->Seq(flloc,List.map(map_nodeflfp)seq)|Prim(loc,name,seq,annots)->Prim(flloc,fpname,List.map(map_nodeflfp)seq,annots)endmoduleSampler=struct(* Sampler copied from [micheline_benchmarks.ml] - lib-micheline cannot depend
on lib-shell-benchmarks. *)type'asampler=Random.State.t->'atypewidth_function=depth:int->intsamplertypenode_kind=|Int_node|String_node|Bytes_node|Seq_node|Prim_node(* We skew the distribution towards non-leaf nodes by repeating the
relevant kinds ;) *)letall_kinds=[|Int_node;String_node;Bytes_node;Seq_node;Prim_node|]letsample_kind:node_kindsampler=funrng_state->leti=Random.State.intrng_state(Array.lengthall_kinds)inall_kinds.(i)letsample_string_=""letsample_bytes_=Bytes.emptyletsample_z_=Z.zeroletsample(w:width_function)rng_state=letrecsampledepthrng_statek=matchsample_kindrng_statewith|Int_node->k(Int(0,sample_zrng_state))|String_node->k(String(0,sample_stringrng_state))|Bytes_node->k(Bytes(0,sample_bytesrng_state))|Seq_node->letwidth=w~depthrng_stateinsample_listdepthwidth[](funterms->k(Seq(0,terms)))rng_state|Prim_node->letwidth=w~depthrng_stateinsample_listdepthwidth[](funterms->k(Prim(0,(),terms,[])))rng_stateandsample_listdepthwidthacckrng_state=ifwidth<0theninvalid_arg"sample_list: negative width"elseifwidth=0thenk(List.revacc)elsesample(depth+1)rng_state(funx->sample_listdepth(width-1)(x::acc)krng_state)insample0rng_state(funx->x)letsample_in_intervalminmaxstate=ifmax-min>=0thenmin+Random.State.intstate(max-min+1)elseinvalid_arg"sample_in_interval"letreasonable_width_function~depthrng_state=(* Entirely ad-hoc *)sample_in_interval0(20/(Bits.numbitsdepth+1))rng_stateletsample=samplereasonable_width_functionendletrng_state=Random.State.make[|0x1337;0x533D|]letrecsample_and_check_n_timesnfg=ifn<=0then()elseletterm=Sampler.samplerng_statein(* Is this a legit use of polymorphic equality? *)assert(fterm=gterm);sample_and_check_n_times(n-1)fgletrecsample_and_check_n_times_canonnfg=ifn<=0then()elseletterm=Sampler.samplerng_stateinletterm=strip_locationstermin(* Is this a legit use of polymorphic equality? *)assert(fterm=gterm);sample_and_check_n_times_canon(n-1)fglet%test_unit"strip_locations"=sample_and_check_n_times1_000Original.strip_locationsstrip_locationslet%test_unit"extract_locations"=sample_and_check_n_times1_000Original.extract_locationsextract_locationslet%test_unit"inject_locations"=sample_and_check_n_times_canon1_000(Original.inject_locations(funi->i))(inject_locations(funi->i))let%test_unit"map"=sample_and_check_n_times_canon1_000(Original.map(fun_i->()))(map(fun_i->()))let%test_unit"map_node"=sample_and_check_n_times1_000(Original.map_node(fun_i->())(fun_i->()))(map_node(fun_i->())(fun_i->()))end)